currently I have a query:
const result = await getInstances();
that provides me an array of document:
[{name: "first", age: 13},
{name: "second", age: 21},
{name: "third", age: 11},
{name: "fourth", age: 14}
...]
The query goes something like this:
...
return Instances.aggregate
.match({//condition})
.skip(skipValue).limit(pageSize) // pagination done here
I want a query that appends a count for the total no. of documents before the pagination, but returns the paginated data, e.g:
...
return Instances.aggregate
.match({//condition}) ## I WANT THE COUNT OF THIS STEP TO BE APPENDED
.<SOME_PIPELINE_HERE>
.skip(skipValue).limit(pageSize) // pagination done here
would return something like:
{
data: [{name: "first", age: 12}....<ALL_PAGINATED_DATA>],
totalCount: 54 #count of data before pagination
}
What I tried and didn't work:
Instances.aggregate()
.match({//CONDITION})
.addFields({count: {$size: _id}})
.skip(value).limit(value)
It seems it goes through and calculates this for each document instead of the whole
One option is to use $facet in order to "fork" the query in the middle, so the same data can be used on different pipelines. For example:
db.collection.aggregate([
{$match: {a: {$in: [7, 8, 9]}}},
{
$facet: {
total: [{$group: {_id: null, count: {$sum: 1}}}],
data: [{$skip: 1}]
}
},
{$project: {data: 1, total: {$first: "$total.count"}}}
])
See how it works on the playground example
Related
Data structure
{
users: [
{id: "aaa"},
{id: "bbb"},
{id: "ccc"}
]
},
{
users: [
{id: "111"},
{id: "222"},
{id: "333"}
]
},
array: ["111", "222", "333"]
I want to get the document where every "id" matches my array, like $in does. But I don't want matches where only two of three matches. So in this case, the query should return the second document.
One way:
Query an array for an element
const cursor = db.collection('inventory').find({
users: [{id: "111"},{id: "222"},{id: "333"}]
});
https://docs.mongodb.com/manual/tutorial/query-arrays/#query-an-array-for-an-element
Related Q:
MongoDB Find Exact Array Match but order doesn't matter
const userIds = ["abc", "123", ...]
I found this query to solve my problem.
{
$and: [
{users: {$elemMatch: {id: {$in: userIds}}}},
{users: {$size: userIds.length}}
]
}
My data looks something like this:
{_id: ObjectId("5e10c2d61a9201e439335816"), name: "Bob", redWins: 23, blueWins: 34}
{_id: ObjectId("5e10c34e1a9201e439335818"), name: "Alice", redWins: 41, blueWins: 52}
{_id: ObjectId("5e10c36f1a9201e439335819"), name: "John", redWins: 12, blueWins: 24}
The goal is to be able to sort the data from most to least total wins (redWins + blueWins) and have a result that has the name and the amount of total wins in it. Desired output:
{name: "Alice", totalWins: 93},
{name: "Bob", totalWins: 57},
{name: "John", totalWins: 36}
One of the things I tried to use aggregation but I can't seem to figure out how to add the numbers before sorting them.
Thanks!
Use $addFields with $sum.
db.collection.aggregate([
{
$addFields: {
totalWins: {$sum: ["$redWins", "$blueWins"]}
}
},
{
$sort: {
totalWins: -1
}
},
{
$project: {
name: 1,
totalWins: 1,
_id: 0
}
}
])
I'm trying to get (filter) all the objects from a document array that match the same key.
Document Schema example:
{
...
country: "ES",
aut_comms:
[
...
{name: "Aragon", province: "Huesca"},
{name: "Aragon", province: "Teruel"},
{name: "Aragon", province: "Zaragoza"},
{name: "Madrid", province: "Madrid"}
...
]
}
If it is possible, im tying to retrieve from the query only the values from the objects that match the same key. Resulting in an array composed like : ["Huesca", "Teruel", "Zaragoza"]
An Array of objects that match the filter will also do the trick:
[
{name: "Aragon", province: "Huesca"},
{name: "Aragon", province: "Teruel"},
{name: "Aragon", province: "Zaragoza"}
]
Thanx
You will be able to get this array by first unwinding the array and then manipulating it
db.demo.aggregate([
{
$unwind:"$aut_comms"
},
{
$match:{"aut_comms.name":"Aragon"}
},
{
$group:{
_id:null,
result: {$push:"$aut_comms.province"}
}
}
])
Edit
It is indeed possible to do such query and output in your expected format. You can do either $unwind or $match first. Personally, I prefer to do a $match first as it would limit the number of (unnecessary) documents generated by the $unwind operation.
db.getCollection('col').aggregate([
{$match: {"aut_comms.name": "Aragon"}},
{$project: {_id: 0, "aut_comms": 1}},
{$unwind: "$aut_comms"},
{$match: {"aut_comms.name": "Aragon"}},
{$project: {"province": "$aut_comms.province"}},
{$group: {
_id: null,
province: {$push: "$province"}
}}
])
The output will be:
/* 1 */
{
"_id" : null,
"province" : [
"Huesca",
"Teruel",
"Zaragoza"
]
}
I have a table Thread:
{
userId: String
messageId: String
}
Now I have an array of userIds, I need to query 20 messageIds for each of them, I can do it with a loop:
const messageIds = {}
for (const userId of userIds) {
const results = await Thread.find({ userId }).sort({ _id: -1 }).limit(20).exec()
messageIds[userId] = results.map(result => result.messageId)
}
But of course this doesn't perform well. Is there a better solution?
The problem with your approach is that you are issuing multiple separate queries to MongoDB.
The simplest workaround to this is using the $push and $slice approach. But this has the problem that the intermediate step would creating an array of huge size.
Another way could be to use $facet as part of aggregation query.
So you need a $facet step in the aggregation like -
[
{$facet: {
'userId1': [
{$match: {userId: 'userId1'} },
{$limit: 20},
{$group: {_id: '', msg: {$push: '$messageId'} } }
],
'userId2': [
{$match: {userId: 'userId2'} },
{$limit: 20},
{$group: {_id: '', msg: {$push: '$messageId'} } }
],
.... (for each userId in array)
}}
]
You can easily just generate this query by iterating over the list of users and adding keys for each user.
So you end up with an object where key is the userId and the value is the array of messages (obj[userId].msg)
You can use aggregation to group threads by userId, and return the top 20:
db.threads.aggregate([
{$match: {userId:{$in: userIds}}},
{$sort: {_id: -1}},
{$group: {_id: "$userId", threads: {$push: "$$ROOT"}}},
{$project: {_id:0, userId:"$_id", threads: {$slice:["$threads", 20]}}}
])
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'}}}
);