Om du behöver räkna ut något sånt här vid körning, med "filtrerat" innehåll från arrayen som bestämmer sorteringsordningen, gör du bäst med .aggregate()
för att omforma och bestämma ett sorteringsvärde så här:
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 } }
])
Där den första delen av pipelinen används för att "förfiltrera" arrayinnehållet (liksom att behålla det ursprungliga fältet) till bara de värden för "score" där id är lika med "t1". Detta görs genom att bearbeta $map
som tillämpar ett villkor för varje element via $cond
för att avgöra om "poängen" för det elementet ska returneras eller false
.
$setDifference
operation gör en jämförelse med en enstaka elementarray [false]
som effektivt tar bort alla false
värden som returneras från $map
. Som en "uppsättning" tar detta också bort dubbletter av poster, men för sorteringsändamålet här är det bra.
Med arrayen reducerad och omformad till värden bearbetar du $unwind
redo för nästa steg för att hantera värdena som individuella element. $group
stadiet gäller i huvudsak $max
på "poängen" för att returnera det högsta värdet som finns i de filtrerade resultaten.
Sedan är det bara att använda $sort
på det fastställda värdet för att beställa dokumenten. Naturligtvis om du ville ha det tvärtom, använd $min
och sortera i stigande ordning istället.
Naturligtvis lägga till en $match
steg till början om allt du verkligen vill ha är dokument som faktiskt innehåller "t1"-värden för id
inom taggarna. Men den delen är minst relevant för sorteringen på filtrerade resultat du vill uppnå.
Alternativet till att beräkna är att göra allt medan du skriver poster till arrayen i dokumenten. Lite rörigt, men det går ungefär så här:
db.collection.update(
{ "_id": docId },
{
"$push": { "tags": { "id": "t1", "score": 60 } },
"$max": { "maxt1score": 60 },
"$min": { "mint1score": 60 }
}
)
Här är $max
uppdateringsoperatören ställer bara in värdet för det angivna fältet om det nya värdet är större än det befintliga värdet eller annars ingen egenskap ännu existerar. Det omvända fallet är sant för $min
, där endast om mindre än det kommer att ersättas med det nya värdet.
Detta skulle naturligtvis ha effekten av att lägga till olika ytterligare egenskaper till dokumenten, men slutresultatet är att sorteringen förenklas avsevärt:
db.collection.find().sort({ "maxt1score": -1 })
Och det kommer att gå mycket snabbare än att beräkna med en aggregeringspipeline.
Så överväg designprinciperna. Strukturerad data i arrayer där du vill ha filtrerade och parade resultat för sortering innebär att man beräknar vid körning för att bestämma vilket värde som ska sorteras på. Lägga till ytterligare egenskaper till dokumentet på .update()
innebär att du helt enkelt kan referera till dessa egenskaper för att direkt sortera resultat.