Als je zoiets tijdens runtime moet berekenen, waarbij "gefilterde" inhoud uit de array de sorteervolgorde bepaalt, dan kun je het beste iets doen met .aggregate()
om een sorteerwaarde als volgt te hervormen en te bepalen:
db.collection.aggregate([
// Pre-filter the array elements
{ "$project": {
"tags": 1,
"score": {
"$setDifference": [
{ "$map": {
"input": "$tags",
"as": "tag",
"in": {
"$cond": [
{ "$eq": [ "$$el.id", "t1" ] },
"$$el.score",
false
]
}
}},
[false]
]
}
}},
// Unwind to denormalize
{ "$unwind": "$score" },
// Group back the "max" score
{ "$group": {
"_id": "$_id",
"tags": { "$first": "$tags" },
"score": { "$max": "$score" }
}},
// Sort descending by score
{ "$sort": { "score": -1 } }
])
Waarbij het eerste deel van de pijplijn wordt gebruikt om de array-inhoud "voor te filteren" (en ook om het originele veld te behouden) tot alleen die waarden van "score" waarbij de id gelijk is aan "t1". Dit wordt gedaan door $map
te verwerken
die een voorwaarde toepast op elk element via $cond
om te bepalen of de "score" voor dat element moet worden geretourneerd of false
.
De $setDifference
operatie maakt een vergelijking met een array met één element [false]
die effectief elke false
. verwijdert waarden geretourneerd uit de $map
. Als een "set" verwijdert dit ook dubbele vermeldingen, maar voor het sorteerdoel hier is dit een goede zaak.
Met de array verkleind en omgevormd tot waarden die u verwerkt $unwind
klaar voor de volgende fase om de waarden als afzonderlijke elementen te behandelen. De $group
stage is in wezen van toepassing $max
op de "score" om de hoogste waarde in de gefilterde resultaten te retourneren.
Dan is het gewoon een kwestie van het toepassen van de $sort
op de vastgestelde waarde de documenten te bestellen. Natuurlijk, als je dit andersom wilde, gebruik dan $min
en sorteer in plaats daarvan in oplopende volgorde.
Voeg natuurlijk een $match
toe
ga naar het begin als u alleen maar documenten wilt die daadwerkelijk "t1"-waarden bevatten voor id
binnen de tags. Maar dat deel is het minst relevant voor de sortering op gefilterde resultaten die u wilt bereiken.
Het alternatief voor berekenen is om het allemaal te doen terwijl u items naar de array in de documenten schrijft. Een beetje rommelig, maar het gaat ongeveer zo:
db.collection.update(
{ "_id": docId },
{
"$push": { "tags": { "id": "t1", "score": 60 } },
"$max": { "maxt1score": 60 },
"$min": { "mint1score": 60 }
}
)
Hier de $max
update-operator stelt de waarde voor het opgegeven veld alleen in als de nieuwe waarde groter is dan de bestaande waarde of als er anders nog geen eigenschap bestaat. Het omgekeerde geldt voor $min
, waarbij alleen indien minder dan deze wordt vervangen door de nieuwe waarde.
Dit zou natuurlijk tot gevolg hebben dat er verschillende extra eigenschappen aan de documenten worden toegevoegd, maar het eindresultaat is dat het sorteren sterk vereenvoudigd wordt:
db.collection.find().sort({ "maxt1score": -1 })
En het gaat een stuk sneller dan rekenen met een aggregatiepijplijn.
Denk dus aan de ontwerpprincipes. Gestructureerde gegevens in arrays waarin u gefilterde en gekoppelde resultaten wilt sorteren, betekent tijdens runtime berekenen op welke waarde moet worden gesorteerd. Extra eigenschappen toevoegen aan het document op .update()
betekent dat u eenvoudig naar die eigenschappen kunt verwijzen om de resultaten direct te sorteren.