MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Sortieren nach eingebettetem Objektwert in Mongodb

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.