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

begrenzen und sortieren Sie jede Gruppe in MongoDB mithilfe von Aggregation

Am besten führen Sie hier separate Abfragen für jedes "Land" (idealerweise parallel) durch und geben die kombinierten Ergebnisse zurück. Die Abfragen sind recht einfach und geben nur die zwei besten Werte zurück, nachdem eine Sortierung auf den Bewertungswert angewendet wurde, und werden ziemlich schnell ausgeführt, selbst wenn Sie mehrere Abfragen durchführen müssen, um das vollständige Ergebnis zu erhalten.

Das Aggregations-Framework ist dafür jetzt und auch in naher Zukunft nicht geeignet. Das Problem ist, dass es keinen solchen Operator gibt, der das Ergebnis einer Gruppierung in irgendeiner Weise "einschränkt". Um dies zu tun, müssen Sie also im Grunde $push alle Inhalte in ein Array und extrahieren Sie daraus die "top n"-Werte.

Die dafür erforderlichen aktuellen Vorgänge sind ziemlich schrecklich, und das Kernproblem besteht darin, dass die Ergebnisse bei den meisten echten Datenquellen wahrscheinlich die BSON-Grenze von 16 MB pro Dokument überschreiten.

Außerdem gibt es ein n Komplexität aufgrund dessen, wie Sie es jetzt tun müssten. Aber nur zur Demonstration mit 2 Elementen:

db.collection.aggregate([
    // Sort content by country and rating
    { "$sort": { "Country": 1, "rating": -1 } },

    // Group by country and push all items, keeping first result
    { "$group": {
        "_id": "$Country",
        "results": {
            "$push": {
                "name": "$name", 
                "rating": "$rating",
                "id": "$id"
            }
        },
        "first": { 
            "$first": {
                "name": "$name", 
                "rating": "$rating",
                "id": "$id"
            }
        }
    }},

    // Unwind the array
    { "$unwind": "results" },

    // Remove the seen result from the array
    { "$redact": {
        "$cond": {
            "if": { "$eq": [ "$results.id", "$first.id" ] },
            "then": "$$PRUNE",
            "else": "$$KEEP"
        }
    }},

    // Group to return the second result which is now first on stack
    { "$group": {
        "_id": "$_id",
        "first": { "$first": "$first" },
        "second": { 
            "$first": {
                "name": "$results.name", 
                "rating": "$results.rating",
                "id": "$results.id"
            }
        }
    }},

    // Optionally put these in an array format
    { "$project": {
        "results": { 
            "$map": {
                "input": ["A","B"],
                "as": "el",
                "in": {
                    "$cond": {
                        "if": { "$eq": [ "$$el", "A" ] },
                        "then": "$first",
                        "else": "$second"
                    }
                }
            }
        }
    }}
])

Das bringt das Ergebnis, aber es ist kein großartiger Ansatz und wird viel komplexer mit Iterationen für höhere Grenzen oder sogar wo Gruppierungen möglicherweise weniger als n haben Ergebnisse, die in einigen Fällen zurückgegeben werden.

Die aktuelle Entwicklungsserie ( 3.1.x ) zum Zeitpunkt des Schreibens hat einen $slice -Operator, der dies etwas einfacher macht, aber immer noch die gleiche "Größen"-Fallgrube hat:

db.collection.aggregate([
    // Sort content by country and rating
    { "$sort": { "Country": 1, "rating": -1 } },

    // Group by country and push all items, keeping first result
    { "$group": {
        "_id": "$Country",
        "results": {
            "$push": {
                "name": "$name", 
                "rating": "$rating",
                "id": "$id"
            }
        }
    }},
    { "$project": {
        "results": { "$slice": [ "$results", 2 ] }
    }}
])

Aber im Grunde, bis das Aggregations-Framework eine Möglichkeit hat, die Anzahl der von $push erzeugten Elemente zu "begrenzen". oder einen ähnlichen Gruppierungs-"Limit"-Operator, dann ist das Aggregations-Framework nicht wirklich die optimale Lösung für diese Art von Problem.

Einfache Abfragen wie diese:

db.collection.find({ "Country": "USA" }).sort({ "rating": -1 }).limit(1)

Laufen Sie für jedes einzelne Land und idealerweise in paralleler Verarbeitung durch Event-Loop-of-Thread mit einem kombinierten Ergebnis, um den derzeit optimalsten Ansatz zu erzielen. Sie holen nur das, was benötigt wird, was das große Problem ist, das das Aggregations-Framework bei einer solchen Gruppierung noch nicht bewältigen kann.

Suchen Sie also stattdessen nach Unterstützung, um diese "kombinierten Abfrageergebnisse" auf die optimalste Weise für Ihre gewählte Sprache zu erstellen, da dies weitaus weniger komplex und viel leistungsfähiger ist, als dies auf das Aggregations-Framework zu werfen.