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

Kembalikan hanya elemen sub-dokumen yang cocok dalam array bersarang

Jadi kueri yang Anda miliki sebenarnya memilih "dokumen" seperti seharusnya. Tetapi yang Anda cari adalah untuk "memfilter array" yang ada sehingga elemen yang dikembalikan hanya cocok dengan kondisi kueri.

Jawaban sebenarnya tentu saja kecuali jika Anda benar-benar menghemat banyak bandwidth dengan menyaring detail seperti itu, maka Anda tidak boleh mencoba, atau setidaknya melampaui kecocokan posisi pertama.

MongoDB memiliki posisi $ operator yang akan mengembalikan elemen array pada indeks yang cocok dari kondisi kueri. Namun, ini hanya mengembalikan indeks kecocokan "pertama" dari elemen larik paling "luar".

db.getCollection('retailers').find(
    { 'stores.offers.size': 'L'},
    { 'stores.$': 1 }
)

Dalam hal ini, artinya "stores" posisi array saja. Jadi, jika ada beberapa entri "penyimpanan", maka hanya "satu" elemen yang berisi kondisi kecocokan Anda yang akan dikembalikan. Tapi , itu tidak melakukan apa pun untuk larik dalam "offers" , dan dengan demikian setiap "penawaran" dalam "stores" . yang cocok array masih akan dikembalikan.

MongoDB tidak memiliki cara untuk "memfilter" ini dalam kueri standar, jadi yang berikut ini tidak berfungsi:

db.getCollection('retailers').find(
    { 'stores.offers.size': 'L'},
    { 'stores.$.offers.$': 1 }
)

Satu-satunya alat yang sebenarnya dimiliki MongoDB untuk melakukan manipulasi tingkat ini adalah dengan kerangka kerja agregasi. Tetapi analisis akan menunjukkan kepada Anda mengapa Anda "mungkin" tidak boleh melakukan ini, dan sebagai gantinya hanya memfilter array dalam kode.

Dalam urutan bagaimana Anda dapat mencapai ini per versi.

Pertama dengan MongoDB 3.2.x dengan menggunakan $filter operasi:

db.getCollection('retailers').aggregate([
  { "$match": { "stores.offers.size": "L" } },
  { "$project": {
    "stores": {
      "$filter": {
        "input": {
          "$map": {
            "input": "$stores",
            "as": "store",
            "in": {
              "_id": "$$store._id",
              "offers": {
                "$filter": {
                  "input": "$$store.offers",
                  "as": "offer",
                  "cond": {
                    "$setIsSubset":  [ ["L"], "$$offer.size" ]
                  }
                }
              }
            }
          }
        },
        "as": "store",
        "cond": { "$ne": [ "$$store.offers", [] ]}
      }
    }
  }}
])

Kemudian dengan MongoDB 2.6.x dan di atasnya dengan $map dan $setDifference :

db.getCollection('retailers').aggregate([
  { "$match": { "stores.offers.size": "L" } },
  { "$project": {
    "stores": {
      "$setDifference": [
        { "$map": {
          "input": {
            "$map": {
              "input": "$stores",
              "as": "store",
              "in": {
                "_id": "$$store._id",
                "offers": {
                  "$setDifference": [
                    { "$map": {
                      "input": "$$store.offers",
                      "as": "offer",
                      "in": {
                        "$cond": {
                          "if": { "$setIsSubset": [ ["L"], "$$offer.size" ] },
                          "then": "$$offer",
                          "else": false
                        }
                      }
                    }},
                    [false]
                  ]
                }
              }
            }
          },
          "as": "store",
          "in": {
            "$cond": {
              "if": { "$ne": [ "$$store.offers", [] ] },
              "then": "$$store",
              "else": false
            }
          }
        }},
        [false]
      ]
    }
  }}
])

Dan akhirnya dalam versi apa pun di atas MongoDB 2.2.x tempat kerangka agregasi diperkenalkan.

db.getCollection('retailers').aggregate([
  { "$match": { "stores.offers.size": "L" } },
  { "$unwind": "$stores" },
  { "$unwind": "$stores.offers" },
  { "$match": { "stores.offers.size": "L" } },
  { "$group": {
    "_id": {
      "_id": "$_id",
      "storeId": "$stores._id",
    },
    "offers": { "$push": "$stores.offers" }
  }},
  { "$group": {
    "_id": "$_id._id",
    "stores": {
      "$push": {
        "_id": "$_id.storeId",
        "offers": "$offers"
      }
    }
  }}
])

Mari kita uraikan penjelasannya.

MongoDB 3.2.x dan yang lebih baru

Jadi secara umum, $filter adalah cara untuk pergi ke sini karena dirancang dengan tujuan dalam pikiran. Karena ada beberapa level array, Anda perlu menerapkan ini di setiap level. Jadi pertama-tama Anda menyelami setiap "offers" dalam "stores" untuk memeriksa dan $filter konten itu.

Perbandingan sederhana di sini adalah "Apakah "size" array berisi elemen yang saya cari" . Dalam konteks logis ini, hal singkat yang harus dilakukan adalah menggunakan $setIsSubset operasi untuk membandingkan larik ("set") dari ["L"] ke larik sasaran. Dimana kondisi itu true ( mengandung "L" ) lalu elemen larik untuk "offers" dipertahankan dan dikembalikan dalam hasil.

Di level yang lebih tinggi $filter , Anda kemudian mencari untuk melihat apakah hasil dari $filter sebelumnya itu mengembalikan array kosong [] untuk "offers" . Jika tidak kosong, maka elemen dikembalikan atau dihilangkan.

MongoDB 2.6.x

Ini sangat mirip dengan proses modern kecuali karena tidak ada $filter dalam versi ini Anda dapat menggunakan $map untuk memeriksa setiap elemen dan kemudian menggunakan $setDifference untuk memfilter elemen apa pun yang dikembalikan sebagai false .

Jadi $map akan mengembalikan seluruh array, tetapi $cond operasi hanya memutuskan apakah akan mengembalikan elemen atau sebagai gantinya false nilai. Dalam perbandingan $setDifference ke satu elemen "set" dari [false] semua false elemen dalam larik yang dikembalikan akan dihapus.

Dalam semua cara lain, logikanya sama seperti di atas.

MongoDB 2.2.x dan yang lebih baru

Jadi di bawah MongoDB 2.6 satu-satunya alat untuk bekerja dengan array adalah $unwind , dan untuk tujuan ini saja Anda harus tidak gunakan kerangka kerja agregasi "hanya" untuk tujuan ini.

Prosesnya memang tampak sederhana, hanya dengan "membongkar" setiap larik, menyaring hal-hal yang tidak Anda perlukan lalu menyatukannya kembali. Perhatian utama ada di "dua" $group tahap, dengan "pertama" untuk membangun kembali array dalam, dan selanjutnya untuk membangun kembali array luar. Ada _id yang berbeda nilai di semua tingkat, jadi ini hanya perlu disertakan di setiap tingkat pengelompokan.

Tapi masalahnya adalah $unwind sangat mahal . Meskipun masih memiliki tujuan, tujuan penggunaan utamanya bukanlah untuk melakukan pemfilteran semacam ini per dokumen. Faktanya, dalam rilis modern, penggunaan hanya boleh dilakukan ketika elemen array perlu menjadi bagian dari "kunci pengelompokan" itu sendiri.

Kesimpulan

Jadi, ini bukanlah proses yang mudah untuk mendapatkan kecocokan di berbagai level larik seperti ini, dan sebenarnya itu bisa sangat mahal jika diterapkan secara tidak benar.

Hanya dua cantuman modern yang boleh digunakan untuk tujuan ini, karena mereka menggunakan tahap saluran pipa "tunggal" selain "query" $match untuk melakukan "penyaringan". Efek yang dihasilkan sedikit lebih banyak daripada bentuk standar .find() .

Namun secara umum, daftar tersebut masih memiliki sejumlah kompleksitas, dan memang kecuali jika Anda benar-benar secara drastis mengurangi konten yang dikembalikan oleh pemfilteran sedemikian rupa sehingga membuat peningkatan yang signifikan dalam bandwidth yang digunakan antara server dan klien, maka Anda lebih baik memfilter hasil kueri awal dan proyeksi dasar.

db.getCollection('retailers').find(
    { 'stores.offers.size': 'L'},
    { 'stores.$': 1 }
).forEach(function(doc) {
    // Technically this is only "one" store. So omit the projection
    // if you wanted more than "one" match
    doc.stores = doc.stores.filter(function(store) {
        store.offers = store.offers.filter(function(offer) {
            return offer.size.indexOf("L") != -1;
        });
        return store.offers.length != 0;
    });
    printjson(doc);
})

Jadi bekerja dengan pemrosesan kueri "posting" objek yang dikembalikan jauh lebih mudah daripada menggunakan pipa agregasi untuk melakukan ini. Dan seperti yang dinyatakan, satu-satunya perbedaan "nyata" adalah Anda membuang elemen lain di "server" dan bukannya menghapusnya "per dokumen" saat diterima, yang mungkin menghemat sedikit bandwidth.

Tetapi kecuali Anda melakukan ini dalam rilis modern dengan hanya $match dan $project , maka "biaya" pemrosesan di server akan jauh lebih besar daripada "keuntungan" dari pengurangan overhead jaringan tersebut dengan menghapus elemen yang tidak cocok terlebih dahulu.

Dalam semua kasus, Anda mendapatkan hasil yang sama:

{
        "_id" : ObjectId("56f277b1279871c20b8b4567"),
        "stores" : [
                {
                        "_id" : ObjectId("56f277b5279871c20b8b4783"),
                        "offers" : [
                                {
                                        "_id" : ObjectId("56f277b1279871c20b8b4567"),
                                        "size" : [
                                                "S",
                                                "L",
                                                "XL"
                                        ]
                                }
                        ]
                }
        ]
}


  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Atribut dinamis dengan Rails dan Mongoid

  2. CouchDB vs. MongoDB:10 hal yang harus Anda ketahui

  3. 4 Cara Mendaftar Koleksi dalam Basis Data MongoDB

  4. MongoDB $dalam Operator Pipa Agregasi

  5. Mengamankan MongoDB dari Serangan Injeksi Eksternal