MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

Mongodb обобщена заявка или твърде сложна?

Въпреки че трябваше да бъде по-ясно във вашия въпрос, вашата изходна извадка от източника предполага, че търсите:

  • Общ брой съобщения на „uid“
  • Отделен брой стойности в „до“
  • Отделен брой стойности в „от“
  • Обобщение на броя на "час" за всеки "uid"

Всичко това е възможно в един израз за агрегиране и е необходимо само внимателно управление на отделните списъци и след това известна манипулация за картографиране на резултатите за всеки час в период от 24 часа.

Най-добрият подход тук е подпомогнат от операторите, въведени в 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 } }
 ])

Преди 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, 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 } }
])

За да разбием това, и двата подхода тук следват едни и същи основни стъпки, като единствената реална разлика се появява в картографирането на „часовете“ за периода от 24 часа.

В първото агрегиране $group етап, целта е да се получат резултати на час, присъстващи в данните и за всяка стойност на „uid“. Простият оператор за агрегиране на дати на $hour помага да се получи тази стойност като част от ключа за групиране.

$addToSet операциите са нещо като "мини-група" сами по себе си и това позволява да се запазят "отделните набори" за всяка от стойностите "до" и "от", докато по същество все още се групират на час.

Следващата $group е по-"организационно", тъй като записаните "преброявания" за всеки час се съхраняват в масив, като същевременно се събират всички данни, за да бъдат просто групирани по "uid". Това основно ви дава всички „данни“, от които наистина се нуждаете за резултата, но разбира се $addToSet операциите тук са просто добавяне на "масиви в масиви" на отделните набори, определени на час.

За да получите тези стойности като наистина различни списъци за всеки "uid" и само, е необходимо да деконструирате всеки масив с помощта на $unwind и след това накрая групирайте обратно само като отделните „набори“. Същият $addToSet уплътнява това и $first операциите просто вземат "първите" стойности на другите полета, които вече са еднакви за целевите "per uid" данни. Доволни сме от тях, така че просто ги запазете такива, каквито са.

Последният(те) етап(и) тук са по същество „козметични“ по природа и могат също така да бъдат постигнати в код от страна на клиента. Тъй като няма налични данни за всеки един часов интервал, те трябва да бъдат картографирани в масив от стойности, представящи всеки час. Двата подхода тук се различават в зависимост от възможностите на наличните оператори между версиите.

Във версията MongoDB 3.2 има $filter и $arrayElemAt оператори, които ефективно ви позволяват да създадете логиката за „транспониране“ на входен източник на всички възможни индексни позиции (24 часа) в стойностите, които вече са определени за броя на тези часове в наличните данни. По същество това е „директно търсене“ на вече записани стойности за всеки наличен час, за да се види дали съществува, където има, броят се транспонира в пълния масив. Когато не присъства, стойност по подразбиране 0 се използва на място.

Без тези оператори извършването на това „съпоставяне“ по същество означава денормализиране на двата масива (записаните данни и пълните 24 позиции), за да се сравнят и транспонират. Това се случва при втория подход с просто сравнение на стойностите на "индекса", за да се види дали има резултат за този час. $max тук се използва главно поради двата $unwind изрази, където всяка записана стойност от изходните данни ще бъде възпроизведена за всяка възможна индексна позиция. Това „уплътнява“ само до стойностите, които се искат за „индексен час“.

При този последен подход тогава става важно $sort на групирането _id стойност. Това е така, защото съдържа позицията "индекс" и това ще е необходимо, когато премествате това съдържание обратно в масив, който очаквате да бъде подреден. Което разбира се е последната $group етап тук, където подредените позиции се поставят в масив с $push .

Обратно към „отделните списъци“, $size операторът се използва във всички случаи за определяне на „дължината“ и следователно „броя“ на различни стойности в списъците за „до“ и „от“. Това е единственото реално ограничение поне за MongoDB 2.6, но в противен случай може да бъде заменено с просто „развиване“ на всеки масив поотделно и след това групиране обратно на _id вече присъства, за да преброи записите в масива във всеки набор. Това е основен процес, но както трябва да видите $size оператор е по-добрата опция тук за цялостна производителност.

Като последна бележка, вашите данни за заключение са малко по-различни, тъй като вероятно записът с "ddd" в "from" е предназначен да бъде същият и в "to", но вместо това е записан като "bbb". Това променя отделния брой на третото групиране "uid" за "до" надолу с един запис. Но, разбира се, логичните резултати при изходните данни са стабилни:

{ "_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 ] }

N.B Източникът също има правописна грешка, като разделителят е вмъкнат с : вместо запетая веднага след клеймото за време на всички редове.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. грешка при заявка за актуализиране на mongo

  2. MongoDB сортиране срещу обобщено $sort на индекс на масив

  3. Получаване на първия елемент в масива и връщане чрез Aggregate?

  4. MongoDB чрез Mongoose JS - Какво е findByID?

  5. MongoExport Твърде много позиционни опции