Der er forskellige tilgange afhængigt af den tilgængelige version, men de opdeles i det væsentlige til at transformere dine dokumentfelter til separate dokumenter i et "array" og derefter "afvikle" det array med $unwind
og gør successive $group
trin for at akkumulere outputtotaler og arrays.
MongoDB 3.4.4 og nyere
Seneste udgivelser har specielle operatorer som $arrayToObject
og $objectToArray
som kan gøre overførslen til det oprindelige "array" fra kildedokumentet mere dynamisk end i tidligere udgivelser:
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"
}
}}
])
Så ved at bruge $objectToArray
du gør det indledende dokument til en række af dets nøgler og værdier som "k"
og "v"
nøgler i den resulterende række af objekter. Vi anvender $filter
her for at vælge med "tast". Her bruger du $in
med en liste over nøgler, vi ønsker, men dette kunne bruges mere dynamisk som en liste over nøgler til at "udelukke", hvor det var kortere. Det er bare at bruge logiske operatorer til at evaluere tilstanden.
Slutstadiet her bruger $replaceRoot
og da al vores manipulation og "gruppering" derimellem stadig holder "k"
og "v"
formular, bruger vi derefter $arrayToObject
her for at promovere vores "array af objekter" som resultat til "nøglerne" af dokumentet på øverste niveau i output.
MongoDB 3.6 $mergeObjects
Som en ekstra rynke her inkluderer MongoDB 3.6 $mergeObjects
som kan bruges som en "akkumulator" "
i en $group
også pipeline-stadiet og erstatter dermed $push
og lave den sidste $replaceRoot
simpelthen at flytte "data"
nøgle til "roden" af det returnerede dokument i stedet:
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" } }
])
Dette er egentlig ikke så forskelligt fra det, der bliver demonstreret overordnet, men demonstrerer blot, hvordan $mergeObjects
kan bruges på denne måde og kan være nyttig i tilfælde, hvor grupperingsnøglen var noget andet, og vi ikke ønskede den endelige "fletning" til objektets rodrum.
Bemærk, at $arrayToObject
er stadig nødvendig for at omdanne "værdien" tilbage til navnet på "nøglen", men vi gør det bare under akkumuleringen i stedet for efter grupperingen, da den nye akkumulering tillader "fusion" af nøgler.
MongoDB 3.2
Tager det en version tilbage, eller selvom du har en MongoDB 3.4.x, der er mindre end 3.4.4-udgivelsen, kan vi stadig bruge meget af dette, men i stedet beskæftiger vi os også med oprettelsen af arrayet på en mere statisk måde. som at håndtere den endelige "transformation" på output anderledes på grund af de aggregeringsoperatorer, vi ikke har:
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),{})
)
Dette er nøjagtig det samme, bortset fra at i stedet for at have en dynamisk transformation af dokumentet til arrayet, tildeler vi faktisk "eksplicit" hvert array-medlem den samme "k"
og "v"
notation. Beholder egentlig bare disse nøglenavne for konventionen på dette tidspunkt, da ingen af aggregeringsoperatørerne her overhovedet er afhængige af det.
Også i stedet for at bruge $replaceRoot
, vi gør præcis det samme, som den forrige implementering af pipelinestadiet gjorde der, men i klientkoden i stedet for. Alle MongoDB-drivere har en vis implementering af cursor.map()
for at aktivere "markørtransformationer". Her med skallen bruger vi de grundlæggende JavaScript-funktioner i Array.map()
og Array.reduce()
at tage det output og igen fremme matrixindholdet til at være nøglerne til det returnerede dokument på øverste niveau.
MongoDB 2.6
Og falder vi tilbage til MongoDB 2.6 for at dække versionerne imellem, er det eneste, der ændrer sig her brugen af $map
og en $literal
til input med array-deklarationen:
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 den grundlæggende idé her er at "iterere" et givet array af feltnavne, kommer den faktiske tildeling af værdier ved at "nesting" $cond
udsagn. For tre mulige udfald betyder dette kun en enkelt indlejring for at "grene" for hvert udfald.
Moderne MongoDB fra 3.4 har $switch
hvilket gør denne forgrening enklere, men dette viser, at logikken altid var mulig, og $cond
operatør har eksisteret siden aggregeringsrammen blev introduceret i MongoDB 2.2.
Igen gælder den samme transformation på markørresultatet, da der ikke er noget nyt der, og de fleste programmeringssprog har evnen til at gøre dette i årevis, hvis ikke fra starten.
Selvfølgelig kan den grundlæggende proces endda udføres helt tilbage til MongoDB 2.2, men bare ved at anvende array-oprettelse og $unwind
på en anden måde. Men ingen burde køre MongoDB under 2.8 på dette tidspunkt, og officiel support selv fra 3.0 er endda hurtigt ved at løbe tør.
Output
Til visualisering har outputtet af alle demonstrerede pipelines her følgende form, før den sidste "transformation" er udført:
/* 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
}
]
}
]
}
Og derefter enten ved $replaceRoot
eller markørtransformationen som vist bliver resultatet:
/* 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
}
]
}
Så selvom vi kan sætte nogle nye og smarte operatører ind i aggregeringspipelinen, hvor vi har dem tilgængelige, er den mest almindelige brugssag i disse "end of pipeline-transformationer", i hvilket tilfælde vi lige så godt kan lave den samme transformation på hvert dokument i markørresultaterne returnerede i stedet.