Erstellen von MongoDB-Aggregationspipelines mit $match, $group und $lookup
Erfahren Sie, wie die Aggregationsstufen von MongoDB Dokumente filtern, gruppieren, umformen, verknüpfen und sortieren sowie wie man sie zu einem Pipeline zusammenfügt, um tatsächliche Berichterstattungsfragen zu beantworten.
Eine einfache find()-Abfrage reicht aus, um Dokumente zu holen, die einer Bedingung entsprechen, doch sie kann den Gesamtumsatz pro Kunde nicht ermitteln, Benutzerdaten zu Bestellungen hinzufügen oder einen gerankten Bericht liefern. Dafür bietet MongoDB das Aggregationsframework an. Diese Anleitung erläutert die fünf am häufigsten verwendeten Phasen, die kleinen Fallstricke in jeder Phase sowie wie man sie zu einem einzigen Pipeline kombiniert, der ein sauberes, sofort nutzbares Ergebnis liefert.
Wie ein Aggregationspipeline funktioniert
Eine Aggregation ist eine geordnete Liste von Phasen. Dokumente gelangen in die erste Phase, jede Phase wandelt den Datenstrom auf eine bestimmte Weise um, und das Ergebnis dient als Eingang für die nächste Phase. Wenn man sich das wie eine Produktionslinie vorstellt, ist die Reihenfolge der Phasen leicht nachvollziehbar.
Der untenstehende kurze Pipeline behält nur abgeschlossene Bestellungen bei, summiert den Betrag pro Kunde und listet die größten Ausgaber aus.
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $group: { _id: "$customerId", total: { $sum: "$amount" } } },
{ $sort: { total: -1 } }
]);
Die folgenden Abschnitte betrachten jede Phase für sich.
Dokumente mit $match filtern
$match akzeptiert dieselben Filterarten wie bei find(). In diesem Beispiel gelangen nur die Dokumente, deren status dem Wert "completed" entspricht, in die nächste Phase.
{
$match: {
status: "completed"
}
}
Platzieren Sie $match so früh wie möglich. Jedes zu Beginn entfernte Dokument bedeutet, dass die späteren Phasen um ein Dokument weniger verarbeiten müssen. Ein $match am Anfang eines Pipelines kann Indexe der Sammlung nutzen, während die späteren Phasen mit Zwischenergebnissen arbeiten, bei denen Indexe nicht helfen können.
Gruppierung und Berechnung von Gesamtwerten mit $group
$group sammelt Dokumente, die einen gemeinsamen Schlüssel teilen, und berechnet Werte für jede Gruppe. Das Beispiel erzeugt ein Dokument pro Kunde mit der zusammengefassten Bestellsumme.
{
$group: {
_id: "$customerId",
totalSales: {
$sum: "$amount"
}
}
}
Zwei Teile übernehmen die Arbeit:
_iddefiniert den Gruppierungsschlüssel; hier ist es der Wert descustomerId-Feldes jedes Dokuments (das Präfix$bedeutet „lesen Sie dieses Feld“).$sumist ein Akkumulator, der dieamount-Werte aller Dokumente in der Gruppe addiert.
Auch andere Akkumulatoren folgen demselben Muster, darunter $avg, $min, $max und $count. Beachten Sie, dass die Ausgabe von $group nur _id sowie die von Ihnen berechneten Felder enthält; alle anderen Felder der ursprünglichen Dokumente sind verschwunden.
Felder mit $project auswählen und umformen
$project bestimmt, welche Felder in der Ausgabe erscheinen, und kann neue erstellen. Wenn ein Feld auf 1 gesetzt wird, wird es mit eingebunden, während _id: 0 den Identifikator ausdrücklich versteckt, der sonst standardmäßig mit enthalten ist.
{
$project: {
customerId: 1,
totalSales: 1,
_id: 0
}
}
Es kann auch Werte berechnen. Hier erzeugt $multiply ein Feld totalWithTax, indem es einen Steuersatz von 18 % auf totalSales anwendet.
{
$project: {
customerId: 1,
totalWithTax: {
$multiply: ["$totalSales", 1.18]
}
}
}
Ein wichtiger Punkt, wenn diese Codeausschnitte nach einer $group-Phase folgen, ist, dass der Kundeneidentifikator dann in _id statt in customerId gespeichert ist; daher würde customerId: 1 nichts ausgeben. Der kombinierte Pipeline löst dieses Problem, indem er customerId: „$_id“ schreibt, wodurch das Feld umbenannt wird. Betrachten Sie $project als die Phase, die die endgültige Antwort bestimmt, die Ihre API zurückgibt.
Hinzufügen einer anderen Sammlung mit $lookup
Dokumente beziehen sich oft auf Daten, die an anderer Stelle gespeichert sind. Bei einer orders-Sammlung und einer users-Sammlung holt $lookup für jede Bestellung den entsprechenden Benutzer ab.
{
$lookup: {
from: "users",
localField: "customerId",
foreignField: "_id",
as: "customer"
}
}
Für jede Bestellung vergleicht MongoDB den customerId der Bestellung mit dem _id in der users-Sammlung und schreibt alle Treffer in ein neues Array-Feld mit dem Namen customer auf. Dies ist das nächstliegende Äquivalent zu einem SQL-Join. Das Ergebnis ist stets ein Array – selbst wenn genau ein Benutzer passt – weshalb es üblich ist, nach dem Lookup $unwind zu verwenden oder das erste Element auszulesen. Stellen Sie außerdem sicher, dass beide Felder denselben Typ haben: Ein String customerId passt nicht zu einem ObjectId in _id.
Sortieren der Ergebnisse mit $sort
$sort ordnet die Dokumente nach einem oder mehreren Feldern. Der Wert 1 führt eine aufsteigende Sortierung durch, während -1 eine absteigende Sortierung bewirkt; dadurch werden in dem Beispiel die Kunden von der höchsten totalSales-Wertung bis zur niedrigsten geordnet.
{
$sort: {
totalSales: -1
}
}
Verbindung der Schritte in einem Bericht
Der eigentliche Wert zeigt sich, wenn die Schritte miteinander verbunden werden. Die folgende Pipeline wandelt Rohbestellungen in eine sortierte Liste von Kunden sowie deren Gesamtumsätze um:
db.orders.aggregate([
{
$match: {
status: "completed"
}
},
{
$group: {
_id: "$customerId",
totalSales: {
$sum: "$amount"
}
}
},
{
$sort: {
totalSales: -1
}
},
{
$project: {
customerId: "$_id",
totalSales: 1,
_id: 0
}
}
]);
Schritt für Schritt erfolgt Folgendes:
- Mit
$matchwerden nur abgeschlossene Bestellungen behalten. - Mit
$groupwerden sie nach Kunde gruppiert. - Die Bestellbeträge jedes Kunden werden zu
totalSalessummiert. - Die Kunden werden nach diesem Gesamtbetrag, von höchst bis niedrigst, sortiert.
- Mit
$projectwird_idincustomerIdumbenannt und_idaus der Ausgabe entfernt.
Um Namen oder E-Mails einzubeziehen, können Sie einen $lookup gegen die Tabelle users hinzufügen. Wenn er nach $group und nach jedem $limit platziert wird, erfolgt die Verknüpfung einmal pro Kunde statt einmal pro Bestellung.
Wichtige Erkenntnisse
Eine kompakte Art, sich die Kernschritte zu merken:
$matchfiltert die Daten.$groupgruppiert und berechnet.$projectwählt aus und formt um.$lookupverknüpft Sammlungen.$sortordnet die Ergebnisse.
Zusammen umfassen sie den größten Teil dessen, was Berichte, Dashboards, Analyse-Endpunkte und Geschäftsregeln benötigen, sobald man über die grundlegenden CRUD-Funktionen hinausgeht. Die Reihenfolge ist wichtig: Filtern Sie früh, gruppieren Sie einmal, verknüpfen Sie so spät wie möglich, solange die Daten es zulassen, und gestalten Sie die Ausgabe erst zum Schluss. In Interviews und Design-Überprüfungen ist es überzeugender, erklären zu können, warum eine bestimmte Phase an dieser Stelle steht, als einfach aufzuzählen, was jede einzelne tut.
Verwandte Artikel
- Vermeidung verlorener Aktualisierungen in Node.js und MongoDB bei gleichzeitigen Schreibvorgängen — Erfahren Sie, wie atomare bedingte Aktualisierungen, versionsbasiertes optimistisches Sperren, 409-Antworten und Transaktionen verhindern, dass gleichzeitige Schreibvorgänge in MongoDB Daten unbemerkt löschen.