sql >> Databáze >  >> NoSQL >> MongoDB

Seskupte odlišné hodnoty a počty pro každou vlastnost v jednom dotazu

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.




  1. Node + Mongoose:Získat poslední vložené ID?

  2. uložení obrázku do mongodb

  3. Jak implementovat trigger pro redis datastore?

  4. Přidejte data do dokumentů v Mongo DB pomocí PHP