My structure.
User:
{
name: "One",
favoriteWorkouts: [ids of workouts],
workouts: [ { name: "My workout 1" },...]
}
I want to get list of favorits/hottest workouts from database.
db.users.aggregate(
{ $unwind : "$favorite" },
{ $group : { _id : "$favorite" , number : { $sum : 1 } } },
{ $sort : { number : -1 } }
)
This returns
{
"hot": [
{
"_id": "521f6c27145c5d515f000006",
"number": 1
},
{
"_id": "521f6c2f145c5d515f000007",
"number": 1
},...
]}
But I want
{
hot: [
{object of hottest workout 1, object of hottest workout 2,...}
]}
How do you sort hottest data and fill the result with object, not just ids?
You are correct to want to use MongoDB's aggregation framework. Aggregation will give you the output you are looking for if used correctly. If you are looking for just a list of the _id's of all users' favorite workouts, then I believe that you would need to add an additional $group operation to your pipeline:
db.users.aggregate(
{ $unwind : "$favoriteWorkouts" },
{ $group : { _id : "$favoriteWorkouts", number : { $sum : 1 } } },
{ $sort : { number : -1 } },
{ $group : { _id : "oneDocumentWithWorkoutArray", hot : { $push : "$_id" } } }
)
This will yield a document of the following form, with the workout ids listed by popularity:
{
"_id" : "oneDocumentWithWorkoutArray",
"hot" : [
"workout6",
"workout1",
"workout5",
"workout4",
"workout3",
"workout2"
]
}
Related
I want to perform an aggregation query that does basic pagination:
Find all orders that belongs to a certain company_id
Sort the orders by order_number
Count the total number of documents
Skips to e.g. document number 100 and passes on the rest
Limits the number of documents to e.g. 2 and passes them on
Finishes by returning the count and a selected few fields from the documents
Here is a breakdown of the query:
db.Order.collection.aggregate([
This finds all matching documents:
{ '$match' : { "company_id" : ObjectId("54c0...") } },
This sorts the documents:
{ '$sort' : { 'order_number' : -1 } },
This counts the documents and passes the unmodified documents, but I'm sure doing it wrong, because things turn weird from here:
{
'$group' : {
'_id' : null,
'count' : { '$sum' : 1 },
'entries' : { '$push' : "$$ROOT" }
}
},
This seems to skip some documents:
{ "$skip" : 100 },
This is supposed to limit the documents, but it does not:
{ "$limit" : 2 },
This does return the count, but it does not return the documents in an array, instead it returns arrays with each field:
{ '$project' : {
'count' : 1,
'entries' : {'_id' : "$entries._id", 'order_number' : "$entries.order_number"}
}
}
])
This is the result:
[
{ "_id" : null,
"count" : 300,
"entries" : [
{
"_id" : [ObjectId('5a5c...'), ObjectId('5a5c...')],
"order_number" : ["4346", "4345"]
},
{
"_id" : [ObjectId('5a5c...'), ObjectId('5a5c...')],
"order_number" : ["4346", "4345"]
},
...
]
}
]
Where do I get it wrong?
To calculate totals and return a subset, you need to apply grouping and skip/limit to the same dataset. For that you can utilise facets
For example to show 3rd page, 10 documents per page:
db.Order.aggregate([
{ '$match' : { "company_id" : ObjectId("54c0...") } },
{ '$sort' : { 'order_number' : -1 } },
{ '$facet' : {
metadata: [ { $count: "total" }, { $addFields: { page: NumberInt(3) } } ],
data: [ { $skip: 20 }, { $limit: 10 } ] // add projection here wish you re-shape the docs
} }
] )
It will return a single document with 2 fields:
{
"metadata" : [
{
"total" : 300,
"page" : 3
}
],
"data" : [
{
... original document ...
},
{
... another document ...
},
{
... etc up to 10 docs ...
}
]
}
Since mongoDB version 5.0 there is another option, that allows to avoid the disadvantage of $facet, the grouping of all returned document into a one big document. The main concern is that a document as a size limit of 16M. Using $setWindowFields allows to avoid this concern:
db.Order.aggregate([
{$match: {company_id: ObjectId("54c0...") } },
{$sort: {order_number: -1 } },
{$setWindowFields: {output: {totalCount: {$count: {}}}}}
{$skip: 20 },
{$limit: 10 }
])
Below is the sample document of a collection, say "CollectionA"
{
"_id" : ObjectId("5ec3f19225701c4f7ab11a5f"),
"workshop" : ObjectId("5ebd37a3d33055331eb4730f"),
"participant" : ObjectId("5ebd382dd33055331eb47310"),
"status" : "analyzed",
"createdBy" : ObjectId("5eb7aa24d33055331eb4728c"),
"updatedBy" : ObjectId("5eb7aa24d33055331eb4728c"),
"results" : [
{
"analyze_by" : {
"user_name" : "m",
"user_id" : "5eb7aa24d33055331eb4728c"
},
"category_list" : [
"Communication",
"Controlling",
"Leading",
"Organizing",
"Planning",
"Staffing"
],
"analyzed_date" : ISODate("2020-05-19T14:48:49.993Z"),
}
],
"summary" : [],
"isDeleted" : false,
"isActive" : true,
"updatedDate" : ISODate("2020-05-19T14:48:50.827Z"),
"createdDate" : ISODate("2020-05-19T14:47:46.374Z"),
"__v" : 0
}
I need to query all the documents to get the "results" array length and return a sum of all document's "results" length.
For example,
document 1 has "results" length - 5
document 2 has "results" length - 6
then output should be 11.
Can we write a query, instead of getting all, iterating and the adding the results length??
If I had understand clearly you would like to project the length of the result attribute.
So you should check the $size operator would work for you.
https://docs.mongodb.com/manual/reference/operator/aggregation/size/
You can use $group and $sum to calculate the total size of a field which contains the size of your results array. To create the field, You can use $size in $addFields to calculate the size of results in each document and put it the field. As below:
db.getCollection('your_collection').aggregate([
{
$addFields: {
result_length: { $size: "$results"}
}
},
{
$group: {
_id: '',
total_result_length: { $sum: '$result_length' }
}
}
])
You use an aggregation grouping query with $sum and $size aggregation operators to get the total sum of array elements size for all documents in the collection.
db.collection.aggregate( [
{
$group: {
_id: null,
total_count: { $sum: { $size: "$results" } }
}
}
] )
Aggregation using Mongoose's Model.aggregate():
SomeModel.aggregate([
{
$group: {
_id: null,
total_count: { $sum: { $size: "$results" } }
}
}
]).
then(function (result) {
console.log(result);
});
I have the following documents in my collection. Need to get all employees in stores using the aggregate function.
//Store 1
{
"_id" : ObjectId("5b0d3fa6b426ea12ec0f6e5a"),
"store_name": KFC
"employees":[
ObjectId("5b0d4c5ec47e6223a08af5fd"), //query id
ObjectId("5b3b0ea9074f944699f1bcfc"),
ObjectId("5b11558d0a50c067a91875e9"),
],.. },
//Store 2
{
"_id" : ObjectId("5b0d3fa6b426ea12ec0f6e5a"),
"store_name": McDonalds
"employees":[
ObjectId("5b0d4c5ec47e6223a08af5fd"),
ObjectId("5b3b0ea9074f944699f1bcfc"),
ObjectId("5b11558d0a50c067a91875e9"),
],.. },
//Store 3
{
"_id" : ObjectId("5b0d3fa6b426ea12ec0f6e5a"),
"store_name": Dominos
"employees":[
ObjectId("5b0d4c5ec47e6223a08af5fd"),
ObjectId("5b1623905bc92d76abfe0ab1"),
ObjectId("5b14e0b1fc1507569f830f7d")
],.. }
Using aggregate function
db.getCollection('stores').aggregate([
{
$match:{
"employees":{
$in:[ ObjectId("5b0d4c5ec47e6223a08af5fd")] //employee_id
}
}
},{
$unwind: "$employees"
},{
$group: {
"_id": null,
"emps": {
$addToSet: "$employees"
}
}
}
])
OUTPUT
{
"_id" : null,
"emps" : [
ObjectId("5b0d4c5ec47e6223a08af5fd"), // employee id
ObjectId("5b3b0ea9074f944699f1bcfc"),
ObjectId("5b11558d0a50c067a91875e9"),
ObjectId("5b1623905bc92d76abfe0ab1"),
ObjectId("5b14e0b1fc1507569f830f7d")
]
}
its fine. I need to get this result without the employee queried id. how i handle it.
Here, How can I remove the queried employee id & need to get result like this
{
"_id" : null,
"emps" : [
ObjectId("5b3b0ea9074f944699f1bcfc"),
ObjectId("5b11558d0a50c067a91875e9"),
ObjectId("5b1623905bc92d76abfe0ab1"),
ObjectId("5b14e0b1fc1507569f830f7d")
]
}
You can use $filter and repeat your $in condition insinde $not:
db.getCollection('stores').aggregate([
// your pipeline,
{
$addFields: {
emps: {
$filter: {
input: "$emps",
as: "emp",
cond: { $not: { $in: [ "$$emp", [ ObjectId("5b0d4c5ec47e6223a08af5fd")] ] } }
}
}
}
}
])
$addFields is used to replace existing emp field
This is how a documents looks like in my dataset:
{
username: 'stack',
attempts: { 1517761701: false, 1512341532: true }
}
{
username: 'overflow',
attempts: { 1217563721: false }
}
Now, I want to retrieve every document in my dataset where attempts contains more than óne key. So the query should return the document of user 'stack' but not of user 'overflow'. What query can I apply here?
try $objectToArray to convert object to array and count the number of keys if you are using mongo 3.6+
db.cols.aggregate(
[
{$addFields: {count : {$size : {$ifNull : [{$objectToArray : "$attempts"}, []]}}}},
{$match: {count : {$gt : 1}}},
{$project: {count : 0}}
]
)
Use $redact for a single pipeline:
db.collection.aggregate([
{
"$redact": {
"$cond": [
{
"$gt": [
{ "$size": {
"$objectToArray": {
"$ifNull": [
"$attempts",
{ }
]
}
} },
1
]
},
"$$KEEP",
"$$PRUNE"
]
}
}
])
Convert attempts to an array, $unwind and then use $sortByCount to do all the work..
db.collection_name.aggregate( [
{ $addFields : { array : { $objectToArray : "$attempts"} } },
{ $unwind : "$array" },
{ $sortByCount : "$username" },
{ $match : { count : { $gte : 2 } } }
])
Outputs:
{
"_id" : "stack",
"count" : 2
}
function findProfilesBySteamIds(ids, res) {
var match = {
$match: {
$and : [
{ steamid: { $in : ids } }
]
}
}
var sort = {
$sort: {
createdAt : -1
}
}
var group = {
$group: {
_id : "$steamid",
profile : { $first : "$$ROOT" },
personahistory : { $push : "$$ROOT" }
}
}
SteamUser
.aggregate([match, sort, group])
}
Okay so here is my issue. I have a collection of profiles with a unique identifier steamid. I am grouping by $steamid and that functionality is working as expected. I also want to add a new field (not in the Schema) called personahistory that has an array of objects that is DISTINCT based on another property of the document, called personaname.
I have tried to use $addToSet following the mongoose reference docs but so far can only use it to create an array of that property only:
$group: {
_id : "$steamid",
profile : { $first : "$$ROOT" },
personahistory : { $addToSet : "$personaname" }
}
This outputs:
{
"_id": "1234567890",
"profile": { ... },
"personahistory" : [
"personaname1",
"personaname2",
"personaname3"
]
}
Instead I'd like the output to be along the lines of:
{
"_id": "1234567890",
"profile": { ... },
"personahistory" : [
{
"_id": "1234567890",
"personaname": "personaname1",
...
},
{
"_id": "1234567891",
"personaname": "personaname2",
...
},
{
"_id": "1234567892",
"personaname": "personaname3",
...
}
]
}
I've tried something like:
{
$addToSet : {
"_id": "$steamid",
"personaname": "$personaname"
}
}
but to no avail.
Furthermore, if this behavior is even possible, I'd like to be able to use two or more DISTINCT fields. So, get all unique combinations of personaname and avatarurl and add those to the set.
Basically, I have a record each time the profile is queried but I only want to return records that are unique based on those two fields.
I'll be happy to provide more information if I haven't been clear enough.