Je nach verfügbarer Version gibt es unterschiedliche Ansätze, aber im Wesentlichen gliedern sie sich darin auf, Ihre Dokumentfelder in separate Dokumente in einem „Array“ umzuwandeln und dieses Array dann mit $unwind
und sukzessive $group
ausführen
Phasen, um die Ausgangssummen und Arrays zu akkumulieren.
MongoDB 3.4.4 und höher
Neueste Versionen haben spezielle Operatoren wie $arrayToObject
und $objectToArray
was die Übertragung zum anfänglichen "Array" aus dem Quelldokument dynamischer machen kann als in früheren Versionen:
db.profile.aggregate([
{ "$project": {
"_id": 0,
"data": {
"$filter": {
"input": { "$objectToArray": "$$ROOT" },
"cond": { "$in": [ "$$this.k", ["gender","caste","education"] ] }
}
}
}},
{ "$unwind": "$data" },
{ "$group": {
"_id": "$data",
"total": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.k",
"v": {
"$push": { "name": "$_id.v", "total": "$total" }
}
}},
{ "$group": {
"_id": null,
"data": { "$push": { "k": "$_id", "v": "$v" } }
}},
{ "$replaceRoot": {
"newRoot": {
"$arrayToObject": "$data"
}
}}
])
Verwenden Sie also $objectToArray
Sie machen das ursprüngliche Dokument zu einem Array seiner Schlüssel und Werte als "k"
und "v"
Schlüssel in das resultierende Array von Objekten. Wir wenden $filter
an
hier um per "Taste" auszuwählen. Hier mit $in
mit einer Liste von Schlüsseln, die wir wollen, aber dies könnte dynamischer als eine Liste von Schlüsseln verwendet werden, die "ausgeschlossen" werden sollen, wo dies kürzer war. Es werden nur logische Operatoren verwendet, um die Bedingung auszuwerten.
Die Endphase hier verwendet $replaceRoot
und da all unsere Manipulationen und "Gruppierungen" dazwischen immer noch dieses "k"
behalten und "v"
Form verwenden wir dann $arrayToObject
hier, um unser "Array von Objekten" im Ergebnis zu den "Schlüsseln" des Dokuments der obersten Ebene in der Ausgabe zu machen.
MongoDB 3.6 $mergeObjects
Als zusätzliches Problem enthält MongoDB 3.6 hier $mergeObjects
die als "Akkumulator verwendet werden kann "
in einem $group
Pipeline-Stufe und ersetzt damit den $push
und Erstellen des letzten $replaceRoot
einfach die "data"
verschieben Schlüssel stattdessen zum "Stamm" des zurückgegebenen Dokuments:
db.profile.aggregate([
{ "$project": {
"_id": 0,
"data": {
"$filter": {
"input": { "$objectToArray": "$$ROOT" },
"cond": { "$in": [ "$$this.k", ["gender","caste","education"] ] }
}
}
}},
{ "$unwind": "$data" },
{ "$group": { "_id": "$data", "total": { "$sum": 1 } }},
{ "$group": {
"_id": "$_id.k",
"v": {
"$push": { "name": "$_id.v", "total": "$total" }
}
}},
{ "$group": {
"_id": null,
"data": {
"$mergeObjects": {
"$arrayToObject": [
[{ "k": "$_id", "v": "$v" }]
]
}
}
}},
{ "$replaceRoot": { "newRoot": "$data" } }
])
Dies unterscheidet sich nicht wirklich von dem, was insgesamt demonstriert wird, sondern zeigt einfach, wie $mergeObjects
kann auf diese Weise verwendet werden und kann in Fällen nützlich sein, in denen der Gruppierungsschlüssel ein anderer war und wir diese endgültige "Verschmelzung" mit dem Stammbereich des Objekts nicht wollten.
Beachten Sie, dass $arrayToObject
wird immer noch benötigt, um den "Wert" wieder in den Namen des "Schlüssels" umzuwandeln, aber wir tun dies nur während der Akkumulation und nicht nach der Gruppierung, da die neue Akkumulation das "Zusammenführen" von Schlüsseln ermöglicht.
MongoDB 3.2
Wenn wir eine Version zurücknehmen oder wenn Sie eine MongoDB 3.4.x haben, die kleiner als die Version 3.4.4 ist, können wir immer noch viel davon verwenden, aber stattdessen behandeln wir die Erstellung des Arrays auch auf statischere Weise da die endgültige "Transformation" bei der Ausgabe aufgrund der Aggregationsoperatoren, die wir nicht haben, anders behandelt wird:
db.profile.aggregate([
{ "$project": {
"data": [
{ "k": "gender", "v": "$gender" },
{ "k": "caste", "v": "$caste" },
{ "k": "education", "v": "$education" }
]
}},
{ "$unwind": "$data" },
{ "$group": {
"_id": "$data",
"total": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.k",
"v": {
"$push": { "name": "$_id.v", "total": "$total" }
}
}},
{ "$group": {
"_id": null,
"data": { "$push": { "k": "$_id", "v": "$v" } }
}},
/*
{ "$replaceRoot": {
"newRoot": {
"$arrayToObject": "$data"
}
}}
*/
]).map( d =>
d.data.map( e => ({ [e.k]: e.v }) )
.reduce((acc,curr) => Object.assign(acc,curr),{})
)
Das ist genau dasselbe, außer dass wir anstelle einer dynamischen Transformation des Dokuments in das Array tatsächlich jedem Array-Mitglied "explizit" denselben "k"
zuweisen und "v"
Notation. Ich behalte diese Schlüsselnamen an dieser Stelle wirklich nur für Konventionen bei, da keiner der Aggregationsoperatoren hier überhaupt davon abhängig ist.
Auch anstelle von $replaceRoot
, machen wir genau das Gleiche wie die vorherige Implementierung der Pipeline-Phase dort, aber stattdessen im Client-Code. Alle MongoDB-Treiber haben eine Implementierung von cursor.map()
um "Cursor-Transformationen" zu aktivieren. Hier verwenden wir mit der Shell die grundlegenden JavaScript-Funktionen von Array.map()
und Array.reduce()
um diese Ausgabe zu nehmen und den Inhalt des Arrays wieder zu den Schlüsseln des zurückgegebenen Dokuments der obersten Ebene zu machen.
MongoDB 2.6
Und wenn Sie auf MongoDB 2.6 zurückgreifen, um die Versionen dazwischen abzudecken, ändert sich hier nur die Verwendung von $map
und ein $literal
für die Eingabe mit der Array-Deklaration:
db.profile.aggregate([
{ "$project": {
"data": {
"$map": {
"input": { "$literal": ["gender","caste", "education"] },
"as": "k",
"in": {
"k": "$$k",
"v": {
"$cond": {
"if": { "$eq": [ "$$k", "gender" ] },
"then": "$gender",
"else": {
"$cond": {
"if": { "$eq": [ "$$k", "caste" ] },
"then": "$caste",
"else": "$education"
}
}
}
}
}
}
}
}},
{ "$unwind": "$data" },
{ "$group": {
"_id": "$data",
"total": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.k",
"v": {
"$push": { "name": "$_id.v", "total": "$total" }
}
}},
{ "$group": {
"_id": null,
"data": { "$push": { "k": "$_id", "v": "$v" } }
}},
/*
{ "$replaceRoot": {
"newRoot": {
"$arrayToObject": "$data"
}
}}
*/
])
.map( d =>
d.data.map( e => ({ [e.k]: e.v }) )
.reduce((acc,curr) => Object.assign(acc,curr),{})
)
Da die Grundidee hier darin besteht, ein bereitgestelltes Array der Feldnamen zu „iterieren“, erfolgt die eigentliche Zuweisung von Werten durch „Verschachtelung“ des $cond
Aussagen. Für drei mögliche Ergebnisse bedeutet dies nur eine einzige Verschachtelung, um für jedes Ergebnis zu "verzweigen".
Moderne MongoDB ab 3.4 haben $switch
was diese Verzweigung einfacher macht, aber dies zeigt, dass die Logik immer möglich war und der $cond
Operator gibt es seit der Einführung des Aggregations-Frameworks in MongoDB 2.2.
Auch hier gilt die gleiche Transformation für das Cursor-Ergebnis, da es dort nichts Neues gibt und die meisten Programmiersprachen die Fähigkeit haben, dies jahrelang zu tun, wenn nicht von Anfang an.
Natürlich kann der grundlegende Prozess sogar weit zurück zu MongoDB 2.2 durchgeführt werden, aber nur die Array-Erstellung und $unwind
auf andere Weise. Aber zu diesem Zeitpunkt sollte niemand MongoDB unter 2.8 ausführen, und die offizielle Unterstützung sogar von 3.0 geht schnell zur Neige.
Ausgabe
Zur Visualisierung hat die Ausgabe aller hier gezeigten Pipelines die folgende Form, bevor die letzte "Transformation" durchgeführt wird:
/* 1 */
{
"_id" : null,
"data" : [
{
"k" : "gender",
"v" : [
{
"name" : "Male",
"total" : 3.0
},
{
"name" : "Female",
"total" : 2.0
}
]
},
{
"k" : "education",
"v" : [
{
"name" : "M.C.A",
"total" : 1.0
},
{
"name" : "B.E",
"total" : 3.0
},
{
"name" : "B.Com",
"total" : 1.0
}
]
},
{
"k" : "caste",
"v" : [
{
"name" : "Lingayath",
"total" : 3.0
},
{
"name" : "Vokkaliga",
"total" : 2.0
}
]
}
]
}
Und dann entweder durch den $replaceRoot
oder die Cursor-Transformation, wie gezeigt, das Ergebnis wird:
/* 1 */
{
"gender" : [
{
"name" : "Male",
"total" : 3.0
},
{
"name" : "Female",
"total" : 2.0
}
],
"education" : [
{
"name" : "M.C.A",
"total" : 1.0
},
{
"name" : "B.E",
"total" : 3.0
},
{
"name" : "B.Com",
"total" : 1.0
}
],
"caste" : [
{
"name" : "Lingayath",
"total" : 3.0
},
{
"name" : "Vokkaliga",
"total" : 2.0
}
]
}
Während wir also einige neue und ausgefallene Operatoren in die Aggregationspipeline einfügen können, wo wir diese zur Verfügung haben, ist der häufigste Anwendungsfall diese "End-of-Pipeline-Transformationen". In diesem Fall können wir genauso gut einfach dieselbe Transformation für jedes Dokument durchführen stattdessen werden die Cursor-Ergebnisse zurückgegeben.