Existují různé přístupy v závislosti na dostupné verzi, ale všechny se v podstatě rozdělují na transformaci polí dokumentu do samostatných dokumentů v „poli“ a následné „odvíjení“ tohoto pole pomocí $unwind
a postupným prováděním $group
stupně, aby bylo možné akumulovat výstupní součty a pole.
MongoDB 3.4.4 a vyšší
Nejnovější verze mají speciální operátory, jako je $arrayToObject
a $objectToArray
což může učinit přenos do počátečního "pole" ze zdrojového dokumentu dynamičtější než v dřívějších verzích:
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"
}
}}
])
Takže pomocí $objectToArray
z počátečního dokumentu vytvoříte pole jeho klíčů a hodnot jako "k"
a "v"
klíče ve výsledném poli objektů. Aplikujeme $filter
zde pro výběr pomocí "klíče". Zde pomocí $in
se seznamem klíčů, který chceme, ale mohl by být dynamičtěji použit jako seznam klíčů k „vyloučení“, kde to bylo kratší. Pouze používá logické operátory k vyhodnocení podmínky.
Koncová fáze zde používá $replaceRoot
a protože všechny naše manipulace a "seskupování" mezi nimi stále zachovává "k"
a "v"
formulář, pak použijeme $arrayToObject
zde povýšit naše "pole objektů" ve výsledku na "klíče" dokumentu nejvyšší úrovně ve výstupu.
MongoDB 3.6 $mergeObjects
Jako další vrásku zde MongoDB 3.6 obsahuje $mergeObjects
který lze použít jako "akumulátor "
." v $group
také fázi potrubí, čímž nahradí $push
a vytvoření konečného $replaceRoot
pouhým posunutím "data"
místo toho klíč ke kořenovému adresáři vráceného 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" } }
])
To se ve skutečnosti příliš neliší od toho, co je demonstrováno celkově, ale jednoduše ukazuje, jak $mergeObjects
lze použít tímto způsobem a může být užitečné v případech, kdy seskupovací klíč byl něco jiného a my jsme nechtěli, aby to konečné "sloučení" do kořenového prostoru objektu.
Všimněte si, že $arrayToObject
je stále potřeba transformovat „hodnotu“ zpět na název „klíče“, ale děláme to pouze během akumulace, nikoli po seskupení, protože nová akumulace umožňuje „sloučení“ klíčů.
MongoDB 3.2
Vezmeme-li zpět verzi, nebo i když máte MongoDB 3.4.x, která je nižší než vydání 3.4.4, stále můžeme mnohé z toho použít, ale místo toho se zabýváme vytvářením pole více statickým způsobem. jako zpracování finální „transformace“ na výstupu odlišně kvůli agregačním operátorům, které nemáme:
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 je přesně to samé, až na to, že místo dynamické transformace dokumentu do pole ve skutečnosti "explicitně" přiřadíme každému členu pole stejné "k"
a "v"
notový zápis. Opravdu jen ponecháme tato klíčová jména pro konvenci v tomto bodě, protože žádný z agregačních operátorů na tom vůbec nezávisí.
Také místo použití $replaceRoot
, prostě děláme přesně to samé, co tam dělala předchozí implementace fáze potrubí, ale místo toho v kódu klienta. Všechny ovladače MongoDB mají určitou implementaci cursor.map()
povolit "transformace kurzoru". Zde s shellem používáme základní funkce JavaScriptu Array.map()
a Array.reduce()
převzít tento výstup a znovu povýšit obsah pole na klíče vráceného dokumentu nejvyšší úrovně.
MongoDB 2.6
A když se vrátíme k MongoDB 2.6, abychom pokryli verze mezi nimi, jediná věc, která se zde mění, je použití $map
a $literal
pro vstup s deklarací pole:
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),{})
)
Vzhledem k tomu, že základní myšlenkou je „iterovat“ poskytnuté pole názvů polí, skutečné přiřazení hodnot přichází „vnořením“ $cond
prohlášení. Pro tři možné výsledky to znamená pouze jediné vnoření, aby se pro každý výsledek „rozvětvily“.
Moderní MongoDB od 3.4 mají $switch
což dělá toto větvení jednodušší, přesto to ukazuje, že logika byla vždy možná a $cond
Operátor existuje od zavedení agregačního rámce v MongoDB 2.2.
Opět platí stejná transformace na výsledek kurzoru, protože tam není nic nového a většina programovacích jazyků to umí léta, ne-li od začátku.
Základní proces lze samozřejmě provést i cestou zpět k MongoDB 2.2, ale stačí použít vytvoření pole a $unwind
jiným způsobem. Ale nikdo by v tuto chvíli neměl provozovat MongoDB pod 2.8 a oficiální podpora dokonce i od 3.0 rychle končí.
Výstup
Pro vizualizaci má výstup všech zde předvedených potrubí před provedením poslední „transformace“ následující podobu:
/* 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 pak buď pomocí $replaceRoot
nebo transformace kurzoru, jak je ukázáno, výsledkem je:
/* 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že i když můžeme do agregačního kanálu vložit některé nové a ozdobné operátory, kde je máme k dispozici, nejběžnějším případem použití jsou tyto „transformace na konci kanálu“, v jejichž případě můžeme také jednoduše provést stejnou transformaci na každém dokumentu v místo toho se vrátily výsledky kurzoru.