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

Gruppieren und zählen Sie mithilfe des Aggregationsframeworks

Es scheint, als hätten Sie damit angefangen, aber Sie haben sich in einigen anderen Konzepten verlaufen. Es gibt einige grundlegende Wahrheiten bei der Arbeit mit Arrays in Dokumenten, aber fangen wir dort an, wo Sie aufgehört haben:

db.sample.aggregate([
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 }
    }}
])

Das wird also nur die $group verwenden Pipeline, um Ihre Dokumente zu den verschiedenen Werten des "Status"-Felds zu sammeln und dann auch ein weiteres Feld für "count" zu erzeugen, das natürlich die Vorkommen des Gruppierungsschlüssels "zählt", indem es einen Wert von 1 zu $sum Operator für jedes gefundene Dokument. Dies bringt Sie an einen Punkt, der dem entspricht, den Sie beschreiben:

{ "_id" : "done", "count" : 2 }
{ "_id" : "canceled", "count" : 1 }

Das ist die erste Stufe davon und leicht zu verstehen, aber jetzt müssen Sie wissen, wie Sie Werte aus einem Array erhalten. Sie könnten dann versucht sein, sobald Sie die "Punktnotation" Konzept richtig, um so etwas zu tun:

db.sample.aggregate([
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 },
        "total": { "$sum": "$devices.cost" }
    }}
])

Aber Sie werden feststellen, dass die "Gesamtsumme" tatsächlich 0 ist für jedes dieser Ergebnisse:

{ "_id" : "done", "count" : 2, "total" : 0 }
{ "_id" : "canceled", "count" : 1, "total" : 0 }

Wieso den? Nun, MongoDB-Aggregationsoperationen wie diese durchlaufen beim Gruppieren nicht wirklich Array-Elemente. Dazu verfügt das Aggregations-Framework über ein Konzept namens $unwind . Der Name ist relativ selbsterklärend. Ein eingebettetes Array in MongoDB ist ähnlich wie eine „Eins-zu-Viele“-Verknüpfung zwischen verknüpften Datenquellen. Was also $unwind tut, ist genau diese Art von "Join"-Ergebnis, bei dem die resultierenden "Dokumente" auf dem Inhalt des Arrays und duplizierten Informationen für jeden Elternteil basieren.

Um also auf Array-Elemente einzuwirken, müssen Sie $unwind Erste. Dies sollte Sie logischerweise zu folgendem Code führen:

db.sample.aggregate([
    { "$unwind": "$devices" },
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 },
        "total": { "$sum": "$devices.cost" }
    }}
])

Und dann das Ergebnis:

{ "_id" : "done", "count" : 4, "total" : 700 }
{ "_id" : "canceled", "count" : 2, "total" : 350 }

Aber das ist nicht ganz richtig, oder? Denken Sie daran, was Sie gerade von $unwind gelernt haben und wie wird eine denormalisierte Verbindung mit den übergeordneten Informationen hergestellt? Das wird jetzt also für jedes Dokument dupliziert, da beide zwei Array-Mitglieder hatten. Während also das "Gesamt"-Feld korrekt ist, ist die "Anzahl" doppelt so hoch, wie es in jedem Fall sein sollte.

Es muss etwas mehr Sorgfalt walten, also anstatt dies in einem einzigen $gruppe Schritt, es wird in zwei Schritten durchgeführt:

db.sample.aggregate([
    { "$unwind": "$devices" },
    { "$group": {
        "_id": "$_id",
        "status": { "$first": "$status" },
        "total": { "$sum": "$devices.cost" }
    }},
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 },
        "total": { "$sum": "$total" }
    }}
])

Was jetzt das Ergebnis mit korrekten Summen enthält:

{ "_id" : "canceled", "count" : 1, "total" : 350 }
{ "_id" : "done", "count" : 2, "total" : 700 }

Jetzt stimmen die Zahlen, aber es ist immer noch nicht genau das, wonach Sie fragen. Ich denke, Sie sollten hier aufhören, da die Art von Ergebnis, die Sie erwarten, wirklich nicht nur für ein einzelnes Ergebnis aus der Aggregation allein geeignet ist. Sie suchen nach der Summe, die sich "innerhalb" des Ergebnisses befindet. Es gehört wirklich nicht dorthin, aber bei kleinen Daten ist es in Ordnung:

db.sample.aggregate([
    { "$unwind": "$devices" },
    { "$group": {
        "_id": "$_id",
        "status": { "$first": "$status" },
        "total": { "$sum": "$devices.cost" }
    }},
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 },
        "total": { "$sum": "$total" }
    }},
    { "$group": {
        "_id": null,
        "data": { "$push": { "count": "$count", "total": "$total" } },
        "totalCost": { "$sum": "$total" }
    }}
])

Und ein endgültiges Ergebnisformular:

{
    "_id" : null,
    "data" : [
            {
                    "count" : 1,
                    "total" : 350
            },
            {
                    "count" : 2,
                    "total" : 700
            }
    ],
    "totalCost" : 1050
}

Aber "Tu das nicht" . MongoDB hat ein Dokumentenlimit für die Antwort von 16 MB, was eine Einschränkung der BSON-Spezifikation ist. Bei kleinen Ergebnissen können Sie diese Art von Convenience Wrapping durchführen, aber im größeren Schema der Dinge möchten Sie die Ergebnisse in der früheren Form und entweder eine separate Abfrage oder leben Sie mit der Iteration der gesamten Ergebnisse, um die Summe aller Dokumente zu erhalten.

Sie scheinen eine MongoDB-Version kleiner als 2.6 zu verwenden oder Ausgaben von einer RoboMongo-Shell zu kopieren, die die Funktionen der neuesten Version nicht unterstützt. Ab MongoDB 2.6 können die Ergebnisse der Aggregation jedoch eher ein „Cursor“ als ein einzelnes BSON-Array sein. Die Gesamtantwort kann also viel größer als 16 MB sein, aber nur, wenn Sie die Ergebnisse nicht zu einem einzigen Dokument komprimieren, wie im letzten Beispiel gezeigt.

Dies gilt insbesondere in Fällen, in denen Sie die Ergebnisse mit 100 bis 1000 Ergebniszeilen „auslagern“, aber nur eine „Gesamtzahl“ in einer API-Antwort zurückgeben möchten, wenn Sie nur eine „Seite“ mit 25 Ergebnissen zurückgeben eine Zeit.

Wie auch immer, das sollte Ihnen eine vernünftige Anleitung geben, wie Sie die Art von Ergebnissen erhalten, die Sie von Ihrem allgemeinen Dokumentenformular erwarten. Denken Sie an $unwind um Arrays zu verarbeiten, und allgemein $group mehrmals, um Summen auf verschiedenen Gruppierungsebenen aus Ihren Dokument- und Sammlungsgruppierungen zu erhalten.