MongoDB
 sql >> Teknologi Basis Data >  >> NoSQL >> MongoDB

Kueri agregat Mongodb, atau terlalu rumit?

Meskipun seharusnya dibuat lebih jelas dalam pertanyaan Anda, sampel keluaran Anda dari sumber menunjukkan bahwa Anda mencari:

  • Jumlah total pesan per "uid"
  • Jumlah nilai yang berbeda dalam "ke"
  • Jumlah nilai yang berbeda dalam "dari"
  • Ringkasan hitungan per "jam" untuk setiap "uid"

Ini semua mungkin dalam satu pernyataan agregasi, dan hanya diperlukan beberapa pengelolaan yang cermat dari daftar yang berbeda dan kemudian beberapa manipulasi untuk memetakan hasil untuk setiap jam dalam periode 24 jam.

Pendekatan terbaik di sini dibantu oleh operator yang diperkenalkan di 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 } }
 ])

Sebelum MongoDB 3.2 Anda perlu sedikit lebih terlibat untuk memetakan konten larik sepanjang hari:

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 } }
])

Untuk memecahnya, kedua pendekatan di sini mengikuti langkah dasar yang sama, dengan satu-satunya perbedaan nyata terjadi pada pemetaan "jam" untuk periode 24 jam.

Dalam agregasi pertama $group tahap, tujuannya adalah untuk mendapatkan hasil per jam yang ada dalam data dan untuk setiap nilai "uid". Operator agregasi tanggal sederhana $hour membantu mendapatkan nilai ini sebagai bagian dari kunci pengelompokan.

$addToSet operasi adalah semacam "mini-grup" dalam dirinya sendiri, dan ini memungkinkan untuk menjaga "set berbeda" untuk masing-masing nilai "ke" dan "dari" sementara pada dasarnya masih mengelompokkan per jam.

$group berikutnya lebih "organisasi", karena "jumlah" yang direkam untuk setiap jam disimpan dalam array sambil menggulung semua data untuk hanya dikelompokkan per "uid". Ini pada dasarnya memberi Anda semua "data" yang benar-benar Anda butuhkan untuk hasilnya, tetapi tentu saja $addToSet operasi di sini hanya menambahkan "array dalam array" dari set berbeda yang ditentukan per jam.

Untuk mendapatkan nilai-nilai ini sebagai daftar yang benar-benar berbeda per setiap "uid" dan hanya, perlu untuk mendekonstruksi setiap array menggunakan $unwind dan akhirnya kelompokkan kembali hanya sebagai "set" yang berbeda. $addToSet yang sama memadatkan ini, dan $first operasi hanya mengambil nilai "pertama" dari bidang lain, yang semuanya sudah sama untuk data target "per uid". Kami senang dengan itu, jadi biarkan mereka apa adanya.

Tahap akhir di sini pada dasarnya bersifat "kosmetik" dan sama-sama dapat dicapai dalam kode sisi klien. Karena tidak ada data yang ada untuk setiap interval jam, data tersebut perlu dipetakan ke dalam larik nilai yang mewakili setiap jam. Dua pendekatan di sini bervariasi pada kemampuan operator yang tersedia antar versi.

Dalam rilis MongoDB 3.2, ada $filter dan $arrayElemAt operator yang secara efektif memungkinkan Anda membuat logika untuk "mengubah posisi" sumber input dari semua posisi indeks yang mungkin (24 jam) ke dalam nilai yang sudah ditentukan untuk hitungan dari jam tersebut dalam data yang tersedia. Ini pada dasarnya adalah "pencarian langsung" dari nilai yang sudah direkam untuk setiap jam yang tersedia untuk melihat apakah ada, di mana penghitungannya dialihkan ke array penuh. Jika tidak ada, nilai default 0 digunakan di tempat.

Tanpa operator tersebut, melakukan "pencocokan" ini pada dasarnya berarti mendenormalisasi kedua array (data yang direkam dan 24 posisi penuh) untuk membandingkan dan mentranspos. Inilah yang terjadi pada pendekatan kedua dengan perbandingan sederhana dari nilai "indeks" untuk melihat apakah ada hasil untuk jam tersebut. $max operator di sini terutama digunakan karena dua $unwind pernyataan, di mana setiap nilai yang direkam dari data sumber akan direproduksi untuk setiap posisi indeks yang mungkin. Ini "memadat" menjadi hanya nilai yang diinginkan per "jam indeks".

Dalam pendekatan yang terakhir itu, menjadi penting untuk $sort pada pengelompokan _id nilai. Ini karena berisi posisi "indeks", dan itu akan diperlukan saat memindahkan konten ini kembali ke array yang Anda harapkan untuk dipesan. Yang tentu saja merupakan $group terakhir tahap di sini di mana posisi yang dipesan dimasukkan ke dalam array dengan $push .

Kembali ke "daftar berbeda", $size operator digunakan dalam semua kasus untuk menentukan "panjang" dan karenanya "menghitung" nilai yang berbeda dalam daftar untuk "ke" dan "dari". Ini adalah satu-satunya batasan nyata pada MongoDB 2.6 setidaknya, tetapi sebaliknya dapat diganti dengan hanya "melepas gulungan" setiap array secara individual dan kemudian mengelompokkan kembali pada _id sudah ada untuk menghitung entri array di setiap set. Ini adalah proses dasar, tetapi seperti yang seharusnya Anda lihat $size operator adalah opsi yang lebih baik di sini untuk kinerja keseluruhan.

Sebagai catatan terakhir, data kesimpulan Anda sedikit salah, karena mungkin entri dengan "ddd" di "dari" dimaksudkan juga sama di "ke", tetapi malah dicatat sebagai "bbb". Ini mengubah hitungan berbeda dari pengelompokan "uid" ketiga untuk "ke" turun satu entri. Tapi tentu saja hasil logis yang diberikan sumber datanya bagus:

{ "_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 Sumbernya juga salah ketik dengan pembatas yang disisipkan dengan : alih-alih koma tepat setelah stempel waktu di semua baris.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Kondisi pertandingan dan tanggal terbaru dari array

  2. filter admin django dan mongodb:Tertangkap DatabaseError saat merender:Kueri ini tidak didukung oleh database

  3. MongoDB menghitung dokumen untuk setiap elemen array

  4. Mongodb menemukan kueri dengan $dekat dan koordinat tidak berfungsi

  5. Saat menyimpan koleksi, MongoDB membuat nama Indeks yang terlalu panjang dan melebihi batas 127 byte. Bagaimana memecahkan ini. dapatkah saya menonaktifkan pengindeksan?