Wenn Sie so etwas zur Laufzeit ausrechnen müssen, wobei "gefilterter" Inhalt aus dem Array die Sortierreihenfolge bestimmt, dann machen Sie am besten etwas mit .aggregate()
um einen Sortierwert wie folgt umzugestalten und zu bestimmen:
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 } }
])
Wobei der erste Teil der Pipeline zum "Vorfiltern" des Array-Inhalts (sowie zum Beibehalten des ursprünglichen Felds) auf nur die Werte von "score" verwendet wird, bei denen die ID gleich "t1" ist. Dies erfolgt durch Verarbeitung von $map
die eine Bedingung auf jedes Element über $cond
um zu bestimmen, ob die "Punktzahl" für dieses Element oder false
zurückgegeben werden soll .
Der $setDifference
Die Operation führt einen Vergleich mit einem Einzelelement-Array [false]
durch was effektiv jeden false
entfernt Werte, die von $map
zurückgegeben werden . Als "Set" entfernt dies auch doppelte Einträge, aber für den Sortierzweck hier ist das eine gute Sache.
Wenn das Array reduziert und auf Werte umgeformt ist, verarbeiten Sie $unwind
bereit für die nächste Stufe, um die Werte als einzelne Elemente zu behandeln. Der $group
Stufe gilt im Wesentlichen $max
auf die "Punktzahl", um den höchsten Wert zurückzugeben, der in den gefilterten Ergebnissen enthalten ist.
Dann muss nur noch der $sort
auf den ermittelten Wert, um die Dokumente zu bestellen. Wenn Sie das umgekehrt wollten, dann verwenden Sie natürlich $min
und sortiere stattdessen in aufsteigender Reihenfolge.
Fügen Sie natürlich ein $match
hinzu
Schritt an den Anfang, wenn Sie wirklich nur Dokumente wollen, die tatsächlich "t1"-Werte für id
enthalten innerhalb der Tags. Aber dieser Teil ist für die Sortierung nach gefilterten Ergebnissen, die Sie erreichen möchten, am wenigsten relevant.
Die Alternative zur Berechnung besteht darin, alles zu tun, während Sie Einträge in das Array in den Dokumenten schreiben. Etwas chaotisch, aber es geht ungefähr so:
db.collection.update(
{ "_id": docId },
{
"$push": { "tags": { "id": "t1", "score": 60 } },
"$max": { "maxt1score": 60 },
"$min": { "mint1score": 60 }
}
)
Hier der $max
Der Aktualisierungsoperator setzt den Wert für das angegebene Feld nur, wenn der neue Wert größer als der vorhandene Wert ist oder andernfalls noch keine Eigenschaft vorhanden ist. Der umgekehrte Fall gilt für $min
, wobei nur kleiner als durch den neuen Wert ersetzt wird.
Dies hätte natürlich den Effekt, dass den Dokumenten verschiedene zusätzliche Eigenschaften hinzugefügt würden, aber das Endergebnis ist das Sortieren stark vereinfacht:
db.collection.find().sort({ "maxt1score": -1 })
Und es läuft viel schneller als die Berechnung mit einer Aggregationspipeline.
Berücksichtigen Sie also die Designprinzipien. Strukturierte Daten in Arrays, bei denen Sie gefilterte und gepaarte Ergebnisse zum Sortieren wünschen, bedeutet, dass zur Laufzeit berechnet wird, nach welchem Wert sortiert werden soll. Hinzufügen zusätzlicher Eigenschaften zum Dokument auf .update()
bedeutet, dass Sie einfach auf diese Eigenschaften verweisen können, um die Ergebnisse direkt zu sortieren.