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

Temukan Hitungan dari semua Interval yang Tumpang Tindih

Seperti yang Anda sebutkan dengan benar, ada berbagai pendekatan dengan berbagai kompleksitas yang melekat pada pelaksanaannya. Ini pada dasarnya mencakup bagaimana hal itu dilakukan dan mana yang Anda terapkan sebenarnya bergantung pada data dan kasus penggunaan mana yang paling cocok untuk Anda.

Pencocokan Rentang Saat Ini

MongoDB 3.6 $lookup

Pendekatan paling sederhana dapat digunakan menggunakan sintaks baru $lookup operator dengan MongoDB 3.6 yang memungkinkan pipa untuk diberikan sebagai ungkapan untuk "menggabungkan diri" dengan koleksi yang sama. Ini pada dasarnya dapat menanyakan koleksi lagi untuk item apa pun di mana starttime "atau" waktu akhir dari dokumen saat ini berada di antara nilai yang sama dari dokumen lain, tidak termasuk yang asli tentu saja:

db.getCollection('collection').aggregate([
  { "$lookup": {
    "from": "collection",
    "let": {
      "_id": "$_id",
      "starttime": "$starttime",
      "endtime": "$endtime"
    },
    "pipeline": [
      { "$match": {
        "$expr": {
          "$and": [
            { "$ne": [ "$$_id", "$_id" },
            { "$or": [
              { "$and": [
                { "$gte": [ "$$starttime", "$starttime" ] },
                { "$lte": [ "$$starttime", "$endtime" ] }
              ]},
              { "$and": [
                { "$gte": [ "$$endtime", "$starttime" ] },
                { "$lte": [ "$$endtime", "$endtime" ] }
              ]}
            ]},
          ]
        },
        "as": "overlaps"
      }},
      { "$count": "count" },
    ]
  }},
  { "$match": { "overlaps.0": { "$exists": true }  } }
])

Single $lookup melakukan "gabung" pada koleksi yang sama yang memungkinkan Anda menyimpan nilai "dokumen saat ini" untuk "_id" , "waktu mulai" dan "waktu akhir" nilai masing-masing melalui "let" pilihan tahap pipa. Ini akan tersedia sebagai "variabel lokal" menggunakan $$ awalan di "pipeline" berikutnya ekspresi.

Dalam "sub-pipeline" ini, Anda menggunakan $match tahap pipeline dan $expr operator kueri, yang memungkinkan Anda mengevaluasi ekspresi logika kerangka agregasi sebagai bagian dari kondisi kueri. Ini memungkinkan perbandingan antar nilai saat memilih dokumen baru yang cocok dengan kondisi.

Syaratnya cukup cari "dokumen yang sudah diproses" dimana "_id" bidang tidak sama dengan "dokumen saat ini", $and di mana "starttime" $or "waktu akhir" nilai "dokumen saat ini" berada di antara properti yang sama dari "dokumen yang diproses". Perhatikan di sini bahwa ini serta masing-masing $gte dan $lte operator adalah "operator perbandingan agregasi" dan bukan "operator kueri" formulir, karena hasil yang dikembalikan dievaluasi oleh $expr harus boolean dalam konteks. Inilah yang sebenarnya dilakukan oleh operator perbandingan agregasi, dan ini juga satu-satunya cara untuk meneruskan nilai untuk perbandingan.

Karena kami hanya menginginkan "hitungan" kecocokan, $hitung tahap pipa digunakan untuk melakukan ini. Hasil keseluruhan $lookup akan menjadi larik "elemen tunggal" di mana ada hitungan, atau "array kosong" di mana tidak ada kecocokan dengan ketentuan.

Kasus alternatifnya adalah "menghilangkan" $count panggung dan cukup izinkan dokumen yang cocok untuk kembali. Ini memungkinkan identifikasi yang mudah, tetapi sebagai "array yang disematkan di dalam dokumen" Anda perlu memperhatikan jumlah "tumpang tindih" yang akan dikembalikan sebagai keseluruhan dokumen dan ini tidak menyebabkan pelanggaran batas BSON 16MB. Dalam kebanyakan kasus ini seharusnya baik-baik saja, tetapi untuk kasus di mana Anda mengharapkan banyak tumpang tindih untuk dokumen tertentu, ini bisa menjadi kasus nyata. Jadi ini benar-benar sesuatu yang lebih untuk diperhatikan.

$lookup tahap pipa dalam konteks ini akan "selalu" mengembalikan array dalam hasil, bahkan jika kosong. Nama properti keluaran "penggabungan" ke dalam dokumen yang ada akan menjadi "overlaps" sebagaimana ditentukan dalam "sebagai" properti ke $lookup panggung.

Mengikuti $lookup , kita kemudian dapat melakukan $match sederhana dengan ekspresi kueri reguler menggunakan $exists tes untuk 0 nilai indeks larik keluaran. Di mana sebenarnya ada beberapa konten dalam larik dan oleh karena itu "tumpang tindih" kondisinya akan benar dan dokumen dikembalikan, menunjukkan jumlah atau dokumen "tumpang tindih" sesuai pilihan Anda.

Versi lain - Kueri untuk "bergabung"

Kasus alternatif di mana MongoDB Anda tidak memiliki dukungan ini adalah untuk "bergabung" secara manual dengan mengeluarkan kondisi kueri yang sama yang diuraikan di atas untuk setiap dokumen yang diperiksa:

db.getCollection('collection').find().map( d => {
  var overlaps = db.getCollection('collection').find({
    "_id": { "$ne": d._id },
    "$or": [
      { "starttime": { "$gte": d.starttime, "$lte": d.endtime } },
      { "endtime": { "$gte": d.starttime, "$lte": d.endtime } }
    ]
  }).toArray();

  return ( overlaps.length !== 0 ) 
    ? Object.assign(
        d,
        {
          "overlaps": {
            "count": overlaps.length,
            "documents": overlaps
          }
        }
      )
    : null;
}).filter(e => e != null);

Ini pada dasarnya adalah logika yang sama kecuali kita sebenarnya perlu "kembali ke database" untuk mengeluarkan kueri agar sesuai dengan dokumen yang tumpang tindih. Kali ini "operator kueri" yang digunakan untuk menemukan di mana nilai dokumen saat ini berada di antara nilai dokumen yang diproses.

Karena hasilnya sudah dikembalikan dari server, tidak ada batasan batasan BSON untuk menambahkan konten ke output. Anda mungkin memiliki batasan memori, tapi itu masalah lain. Sederhananya kita mengembalikan array daripada kursor melalui .toArray() jadi kami memiliki dokumen yang cocok dan cukup mengakses panjang array untuk mendapatkan hitungan. Jika Anda tidak benar-benar membutuhkan dokumen, gunakan .count() bukannya .find() jauh lebih efisien karena tidak ada dokumen yang mengambil overhead.

Outputnya kemudian digabungkan dengan dokumen yang ada, di mana perbedaan penting lainnya adalah karena tesis adalah "beberapa kueri", tidak ada cara untuk memberikan kondisi bahwa mereka harus "cocok" dengan sesuatu. Jadi ini membuat kita mempertimbangkan akan ada hasil di mana hitungan ( atau panjang array ) adalah 0 dan yang bisa kita lakukan saat ini adalah mengembalikan null nilai yang nantinya bisa kita .filter() dari larik hasil. Metode lain untuk mengulangi kursor menggunakan prinsip dasar yang sama yaitu "membuang" hasil di mana kita tidak menginginkannya. Tapi tidak ada yang menghentikan kueri yang dijalankan di server dan pemfilteran ini adalah "pemrosesan pasca" dalam beberapa bentuk atau lainnya.

Mengurangi Kompleksitas

Jadi pendekatan di atas bekerja dengan struktur seperti yang dijelaskan, tetapi tentu saja kompleksitas keseluruhan mengharuskan untuk setiap dokumen Anda pada dasarnya harus memeriksa setiap dokumen lain dalam koleksi untuk mencari tumpang tindih. Oleh karena itu saat menggunakan $lookup memungkinkan untuk beberapa "efisiensi" dalam pengurangan overhead transportasi dan respons, masih mengalami masalah yang sama bahwa pada dasarnya Anda masih membandingkan setiap dokumen dengan semuanya.

Solusi yang lebih baik "di mana Anda dapat membuatnya sesuai" adalah sebagai gantinya menyimpan perwakilan "nilai keras"* dari interval pada setiap dokumen. Misalnya kita dapat "menganggap" bahwa ada periode "pemesanan" yang solid selama satu jam dalam sehari dengan total 24 periode pemesanan. Ini "bisa" direpresentasikan seperti:

{ "_id": "A", "booking": [ 10, 11, 12 ] }
{ "_id": "B", "booking": [ 12, 13, 14 ] }
{ "_id": "C", "booking": [ 7, 8 ] }
{ "_id": "D", "booking": [ 9, 10, 11 ] }

Dengan data yang diatur seperti itu di mana ada indikator yang ditetapkan untuk interval, kompleksitasnya sangat berkurang karena itu benar-benar hanya masalah "pengelompokan" pada nilai interval dari array dalam "booking" properti:

db.booking.aggregate([
  { "$unwind": "$booking" },
  { "$group": { "_id": "$booking", "docs": { "$push": "$_id" } } },
  { "$match": { "docs.1": { "$exists": true } } }
])

Dan hasilnya:

{ "_id" : 10, "docs" : [ "A", "D" ] }
{ "_id" : 11, "docs" : [ "A", "D" ] }
{ "_id" : 12, "docs" : [ "A", "B" ] }

Itu dengan benar mengidentifikasi itu untuk 10 dan 11 interval keduanya "A" dan "D" mengandung tumpang tindih, sementara "B" dan "A" tumpang tindih pada 12 . Interval dan pencocokan dokumen lainnya dikecualikan melalui $exists tes kecuali kali ini pada 1 index ( atau elemen array kedua yang ada ) untuk melihat bahwa ada "lebih dari satu" dokumen dalam pengelompokan, sehingga menunjukkan tumpang tindih.

Ini hanya menggunakan $unwind tahap pipa agregasi untuk "mendekonstruksi/mendenormalisasi" konten array sehingga kami dapat mengakses nilai dalam untuk pengelompokan. Inilah yang terjadi di $group tahap di mana "kunci" yang diberikan adalah id interval pemesanan dan $push operator digunakan untuk "mengumpulkan" data tentang dokumen saat ini yang ditemukan di grup itu. $match adalah seperti yang dijelaskan sebelumnya.

Ini bahkan dapat diperluas untuk presentasi alternatif:

db.booking.aggregate([
  { "$unwind": "$booking" },
  { "$group": { "_id": "$booking", "docs": { "$push": "$_id" } } },
  { "$match": { "docs.1": { "$exists": true } } },
  { "$unwind": "$docs" },
  { "$group": {
    "_id": "$docs",
    "intervals": { "$push": "$_id" }  
  }}
])

Dengan keluaran:

{ "_id" : "B", "intervals" : [ 12 ] }
{ "_id" : "D", "intervals" : [ 10, 11 ] }
{ "_id" : "A", "intervals" : [ 10, 11, 12 ] }

Ini adalah demonstrasi yang disederhanakan, tetapi jika data yang Anda miliki memungkinkannya untuk jenis analisis yang diperlukan, maka ini adalah pendekatan yang jauh lebih efisien. Jadi, jika Anda dapat mempertahankan "perincian" untuk diperbaiki ke interval "setel" yang dapat direkam secara umum pada setiap dokumen, maka analisis dan pelaporan dapat menggunakan pendekatan terakhir untuk mengidentifikasi tumpang tindih tersebut dengan cepat dan efisien.

Pada dasarnya, ini adalah bagaimana Anda akan mengimplementasikan apa yang pada dasarnya Anda sebutkan sebagai pendekatan "lebih baik", dan yang pertama adalah peningkatan "sedikit" dari apa yang Anda teorikan semula. Lihat mana yang benar-benar sesuai dengan situasi Anda, tetapi ini harus menjelaskan implementasi dan perbedaannya.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Pengecualian upsert MongoDb bidang BSON tidak valid

  2. Mongodb menghemat satu hari lebih sedikit - Masalah Zona Waktu

  3. pembaruan kueri mongodb pilih bidang bersarang

  4. MongoDB - haruskah dokumen pengguna saya menyimpan daftar id proyek?

  5. cara mengimplementasikan fungsi seperti gabung kiri mysql di luwak