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

Souhrnný dotaz Mongodb, nebo příliš složitý?

I když to mělo být ve vaší otázce jasnější, váš výstupní vzorek ze zdroje naznačuje, že hledáte:

  • Celkový počet zpráv na "uid"
  • Odlišný počet hodnot v "do"
  • Odlišný počet hodnot v „od“
  • Souhrn počtů za "hodinu" pro každé "uid"

To vše je možné v jediném agregačním příkazu a vyžaduje to jen pečlivou správu jednotlivých seznamů a poté určitou manipulaci k mapování výsledků pro každou hodinu v období 24 hodin.

Nejlepší přístup je zde pomocí operátorů představených v MongoDB 3.2:

db.collection.aggregate([
    // First group by hour within "uid" and keep distinct "to" and "from"
    { "$group": {
        "_id": {
            "uid": "$uid",
            "time": { "$hour": "$timestamp" }
        },
        "from": { "$addToSet": "$from" },
        "to": { "$addToSet": "$to" },
        "count": { "$sum": 1 }
    }},

    // Roll-up to "uid" and keep each hour in an array
    { "$group": {
        "_id": "$_id.uid",
        "total": { "$sum": "$count" },
        "from": { "$addToSet": "$from" },
        "to": { "$addToSet": "$to" },
        "temp_hours": { 
            "$push": {
                "index": "$_id.time",
                "count": "$count"
            }
        }
     }},

     // Getting distinct "to" and "from" requires a double unwind of arrays
     { "$unwind": "$to" },
     { "$unwind": "$to" },
     { "$unwind": "$from" },
     { "$unwind": "$from" },

     // And then adding back to sets for distinct
     { "$group": {
        "_id": "$_id",
        "total": { "$first": "$total" },
        "from": { "$addToSet": "$from" },
        "to": { "$addToSet": "$to" },
        "temp_hours": { "$first": "$temp_hours" }
     }},

     // Map out for each hour and count size of distinct lists
     { "$project": {
        "count": "$total",
        "from_count": { "$size": "$from" },
        "to_count": { "$size": "$to" },
        "hours": {
            "$map": {
                "input": [
                     00,01,02,03,04,05,06,07,08,09,10,11,
                     12,13,14,15,16,17,18,19,20,21,22,23
                 ],
                 "as": "el",
                 "in": {
                      "$ifNull": [
                          { "$arrayElemAt": [
                              { "$map": {
                                  "input": { "$filter": {
                                     "input": "$temp_hours",
                                     "as": "tmp",
                                     "cond": {
                                         "$eq": [ "$$el", "$$tmp.index" ]
                                     }
                                  }},
                                 "as": "out",
                                 "in": "$$out.count"
                              }},
                              0
                          ]},
                          0
                      ]
                 }
            }
        }
     }},

     // Optionally sort in "uid" order
     { "$sort": { "_id": 1 } }
 ])

Před MongoDB 3.2 se musíte trochu více zapojit do mapování obsahu pole pro všechny hodiny dne:

db.collection.aggregate([

    // First group by hour within "uid" and keep distinct "to" and "from"
    { "$group": {
        "_id": {
            "uid": "$uid",
            "time": { "$hour": "$timestamp" }
        },
        "from": { "$addToSet": "$from" },
        "to": { "$addToSet": "$to" },
        "count": { "$sum": 1 }
    }},

    // Roll-up to "uid" and keep each hour in an array
    { "$group": {
        "_id": "$_id.uid",
        "total": { "$sum": "$count" },
        "from": { "$addToSet": "$from" },
        "to": { "$addToSet": "$to" },
        "temp_hours": { 
            "$push": {
                "index": "$_id.time",
                "count": "$count"
            }
        }
     }},

     // Getting distinct "to" and "from" requires a double unwind of arrays
     { "$unwind": "$to" },
     { "$unwind": "$to" },
     { "$unwind": "$from" },
     { "$unwind": "$from" },

     // And then adding back to sets for distinct, also adding the indexes array
     { "$group": {
        "_id": "$_id",
        "total": { "$first": "$total" },
        "from": { "$addToSet": "$from" },
        "to": { "$addToSet": "$to" },
        "temp_hours": { "$first": "$temp_hours" },
        "indexes": { "$first": { "$literal": [
                     00,01,02,03,04,05,06,07,08,09,10,11,
                     12,13,14,15,16,17,18,19,20,21,22,23
        ] } }
     }},

     // Denormalize both arrays
     { "$unwind": "$temp_hours" },
     { "$unwind": "$indexes" },

     // Marry up the index entries and keep either the value or 0
     // Note you are normalizing the double unwind to distinct index
     { "$group": {
         "_id": {
             "_id": "$_id",
             "index": "$indexes"
         },
         "total": { "$first": "$total" }, 
         "from": { "$first": "$from" },
         "to": { "$first": "$to" },
         "count": {
             "$max": {
                 "$cond": [
                     { "$eq": [ "$indexes", "$temp_hours.index" ] },
                     "$temp_hours.count",
                     0
                 ]
             }
         }
     }},

     // Sort to keep index order - !!Important!!         
     { "$sort": { "_id": 1 } },

     // Put the hours into the array and get sizes for other results
     { "$group": {
         "_id": "$_id._id",
         "count": { "$first": "$total" },
         "from_count": { "$first": { "$size": "$from" } },
         "to_count": { "$first": { "$size": "$to" } },
         "hours": { "$push": "$count" }
     }},

     // Optionally sort in "uid" order
     { "$sort": { "_id": 1 } }
])

Abychom to rozebrali, oba přístupy zde sledují stejné základní kroky, s jediným skutečným rozdílem v mapování „hodin“ za 24 hodin.

V první agregaci $group cílem je získat výsledky za hodinu přítomné v datech a pro každou hodnotu „uid“. Jednoduchý operátor agregace dat $hour pomáhá získat tuto hodnotu jako součást seskupovacího klíče.

$addToSet operace jsou samy o sobě jakousi „mini-skupinou“, a to umožňuje ponechat „odlišné sady“ pro každou z hodnot „do“ a „od“, přičemž se v podstatě stále seskupují za hodinu.

Další $skupina je více "organizační", protože zaznamenané "počty" pro každou hodinu jsou uchovávány v poli, zatímco se shromažďují všechna data, aby byla seskupena podle "uid". To vám v podstatě poskytuje všechna "data", která skutečně potřebujete pro výsledek, ale samozřejmě $addToSet operace zde pouze přidávají "pole v polích" různých sad určených za hodinu.

Aby bylo možné získat tyto hodnoty jako skutečně odlišné seznamy pro každé „uid“ a pouze, je nutné dekonstruovat každé pole pomocí $unwind a nakonec seskupit zpět jako pouze odlišné "množiny". Stejný $addToSet zkomprimuje to a $first operace pouze přebírají „první“ hodnoty ostatních polí, která jsou již všechna stejná pro cílová data „per uid“. Jsme s nimi spokojeni, takže je nechte tak, jak jsou.

Konečné fáze zde mají v podstatě "kosmetický" charakter a lze je rovněž dosáhnout v kódu na straně klienta. Protože nejsou k dispozici data pro každý jednotlivý hodinový interval, je třeba je namapovat do pole hodnot představujících každou hodinu. Tyto dva přístupy se zde liší podle schopností dostupných operátorů mezi verzemi.

Ve verzi MongoDB 3.2 jsou $filter a $arrayElemAt operátory, které efektivně umožňují vytvořit logiku pro "transponování" vstupního zdroje všech možných indexových pozic (24 hodin) do hodnot, které jsou již určeny pro počty z těchto hodin v dostupných datech. Toto je v podstatě "přímé vyhledávání" hodnot již zaznamenaných pro každou dostupnou hodinu, aby se zjistilo, zda existuje, kde se počet transponuje do celého pole. Pokud není přítomen, výchozí hodnota je 0 se používá na místě.

Bez těchto operátorů znamená provedení tohoto „spárování“ v podstatě denormalizaci obou polí (zaznamenaná data a celých 24 pozic) za účelem srovnání a transpozice. To je to, co se děje ve druhém přístupu s jednoduchým porovnáním hodnot "indexu", aby se zjistilo, zda pro tuto hodinu existuje výsledek. $max Operátor se zde používá hlavně kvůli dvěma $unwind příkazy, kde bude každá zaznamenaná hodnota ze zdrojových dat reprodukována pro každou možnou pozici indexu. To se "zhutní" pouze na hodnoty, které jsou požadovány za "indexovou hodinu".

V tomto druhém přístupu je pak důležité $sort na seskupení _id hodnota. Je to proto, že obsahuje pozici "index" a ta bude potřeba při přesunu tohoto obsahu zpět do pole, u kterého očekáváte, že bude uspořádán. Což je samozřejmě poslední $group zde jsou uspořádané pozice vloženy do pole pomocí $push .

Zpět na „odlišné seznamy“, $size Operátor se používá ve všech případech k určení "délky" a tedy "počtu" různých hodnot v seznamech pro "do" a "od". Toto je jediné skutečné omezení přinejmenším na MongoDB 2.6, ale jinak může být nahrazeno jednoduchým „rozvinutím“ každého pole jednotlivě a pak seskupením zpět na _id již přítomen, aby bylo možné spočítat položky pole v každé sadě. Je to základní proces, ale jak byste měli vidět $size Operátor je lepší volbou pro celkový výkon.

Závěrečná poznámka, vaše závěrová data jsou trochu mimo, protože záznam s „ddd“ v „od“ měl být pravděpodobně stejný v „to“, ale místo toho je zaznamenán jako „bbb“. Tím se změní zřetelný počet třetího seskupení "uid" pro "to" o jeden záznam. Ale logické výsledky dané zdrojovými daty jsou samozřejmě správné:

{ "_id" : 1000000, "count" : 3, "from_count" : 2, "to_count" : 2, "hours" : [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0 ] }
{ "_id" : 2000000, "count" : 2, "from_count" : 1, "to_count" : 1, "hours" : [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0 ] }
{ "_id" : 3000000, "count" : 5, "from_count" : 5, "to_count" : 4, "hours" : [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0 ] }

Poznámka:Zdroj má také překlep s oddělovačem vloženým do : místo čárky hned za časovým razítkem na všech řádcích.




  1. MongoDB, přidat nové { pole :hodnota } do existujícího vloženého dokumentu s víceúrovňovou tečkovou notací?

  2. Mongoose vyplní dílčí dílčí dokument

  3. Tisková zpráva:ScaleGrid oznamuje hostingovou službu pro Redis™ na AWS

  4. Filtrujte a znovu uspořádejte výsledky pomocí node.js a mongodb podle data (měsíce)