Istnieją różne podejścia w zależności od dostępnej wersji, ale wszystkie zasadniczo polegają na przekształceniu pól dokumentu w oddzielne dokumenty w „tablicy”, a następnie „rozwinięciu” tej tablicy za pomocą $unwind
i wykonywanie kolejnych $group
etapy w celu zgromadzenia sum wyjściowych i tablic.
MongoDB 3.4.4 i nowsze
Najnowsze wydania mają specjalne operatory, takie jak $arrayToObject
i $objectToArray
co może sprawić, że transfer do początkowej "tablicy" z dokumentu źródłowego będzie bardziej dynamiczny niż we wcześniejszych wydaniach:
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"
}
}}
])
Używając $objectToArray
zamieniasz początkowy dokument w tablicę jego kluczy i wartości jako "k"
i "v"
klucze w wynikowej tablicy obiektów. Stosujemy $filter
tutaj, aby wybrać za pomocą "klucza". Tutaj używając $in
z listą kluczy, które chcemy, ale może to być bardziej dynamicznie używane jako lista kluczy do „wykluczenia” tych, które były krótsze. Po prostu używamy operatorów logicznych do oceny warunku.
Etap końcowy używa tutaj $replaceRoot
a ponieważ cała nasza manipulacja i "grupowanie" pomiędzy nadal utrzymuje to "k"
i "v"
formularz, następnie używamy $arrayToObject
tutaj, aby w rezultacie promować naszą „tablicę obiektów” do „kluczy” dokumentu najwyższego poziomu w wynikach.
MongoDB 3.6 $mergeObjects
Jako dodatkową zmarszczkę, MongoDB 3.6 zawiera $mergeObjects
który może być używany jako "akumulator
w $group
również etap potoku, zastępując w ten sposób $push
i tworzenie ostatecznego $replaceRoot
po prostu przesuwając "dane"
klucz do "głównego" zwróconego dokumentu:
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" } }
])
Nie różni się to aż tak bardzo od tego, co zostało zaprezentowane ogólnie, ale po prostu pokazuje, jak $mergeObjects
może być używany w ten sposób i może być przydatny w przypadkach, gdy klucz grupujący był czymś innym i nie chcieliśmy tego ostatecznego „scalenia” z przestrzenią główną obiektu.
Pamiętaj, że $arrayToObject
jest nadal potrzebne do przekształcenia „wartości” z powrotem w nazwę „klucza”, ale robimy to po prostu podczas akumulacji, a nie po grupowaniu, ponieważ nowa akumulacja umożliwia „scalanie” kluczy.
MongoDB 3.2
Cofając się o wersję lub nawet jeśli masz MongoDB 3.4.x, która jest mniejsza niż wersja 3.4.4, nadal możemy jej używać, ale zamiast tego zajmujemy się tworzeniem tablicy w bardziej statyczny sposób jako sposób obsługi końcowej "transformacji" na wyjściu inaczej ze względu na operatory agregacji, których nie mamy:
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),{})
)
To jest dokładnie to samo, z wyjątkiem tego, że zamiast dynamicznego przekształcania dokumentu w tablicę, w rzeczywistości "jawnie" przypisujemy każdemu elementowi tablicy ten sam "k"
i "v"
notacja. Naprawdę po prostu zachowaj te nazwy kluczy dla konwencji w tym momencie, ponieważ żaden z operatorów agregacji tutaj w ogóle nie zależy od tego.
Również zamiast używać $replaceRoot
, robimy dokładnie to samo, co robiła tam poprzednia implementacja na etapie potoku, ale zamiast tego w kodzie klienta. Wszystkie sterowniki MongoDB mają implementację cursor.map()
aby włączyć "przekształcenia kursora". Tutaj z powłoką używamy podstawowych funkcji JavaScript Array.map()
i Array.reduce()
aby wziąć to wyjście i ponownie promować zawartość tablicy jako klucze zwróconego dokumentu najwyższego poziomu.
MongoDB 2.6
Wracając do MongoDB 2.6, aby uwzględnić wersje pomiędzy nimi, jedyną rzeczą, która się tutaj zmienia, jest użycie $map
i $literal
dla danych wejściowych z deklaracją tablicy:
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),{})
)
Ponieważ podstawową ideą jest tutaj „iteracja” dostarczonej tablicy nazw pól, faktyczne przypisanie wartości następuje przez „zagnieżdżenie” $cond
sprawozdania. Dla trzech możliwych wyników oznacza to tylko jedno zagnieżdżenie w celu "rozgałęzienia" dla każdego wyniku.
Nowoczesne MongoDB z wersji 3.4 ma $switch
co upraszcza to rozgałęzienie, ale pokazuje, że logika była zawsze możliwa i $cond
operator istnieje od czasu wprowadzenia struktury agregacji w MongoDB 2.2.
Ponownie, ta sama transformacja na wyniku kursora ma zastosowanie, ponieważ nie ma tam nic nowego, a większość języków programowania ma możliwość robienia tego przez lata, jeśli nie od samego początku.
Oczywiście podstawowy proces można wykonać nawet z powrotem do MongoDB 2.2, ale po prostu stosując tworzenie tablicy i $unwind
w inny sposób. Ale w tym momencie nikt nie powinien używać MongoDB w wersji 2.8, a oficjalne wsparcie nawet od wersji 3.0 szybko się wyczerpuje.
Wyjście
W celu wizualizacji dane wyjściowe wszystkich zademonstrowanych tu potoków mają następującą postać przed wykonaniem ostatniej „transformacji”:
/* 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
}
]
}
]
}
A potem albo przez $replaceRoot
lub kursor przekształci się, jak pokazano, wynik wygląda następująco:
/* 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
}
]
}
Tak więc, chociaż możemy umieścić kilka nowych i fantazyjnych operatorów w potoku agregacji, w którym są one dostępne, najczęstszym przypadkiem użycia są te "transformacje końca potoku", w którym to przypadku równie dobrze możemy po prostu wykonać tę samą transformację na każdym dokumencie w zamiast tego zostały zwrócone wyniki kursora.