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

Filter agregasi setelah $lookup

Pertanyaan di sini sebenarnya tentang sesuatu yang berbeda dan tidak perlu $lookup sama sekali. Tetapi bagi siapa pun yang datang ke sini murni dari judul "filtering after $lookup" maka ini adalah tekniknya untuk Anda:

MongoDB 3.6 - Sub-pipa

db.test.aggregate([
    { "$match": { "id": 100 } },
    { "$lookup": {
      "from": "test",
      "let": { "id": "$id" },
      "pipeline": [
        { "$match": {
          "value": "1",
          "$expr": { "$in": [ "$$id", "$contain" ] }
        }}
      ],
      "as": "childs"
    }}
])

Sebelumnya - $lookup + $unwind + $match coalescence

db.test.aggregate([
    { "$match": { "id": 100 } },
    { "$lookup": {
        "from": "test",
        "localField": "id",
        "foreignField": "contain",
        "as": "childs"
    }},
    { "$unwind": "$childs" },
    { "$match": { "childs.value": "1" } },
    { "$group": {
        "_id": "$_id",
        "id": { "$first": "$id" },
        "value": { "$first": "$value" },
        "contain": { "$first": "$contain" },
        "childs": { "$push": "$childs" }
     }}
])

Jika Anda mempertanyakan mengapa Anda $unwind dibandingkan dengan menggunakan $filter pada larik, lalu baca Agregat $lookup Ukuran total dokumen dalam saluran yang cocok melebihi ukuran dokumen maksimum untuk semua detail tentang mengapa hal ini umumnya diperlukan dan jauh lebih optimal.

Untuk rilis MongoDB 3.6 dan seterusnya, maka "sub-pipeline" yang lebih ekspresif umumnya adalah apa yang Anda ingin "filter" hasil koleksi asing sebelum apa pun dikembalikan ke dalam array sama sekali.

Kembali ke jawaban yang sebenarnya menjelaskan mengapa pertanyaan yang diajukan perlu "tidak bergabung" sama sekali....

Asli

Menggunakan $lookup seperti ini bukan cara yang paling "efisien" untuk melakukan apa yang Anda inginkan di sini. Tapi lebih lanjut tentang ini nanti.

Sebagai konsep dasar, cukup gunakan $filter pada larik yang dihasilkan:

db.test.aggregate([ 
    { "$match": { "id": 100 } }, 
    { "$lookup": {
        "from": "test",
        "localField": "id",
        "foreignField": "contain",
        "as": "childs"
    }},
    { "$project": {
        "id": 1,
        "value": 1,
        "contain": 1,
        "childs": {
           "$filter": {
               "input": "$childs",
               "as": "child",
               "cond": { "$eq": [ "$$child.value", "1" ] }
           }
        }
    }}
]);

Atau gunakan $redact sebagai gantinya:

db.test.aggregate([ 
    { "$match": { "id": 100 } }, 
    { "$lookup": {
        "from": "test",
        "localField": "id",
        "foreignField": "contain",
        "as": "childs"
    }},
    { "$redact": {
        "$cond": {
           "if": {
              "$or": [
                { "$eq": [ "$value", "0" ] },
                { "$eq": [ "$value", "1" ] }
              ]
           },
           "then": "$$DESCEND",
           "else": "$$PRUNE"
        }
    }}
]);

Keduanya mendapatkan hasil yang sama:

{  
  "_id":ObjectId("570557d4094a4514fc1291d6"),
  "id":100,
  "value":"0",
  "contain":[ ],
  "childs":[ {  
      "_id":ObjectId("570557d4094a4514fc1291d7"),
      "id":110,
      "value":"1",
      "contain":[ 100 ]
    },
    {  
      "_id":ObjectId("570557d4094a4514fc1291d8"),
      "id":120,
      "value":"1",
      "contain":[ 100 ]
    }
  ]
}

Intinya adalah $lookup sendiri tidak dapat "belum" meminta untuk hanya memilih data tertentu. Jadi semua "pemfilteran" perlu dilakukan setelah $lookup

Tapi sebenarnya untuk jenis "self join" ini sebaiknya tidak menggunakan $lookup sama sekali dan menghindari overhead dari pembacaan tambahan dan "gabungan hash" sepenuhnya. Ambil saja item terkait dan $group sebagai gantinya:

db.test.aggregate([
  { "$match": { 
    "$or": [
      { "id": 100 },
      { "contain.0": 100, "value": "1" }
    ]
  }},
  { "$group": {
    "_id": {
      "$cond": {
        "if": { "$eq": [ "$value", "0" ] },
        "then": "$id",
        "else": { "$arrayElemAt": [ "$contain", 0 ] }
      }
    },
    "value": { "$first": { "$literal": "0"} },
    "childs": {
      "$push": {
        "$cond": {
          "if": { "$ne": [ "$value", "0" ] },
          "then": "$$ROOT",
          "else": null
        }
      }
    }
  }},
  { "$project": {
    "value": 1,
    "childs": {
      "$filter": {
        "input": "$childs",
        "as": "child",
        "cond": { "$ne": [ "$$child", null ] }
      }
    }
  }}
])

Yang hanya keluar sedikit berbeda karena saya sengaja menghapus bidang asing. Tambahkan sendiri jika Anda benar-benar ingin:

{
  "_id" : 100,
  "value" : "0",
  "childs" : [
    {
      "_id" : ObjectId("570557d4094a4514fc1291d7"),
      "id" : 110,
      "value" : "1",
      "contain" : [ 100 ]
    },
    {
      "_id" : ObjectId("570557d4094a4514fc1291d8"),
      "id" : 120,
      "value" : "1",
      "contain" : [ 100 ]
    }
  ]
}

Jadi satu-satunya masalah sebenarnya di sini adalah "memfilter" null apa pun hasil dari larik, dibuat saat dokumen saat ini adalah parent dalam memproses item menjadi $push .

Apa yang juga tampaknya Anda lewatkan di sini adalah bahwa hasil yang Anda cari tidak memerlukan agregasi atau "sub-kueri" sama sekali. Struktur yang telah Anda simpulkan atau mungkin ditemukan di tempat lain "dirancang" sehingga Anda bisa mendapatkan "simpul" dan semua "anak" dalam satu permintaan kueri.

Itu berarti hanya "permintaan" yang benar-benar dibutuhkan, dan pengumpulan data (yang merupakan semua yang terjadi karena tidak ada konten yang benar-benar "dikurangi" ) hanyalah fungsi dari pengulangan hasil kursor:

var result = {};

db.test.find({
  "$or": [
    { "id": 100 },
    { "contain.0": 100, "value": "1" }
  ]
}).sort({ "contain.0": 1 }).forEach(function(doc) {
  if ( doc.id == 100 ) {
    result = doc;
    result.childs = []
  } else {
    result.childs.push(doc)
  }
})

printjson(result);

Ini melakukan hal yang persis sama:

{
  "_id" : ObjectId("570557d4094a4514fc1291d6"),
  "id" : 100,
  "value" : "0",
  "contain" : [ ],
  "childs" : [
    {
      "_id" : ObjectId("570557d4094a4514fc1291d7"),
      "id" : 110,
      "value" : "1",
      "contain" : [
              100
      ]
    },
    {
      "_id" : ObjectId("570557d4094a4514fc1291d8"),
      "id" : 120,
      "value" : "1",
      "contain" : [
              100
      ]
    }
  ]
}

Dan berfungsi sebagai bukti bahwa yang perlu Anda lakukan di sini hanyalah mengeluarkan kueri "tunggal" untuk memilih induk dan anak-anak. Data yang dikembalikan sama saja, dan semua yang Anda lakukan di server atau klien adalah "memijat" ke format lain yang dikumpulkan.

Ini adalah salah satu kasus di mana Anda bisa "terperangkap" dalam memikirkan bagaimana Anda melakukan sesuatu dalam database "relasional", dan tidak menyadari bahwa karena cara data disimpan telah "berubah", Anda tidak perlu lagi menggunakan pendekatan yang sama.

Itulah inti dari contoh dokumentasi "Struktur Pohon Model dengan Referensi Anak" dalam strukturnya, yang memudahkan untuk memilih orang tua dan anak dalam satu kueri.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Menggunakan operator $slice untuk mendapatkan elemen terakhir dari array

  2. Bagaimana saya bisa menambahkan id unik dua kolom ke mongodb di aplikasi meteor?

  3. Dapatkan data dari koleksi b tidak dalam koleksi a dalam kueri shell MongoDB

  4. MongoDB $hariDariMinggu

  5. Cara Menyebarkan Database Open edX MongoDB untuk Ketersediaan Tinggi