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

Kelompokkan dan hitung pada rentang awal dan akhir

Algoritme untuk ini pada dasarnya adalah "mengulangi" nilai antara interval dua nilai. MongoDB memiliki beberapa cara untuk mengatasi hal ini, yang selalu ada dengan mapReduce() dan dengan fitur baru yang tersedia untuk aggregate() metode.

Saya akan memperluas pilihan Anda untuk dengan sengaja menunjukkan bulan yang tumpang tindih karena contoh Anda tidak memilikinya. Ini akan menghasilkan nilai "HGV" yang muncul dalam "tiga" bulan keluaran.

{
        "_id" : 1,
        "startDate" : ISODate("2017-01-01T00:00:00Z"),
        "endDate" : ISODate("2017-02-25T00:00:00Z"),
        "type" : "CAR"
}
{
        "_id" : 2,
        "startDate" : ISODate("2017-02-17T00:00:00Z"),
        "endDate" : ISODate("2017-03-22T00:00:00Z"),
        "type" : "HGV"
}
{
        "_id" : 3,
        "startDate" : ISODate("2017-02-17T00:00:00Z"),
        "endDate" : ISODate("2017-04-22T00:00:00Z"),
        "type" : "HGV"
}

Agregat - Memerlukan MongoDB 3.4

db.cars.aggregate([
  { "$addFields": {
    "range": {
      "$reduce": {
        "input": { "$map": {
          "input": { "$range": [ 
            { "$trunc": { 
              "$divide": [ 
                { "$subtract": [ "$startDate", new Date(0) ] },
                1000
              ]
            }},
            { "$trunc": {
              "$divide": [
                { "$subtract": [ "$endDate", new Date(0) ] },
                1000
              ]
            }},
            60 * 60 * 24
          ]},
          "as": "el",
          "in": {
            "$let": {
              "vars": {
                "date": {
                  "$add": [ 
                    { "$multiply": [ "$$el", 1000 ] },
                    new Date(0)
                  ]
                },
                "month": {
                }
              },
              "in": {
                "$add": [
                  { "$multiply": [ { "$year": "$$date" }, 100 ] },
                  { "$month": "$$date" }
                ]
              }
            }
          }
        }},
        "initialValue": [],
        "in": {
          "$cond": {
            "if": { "$in": [ "$$this", "$$value" ] },
            "then": "$$value",
            "else": { "$concatArrays": [ "$$value", ["$$this"] ] }
          }
        }
      }
    }
  }},
  { "$unwind": "$range" },
  { "$group": {
    "_id": {
      "type": "$type",
      "month": "$range"
    },
    "count": { "$sum": 1 }
  }},
  { "$sort": { "_id": 1 } },
  { "$group": {
    "_id": "$_id.type",
    "monthCounts": { 
      "$push": { "month": "$_id.month", "count": "$count" }
    }
  }}
])

Kunci untuk membuat ini berfungsi adalah $range operator yang mengambil nilai untuk "mulai" dan dan "akhir" serta "interval" untuk diterapkan. Hasilnya adalah array nilai yang diambil dari "awal" dan ditambah hingga "akhir" tercapai.

Kami menggunakan ini dengan startDate dan endDate untuk menghasilkan kemungkinan tanggal di antara nilai-nilai tersebut. Anda akan mencatat bahwa kita perlu melakukan beberapa perhitungan di sini karena $range hanya membutuhkan bilangan bulat 32-bit, tetapi kita dapat mengambil milidetik dari nilai stempel waktu sehingga tidak masalah.

Karena kami menginginkan "bulan", operasi yang diterapkan mengekstrak nilai bulan dan tahun dari rentang yang dihasilkan. Kami benar-benar menghasilkan rentang karena "hari" di antaranya karena "bulan" sulit untuk ditangani dalam matematika. $reduce berikutnya operasi hanya membutuhkan "bulan yang berbeda" dari rentang tanggal.

Oleh karena itu, hasil dari tahap pipa agregasi pertama adalah bidang baru dalam dokumen yang merupakan "array" dari semua bulan berbeda yang tercakup antara startDate dan endDate . Ini memberikan "iterator" untuk sisa operasi.

Dengan "iterator" maksud saya daripada ketika kita menerapkan $unwind kami mendapatkan salinan dokumen asli untuk setiap bulan berbeda yang tercakup dalam interval tersebut. Ini kemudian memungkinkan dua $group berikut tahapan untuk terlebih dahulu menerapkan pengelompokan ke kunci umum "bulan" dan "ketik" untuk "total" penghitungan melalui $sum , dan berikutnya $group membuat kunci hanya "ketik" dan menempatkan hasilnya dalam array melalui $push .

Ini memberikan hasil pada data di atas:

{
        "_id" : "HGV",
        "monthCounts" : [
                {
                        "month" : 201702,
                        "count" : 2
                },
                {
                        "month" : 201703,
                        "count" : 2
                },
                {
                        "month" : 201704,
                        "count" : 1
                }
        ]
}
{
        "_id" : "CAR",
        "monthCounts" : [
                {
                        "month" : 201701,
                        "count" : 1
                },
                {
                        "month" : 201702,
                        "count" : 1
                }
        ]
}

Perhatikan bahwa cakupan "bulan" hanya ada jika ada data aktual. Meskipun mungkin untuk menghasilkan nilai nol pada suatu rentang, hal itu memerlukan sedikit perselisihan untuk melakukannya dan sangat tidak praktis. Jika Anda menginginkan nilai nol maka lebih baik menambahkannya dalam pemrosesan pos di klien setelah hasilnya diambil.

Jika Anda benar-benar ingin menetapkan nilai nol, maka Anda harus secara terpisah meminta $min dan $max nilai, dan meneruskannya ke "brute force" pipeline untuk menghasilkan salinan untuk setiap nilai rentang yang mungkin diberikan.

Jadi kali ini "rentang" dibuat secara eksternal untuk semua dokumen, dan Anda kemudian menggunakan $cond pernyataan ke akumulator untuk melihat apakah data saat ini berada dalam kisaran kelompok yang dihasilkan. Juga karena pembuatannya adalah "eksternal", kami benar-benar tidak memerlukan operator MongoDB 3.4 dari $range , jadi ini juga dapat diterapkan ke versi sebelumnya:

// Get min and max separately 
var ranges = db.cars.aggregate(
 { "$group": {
   "_id": null,
   "startRange": { "$min": "$startDate" },
   "endRange": { "$max": "$endDate" }
 }}
).toArray()[0]

// Make the range array externally from all possible values
var range = [];
for ( var d = new Date(ranges.startRange.valueOf()); d <= ranges.endRange; d.setUTCMonth(d.getUTCMonth()+1)) {
  var v = ( d.getUTCFullYear() * 100 ) + d.getUTCMonth()+1;
  range.push(v);
}

// Run conditional aggregation
db.cars.aggregate([
  { "$addFields": { "range": range } },
  { "$unwind": "$range" },
  { "$group": {
    "_id": {
      "type": "$type",
      "month": "$range"
    },
    "count": { 
      "$sum": {
        "$cond": {
          "if": {
            "$and": [
              { "$gte": [
                "$range",
                { "$add": [
                  { "$multiply": [ { "$year": "$startDate" }, 100 ] },
                  { "$month": "$startDate" }
                ]}
              ]},
              { "$lte": [
                "$range",
                { "$add": [
                  { "$multiply": [ { "$year": "$endDate" }, 100 ] },
                  { "$month": "$endDate" }
                ]}
              ]}
            ]
          },
          "then": 1,
          "else": 0
        }
      }
    }
  }},
  { "$sort": { "_id": 1 } },
  { "$group": {
    "_id": "$_id.type",
    "monthCounts": { 
      "$push": { "month": "$_id.month", "count": "$count" }
    }
  }}
])

Yang menghasilkan pengisian nol yang konsisten untuk semua kemungkinan bulan di semua pengelompokan:

{
        "_id" : "HGV",
        "monthCounts" : [
                {
                        "month" : 201701,
                        "count" : 0
                },
                {
                        "month" : 201702,
                        "count" : 2
                },
                {
                        "month" : 201703,
                        "count" : 2
                },
                {
                        "month" : 201704,
                        "count" : 1
                }
        ]
}
{
        "_id" : "CAR",
        "monthCounts" : [
                {
                        "month" : 201701,
                        "count" : 1
                },
                {
                        "month" : 201702,
                        "count" : 1
                },
                {
                        "month" : 201703,
                        "count" : 0
                },
                {
                        "month" : 201704,
                        "count" : 0
                }
        ]
}

MapReduce

Semua versi MongoDB mendukung mapReduce, dan kasus sederhana "iterator" seperti yang disebutkan di atas ditangani oleh for lingkaran di mapper. Kita bisa mendapatkan output seperti yang dihasilkan hingga $group pertama dari atas hanya dengan melakukan:

db.cars.mapReduce(
  function () {
    for ( var d = this.startDate; d <= this.endDate;
      d.setUTCMonth(d.getUTCMonth()+1) )
    { 
      var m = new Date(0);
      m.setUTCFullYear(d.getUTCFullYear());
      m.setUTCMonth(d.getUTCMonth());
      emit({ id: this.type, date: m},1);
    }
  },
  function(key,values) {
    return Array.sum(values);
  },
  { "out": { "inline": 1 } }
)

Yang menghasilkan:

{
        "_id" : {
                "id" : "CAR",
                "date" : ISODate("2017-01-01T00:00:00Z")
        },
        "value" : 1
},
{
        "_id" : {
                "id" : "CAR",
                "date" : ISODate("2017-02-01T00:00:00Z")
        },
        "value" : 1
},
{
        "_id" : {
                "id" : "HGV",
                "date" : ISODate("2017-02-01T00:00:00Z")
        },
        "value" : 2
},
{
        "_id" : {
                "id" : "HGV",
                "date" : ISODate("2017-03-01T00:00:00Z")
        },
        "value" : 2
},
{
        "_id" : {
                "id" : "HGV",
                "date" : ISODate("2017-04-01T00:00:00Z")
        },
        "value" : 1
}

Jadi itu tidak memiliki pengelompokan kedua untuk digabungkan ke array, tetapi kami menghasilkan output agregat dasar yang sama.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Bagaimana cara menghapus duplikat berdasarkan kunci di Mongodb?

  2. MongoDB:Mapreduce:kurangi-> banyak belum didukung

  3. MongoDB - Bagaimana dengan jenis nilai Desimal?

  4. Cara mencetak hasil minimum di MongoDB

  5. Tambahkan susunan default ke koleksi mongodb yang ada