I'm trying to sort documents based on a label
and a level
, with a limit.
Consider the following document structure
{
"_id" : ObjectId("660269c42c04edea870276d4"),
"skills" : [
{"label" : "painting", "level" : 10},
{"label" : "singing", "level" : 5}
]
}
I'm filtering the documents to only consider the ones with the painting skill. My query is
{"skills.label" : "painting", "_id" : {"$nin" : ARRAY_OF_PREVIOUSLY_RETURNED_IDS }}
The problem I'm having is that I want to return the results from top to bottom based on level
by batches of 50
, so I'm passing a limit. I'll be saving the _id
so I can skip the previously returned.
How can I do that ? Ideally my sort object would be
{"skills.label" : "painting", "skills.level" : -1}
but I cannot refilter inside the sorting object. How can I sort considering only the level
of the painting
skill ? The desired output is
[
{
"_id" : ObjectId("660269c42c04edea870276d4"),
"skills" : [
{"label" : "painting", "level" : 10},
{"label" : "singing", "level" : 5}
]
},
{
"_id" : ObjectId("660269c42c04edea870276d5"),
"skills" : [
{"label" : "painting", "level" : 9},
{"label" : "dancing", "level" : 15}
]
},
...
]
Create a temporary field which has the value or object which you will be filtering on. In this case, the skills
object where label = 'painting'
. Sort on that field and then unset.
In this case, I've created the temporary field by filtering the skills
array to only the 'painting' skill object and then using just the $first
one, so that it's just that single object. Then sorting on the nested .level
field.
db.collection.aggregate([
{
$set: {
sortField: {
$first: {
$filter: {
input: "$skills",
cond: { $eq: ["$$this.label", "painting"] }
}
}
}
}
},
{ $sort: { "sortField.level": -1 } },
{ $unset: "sortField" }
])
You could also use $reduce
to create that with just the level field.
Edit: Additionally, since this sort is deterministic, you can use $skip
and $limit
per usual instead of needing to keep excluding previously returned ids
. Otherwise, getting page 100 means first getting the results from pages 1-99 (99 queries) and then the 100th. (This can be optimised but is better to just avoid; and use skip & limit instead.)