Limiting documents pushed to array in group aggregation - mongodb

I have a collection comments. Each comment has an authorId.
I want to group the comments collection into 'threads' by the authorId, and attach the 5 most recent comments.
So far I have tried this:
db.comments.aggregate([{$group: { _id: "$authorId", recentComments: { $push: "$$ROOT"} }}])
But this attaches all comments. I then tried to add a limit like this:
db.comments.aggregate([{$group: { _id: "$authorId", recentComments: { $push: "$$ROOT"} }}, {$limit: 5}])
But this doesnt limit the number of documents, but instead the number of grouped documents.
Any ideas?

Adding a project worked for me...
db.comments.aggregate([
{$group: { _id: "$authorId", recentComments: { $push: "$$ROOT"} }},
{$project: {_id: 1, title: 1, recentComments: {$slice: ['$recentComments', 0, 5]}}}])

Related

Sort and assign the order to query in mongodb

I'd like to sort a collection, then add a virtual property to the result which is their numerical order in which the results where displayed as.
So for example, we have a collection called calls, and we'd like to ascertain the current call queue priority as a number so it can be synced to our CRM via reverse ETL.
We have to do this inside of the query itself because we don't have an intermediary step where we can introduce any logic to determine this logic.
So my current query is
db.getCollection('callqueues').aggregate([
{
$match: {
'invalidated': false,
'assigned_agent': null
}
},
{ $sort: {
score: -1, _id: -1
} },
{
$addFields: {
order: "<NEW ORDER PROPERTY HERE>",
}
},
])
So I was wondering how would I insert as a virtual property their order, where the first element after the sort should be 1, second 2, etc
One option (since mongoDB version 5.0) is to use $setWindowFields for this:
db.collection.aggregate([
{$match: {invalidated: false, assigned_agent: null}},
{$setWindowFields: {
sortBy: {score: -1, _id: -1},
output: {
order: {
$sum: 1,
window: {documents: ["unbounded", "current"]}
}
}
}}
])
See how it works on the playground example
EDIT: If your mongoDB version is earlier than 5.0, you can use a less efficient query, involving $group and $unwind:
db.collection.aggregate([
{$match: {invalidated: false, assigned_agent: null}},
{$sort: {score: -1, _id: -1}},
{$group: {_id: 0, data: {$push: "$$ROOT"}}},
{$unwind: {path: "$data", includeArrayIndex: "order"}},
{$replaceRoot: {newRoot: {$mergeObjects: ["$data", {order: {$add: ["$order", 1]}}]}}}
])
See how it works on the playground example < 5.0

Cannot sort arrays using unwind or reduce mongodb aggregation pipline

I am having a problem sorting an array in a mongodb aggregation pipeline. I couldn't find an answer in stackoverflow so am posting my problem
I have some data in mongodb which has this structure
{
testOne:{ scores:[1, 5, 8]},
testTwo:{scores:[3, 4, 5]},
testThree:{scores:[9,0,1]},
........
testFifty:{scores:[0,8,1]}
}
Basically a series of many tests and the scores from those tests (this is a simplified example to illustrate the problem).
After a few hours banging my head against a wall I found I could only unwind two arrays, any more and no data was returned.
So I couldn't unwind all my arrays and group to get arrays of each test scores from the db
db.collection.aggregate([
{
{$unwind: '$testOne.scores'},
{$unwind: '$testTwo.scores'},
{$unwind: '$testThree.scores'},
{$group:{
_id: {},
concatTestOne: { $push: "$testOne.scores"},
concatTestTwo: { $push: "$testTwo.scores"},
concatTestThree: { $push: "$testThree.scores"}
}
}
To my frustration , even if I just did one unwind the resulting array did not sort properly see below:
db.collection.aggregate([
{
{$unwind: '$testOne.scores'},
{$group:{
_id: {},
concatTestOne: { $push: "$testOne.scores"}
}
{$sort:{concatTestOne: 1}},
}
The result was [1,5,8,3,4,5,....,9,0,1]. No sorting
So to get all the tests scores I used reduce to flatten the nested arrays resulting from the grouping stage with no 'unwinding' e.g.:
db.collection.aggregate([
{
{$group:{
_id: {},
concatTestOne: { $push: "$testOne.scores"}
},
{$addFields:{
testOneScores: {$reduce: {
input: "$concatTestOne",
initialValue: [ ],
in: { $concatArrays: [ "$$value", "$$this" ] }
}
}
}
Once again the resulting arrays do not sort. Can anyone tell me what I am doing wrong. The arrays are large (length approx 3500) is it just mongoDB aggregation doesn't handle large arrays ?
Many thanks for any comments I have spent a lot of time trying to sort my arrays and noting works so far
In my understanding, this is your needed query, otherwise please update your sample output.
db.col.aggregate([
{$unwind: '$testOne.scores'},
{$unwind: '$testTwo.scores'},
{$unwind: '$testThree.scores'},
{"$sort" : {"testOne.scores" : -1,"testTwo.scores" : -1,"testThree.scores" : -1}},
{$group:{
_id: {},
concatTestOne: { $addToSet: "$testOne.scores"},
concatTestTwo: { $addToSet: "$testTwo.scores"},
concatTestThree: { $addToSet: "$testThree.scores"}
}
}
])

Distinct query in Mongo

I have a document (name, firstName, age).
This command gives me the different names of the document:
db.getCollection('persons').distinct("name")
How do I do to get the corresponding firstNames?
Thanks!
You could try an aggregation query which groups the name and firstName. Eventually you could also add a count to it to see which combinations are repeated (but that is not necessary).
Here is an example:
db.test1.aggregate(
[
{
$group: {_id: {name: "$name", firstName: "$firstName"}, count: {$sum: 1}}
}
]
)
Here is another another option to show an aggregated list:
db.test1.aggregate(
[
{
$group: {_id: {name: "$name"}, firstName: { $push: "$firstName" }}
}
]
)

MongoDB: How can I get a count of a field in a collection grouped by first character and matching a 2nd field?

Following this question's answer (https://stackoverflow.com/a/20817040/2656506) I was able to group a field based on it's first character with this command:
db.kits.aggregate({ $group: {_id: {$substr: ['$kit', 0, 1]}, count: {$sum: 1}}})
But I can't figure out how I can additionally group only those documents which match an additional condition like _id: 'abc' in the same query. Can it be done in one query?
Thanks in advance!
add $match pipeline stage to your aggregation query:
db.kits.aggregate(
[
{
$match: {
_id: 'abc'
}
},
{
$group: {
_id: {
$substr: ['$kit', 0, 1]
},
count: {$sum: 1}
}
}
]
)

Mongodb aggregate query with not in

I have a list of students in one collection and their grades in another collection. The schema (stripped of other details) look like
Students
{
_id: 1234,
student: {
code: "WUKD984KUK"
}
}
Grades
{
_id: 3456,
grade: 24,
studentCode: "WUKD984KUK"
}
There can be multiple grade entries for each student. I need a total count of students who are present in grades table but not in the student table. I also need a count of grades for each student who are not in the student table. The following is the query I had written,
var existingStudents = db.students.find({}, {_id: 0, 'student.code': 1});
db.grades.aggregate(
{$match: { 'studentCode': {'$nin': existingStudents}}},
{$group: {_id: '$studentCode', count:{$sum: 1}}},
{$project: {tmp: {code: '$_id', count: '$count'}}},
{$group: {_id: null, total:{$sum:1}, data:{$addToSet: '$tmp'}}}
);
But this returns me all of the student details as if the match is not working. When I run just the first part of this query, I get the student details as
{ "student" : { "code" : "A210225504104" } }
I feel that because the return value is two deep the match isnt working. What is the right way to get this?
Use this code
var existingStudents=[];
db.students.find({}, {_id: 0, 'student.code': 1}).forEach(function(doc){existingStudents.push(doc.student.code)})
db.grades.aggregate(
{$match: { 'studentCode': {'$nin': existingStudents}}},
{$group: {_id: '$studentCode', count:{$sum: 1}}},
{$project: {tmp: {code: '$_id', count: '$count'}}},
{$group: {_id: null, total:{$sum:1}, data:{$addToSet: '$tmp'}}}
);