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

Seskupování a počítání pomocí agregačního rámce

Zdá se, že jste s tím začali, ale ztratili jste se v některých dalších konceptech. Při práci s poli v dokumentech existuje několik základních pravd, ale začněme tam, kde jste skončili:

db.sample.aggregate([
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 }
    }}
])

Takže to bude jen používat $group potrubí pro shromažďování vašich dokumentů na různých hodnotách pole „stav“ a poté také vytvoření dalšího pole pro „count“, které samozřejmě „počítá“ výskyty seskupovacího klíče předáním hodnoty 1 na $sum operátor pro každý nalezený dokument. Tím se dostanete do bodu, který je podobný tomu, co popisujete:

{ "_id" : "done", "count" : 2 }
{ "_id" : "canceled", "count" : 1 }

To je první fáze tohoto a dostatečně snadno pochopitelná, ale nyní potřebujete vědět, jak získat hodnoty z pole. Možná budete v pokušení, jakmile pochopíte "tečkovou notaci" správně udělat něco takového:

db.sample.aggregate([
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 },
        "total": { "$sum": "$devices.cost" }
    }}
])

Zjistíte však, že „celkem“ bude ve skutečnosti 0 pro každý z těchto výsledků:

{ "_id" : "done", "count" : 2, "total" : 0 }
{ "_id" : "canceled", "count" : 1, "total" : 0 }

Proč? Operace agregace MongoDB, jako je tato, ve skutečnosti při seskupování neprocházejí prvky pole. Aby to bylo možné, agregační rámec má koncept nazvaný $unwind . Název je poměrně samovysvětlující. Vestavěné pole v MongoDB je podobné tomu, že mezi propojenými datovými zdroji existuje asociace „one-to-many“. Co tedy $unwind do je přesně takový výsledek "spojení", kde výsledné "dokumenty" jsou založeny na obsahu pole a duplicitních informacích pro každého rodiče.

Chcete-li tedy působit na prvky pole, musíte použít $unwind První. To by vás mělo logicky vést ke kódu, jako je tento:

db.sample.aggregate([
    { "$unwind": "$devices" },
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 },
        "total": { "$sum": "$devices.cost" }
    }}
])

A pak výsledek:

{ "_id" : "done", "count" : 4, "total" : 700 }
{ "_id" : "canceled", "count" : 2, "total" : 350 }

Ale to není úplně správné, že? Pamatujte si, co jste se právě naučili z $unwind a jak se provede dennormalizované spojení s nadřazenými informacemi? Nyní je to duplikováno pro každý dokument, protože oba měly dva členy pole. Takže zatímco pole „celkem“ je správné, „počet“ je dvakrát větší, než by měl být v každém případě.

Je třeba věnovat trochu větší opatrnosti, takže místo toho, abyste to dělali v jediném $group fáze se provádí ve dvou:

db.sample.aggregate([
    { "$unwind": "$devices" },
    { "$group": {
        "_id": "$_id",
        "status": { "$first": "$status" },
        "total": { "$sum": "$devices.cost" }
    }},
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 },
        "total": { "$sum": "$total" }
    }}
])

Což nyní dostane výsledek se správnými součty:

{ "_id" : "canceled", "count" : 1, "total" : 350 }
{ "_id" : "done", "count" : 2, "total" : 700 }

Nyní jsou čísla správná, ale stále to není přesně to, co požadujete. Myslím, že byste se tam měli zastavit, protože druh výsledku, který očekáváte, opravdu není vhodný pouze pro jeden výsledek ze samotné agregace. Hledáte, aby součet byl „uvnitř“ výsledku. Opravdu to tam nepatří, ale na malých datech je to v pořádku:

db.sample.aggregate([
    { "$unwind": "$devices" },
    { "$group": {
        "_id": "$_id",
        "status": { "$first": "$status" },
        "total": { "$sum": "$devices.cost" }
    }},
    { "$group": {
        "_id": "$status",
        "count": { "$sum": 1 },
        "total": { "$sum": "$total" }
    }},
    { "$group": {
        "_id": null,
        "data": { "$push": { "count": "$count", "total": "$total" } },
        "totalCost": { "$sum": "$total" }
    }}
])

A konečný výsledek:

{
    "_id" : null,
    "data" : [
            {
                    "count" : 1,
                    "total" : 350
            },
            {
                    "count" : 2,
                    "total" : 700
            }
    ],
    "totalCost" : 1050
}

Ale "Nedělejte to" . MongoDB má pro dokument limit odezvy 16 MB, což je omezení specifikace BSON. U malých výsledků můžete provést tento druh pohodlného balení, ale ve větším schématu věcí chcete výsledky v dřívější podobě a buď samostatný dotaz, nebo žít s iterací celých výsledků, abyste získali součet ze všech dokumentů.

Zdá se, že používáte verzi MongoDB nižší než 2.6 nebo kopírujete výstup z prostředí RoboMongo, které nepodporuje funkce nejnovější verze. Od MongoDB 2.6 mohou být výsledky agregace spíše „kurzorem“ než jedním polem BSON. Celková odezva tedy může být mnohem větší než 16 MB, ale pouze v případě, že výsledky nekomprimujete do jednoho dokumentu, jak je znázorněno v posledním příkladu.

To by platilo zejména v případech, kdy jste „stránkovali“ výsledky se 100 až 1000 řádky výsledků, ale chtěli jste pouze vrátit „celkem“ v odpovědi API, když vracíte pouze „stránku“ s 25 výsledky na čas.

V každém případě by vám to mělo poskytnout rozumný návod, jak získat typ výsledků, které očekáváte od běžného formuláře dokumentu. Pamatujte $unwind za účelem zpracování polí a obecně $group vícekrát, abyste získali součty na různých úrovních seskupení ze seskupení dokumentů a kolekcí.




  1. Otestujte prázdný řetězec v mongodb a pymongo

  2. MongoDB Bound Queries:Jak převedu míli na radiány?

  3. Spuštění Meteoru na localhost selže s RangeError:port by měl být>=0 a <65536:NaN

  4. Jak nahradit NaN null z Mongo shell?