Anda perlu "memproyeksikan" kecocokan di sini karena semua yang dilakukan kueri MongoDB adalah mencari "dokumen" yang memiliki "setidaknya satu elemen" itu "lebih besar dari" kondisi yang Anda minta.
Jadi memfilter "array" tidak sama dengan kondisi "query" yang Anda miliki.
"Proyeksi" sederhana hanya akan mengembalikan item yang cocok "pertama" ke kondisi itu. Jadi mungkin bukan itu yang Anda inginkan, tetapi sebagai contoh:
Order.find({ "articles.quantity": { "$gte": 5 } })
.select({ "articles.$": 1 })
.populate({
"path": "articles.article",
"match": { "price": { "$lte": 500 } }
}).exec(function(err,orders) {
// populated and filtered twice
}
)
"Semacam" itu melakukan apa yang Anda inginkan, tetapi masalahnya adalah hanya akan mengembalikan paling banyak satu elemen dalam "articles"
larik.
Untuk melakukan ini dengan benar, Anda memerlukan .aggregate()
untuk menyaring konten array. Idealnya ini dilakukan dengan MongoDB 3.2 dan $filter
. Tetapi ada juga cara khusus untuk .populate()
di sini:
Order.aggregate(
[
{ "$match": { "artciles.quantity": { "$gte": 5 } } },
{ "$project": {
"orderdate": 1,
"articles": {
"$filter": {
"input": "$articles",
"as": "article",
"cond": {
"$gte": [ "$$article.quantity", 5 ]
}
}
},
"__v": 1
}}
],
function(err,orders) {
Order.populate(
orders.map(function(order) { return new Order(order) }),
{
"path": "articles.article",
"match": { "price": { "$lte": 500 } }
},
function(err,orders) {
// now it's all populated and mongoose documents
}
)
}
)
Jadi yang terjadi di sini adalah "pemfilteran" sebenarnya dari array yang terjadi di dalam .aggregate()
pernyataan, tetapi tentu saja hasil dari ini bukan lagi "dokumen luwak" karena salah satu aspek dari .aggregate()
adalah bahwa ia dapat "mengubah" struktur dokumen, dan untuk alasan ini luwak "menganggap" itu masalahnya dan hanya mengembalikan "objek biasa".
Itu sebenarnya bukan masalah, karena ketika Anda melihat $project
tahap, kami sebenarnya meminta semua bidang yang sama yang ada dalam dokumen sesuai dengan skema yang ditentukan. Jadi meskipun itu hanya "objek biasa", tidak ada masalah untuk "melemparkannya" kembali ke dokumen luwak.
Di sinilah .map()
masuk, karena mengembalikan larik "dokumen" yang dikonversi, yang kemudian penting untuk tahap berikutnya.
Sekarang Anda memanggil Model.populate()
yang kemudian dapat menjalankan "populasi" lebih lanjut pada "array dokumen luwak".
Hasilnya akhirnya seperti yang Anda inginkan.
MongoDB versi lebih lama dari 3.2.x
Satu-satunya hal yang benar-benar berubah di sini adalah jalur agregasi, Jadi hanya itu yang perlu disertakan untuk singkatnya.
MongoDB 2.6 - Dapat memfilter array dengan kombinasi $map
dan $setDifference
. Hasilnya adalah "set" tetapi itu tidak masalah ketika luwak membuat _id
bidang pada semua larik sub-dokumen secara default:
[
{ "$match": { "artciles.quantity": { "$gte": 5 } } },
{ "$project": {
"orderdate": 1,
"articles": {
"$setDiffernce": [
{ "$map": {
"input": "$articles",
"as": "article",
"in": {
"$cond": [
{ "$gte": [ "$$article.price", 5 ] },
"$$article",
false
]
}
}},
[false]
]
},
"__v": 1
}}
],
Revisi yang lebih lama dari itu harus menggunakan $unwind
:
[
{ "$match": { "artciles.quantity": { "$gte": 5 } }},
{ "$unwind": "$articles" },
{ "$match": { "artciles.quantity": { "$gte": 5 } }},
{ "$group": {
"_id": "$_id",
"orderdate": { "$first": "$orderdate" },
"articles": { "$push": "$articles" },
"__v": { "$first": "$__v" }
}}
],
Alternatif $lookup
Alternatif lain adalah melakukan semuanya di "server" saja. Ini adalah opsi dengan $lookup
dari MongoDB 3.2 dan yang lebih baru:
Order.aggregate(
[
{ "$match": { "artciles.quantity": { "$gte": 5 } }},
{ "$project": {
"orderdate": 1,
"articles": {
"$filter": {
"input": "$articles",
"as": "article",
"cond": {
"$gte": [ "$$article.quantity", 5 ]
}
}
},
"__v": 1
}},
{ "$unwind": "$articles" },
{ "$lookup": {
"from": "articles",
"localField": "articles.article",
"foreignField": "_id",
"as": "articles.article"
}},
{ "$unwind": "$articles.article" },
{ "$group": {
"_id": "$_id",
"orderdate": { "$first": "$orderdate" },
"articles": { "$push": "$articles" },
"__v": { "$first": "$__v" }
}},
{ "$project": {
"orderdate": 1,
"articles": {
"$filter": {
"input": "$articles",
"as": "article",
"cond": {
"$lte": [ "$$article.article.price", 500 ]
}
}
},
"__v": 1
}}
],
function(err,orders) {
}
)
Dan meskipun itu hanya dokumen biasa, itu hanya hasil yang sama seperti yang akan Anda dapatkan dari .populate()
mendekati. Dan tentu saja Anda selalu dapat pergi dan "melempar" ke dokumen luwak dalam semua kasus lagi jika Anda benar-benar harus.
Jalur "terpendek"
Ini benar-benar kembali ke pernyataan asli di mana Anda pada dasarnya hanya "menerima" bahwa "kueri" tidak dimaksudkan untuk "memfilter" konten array. .populate()
dapat melakukannya dengan senang hati karena itu hanyalah "permintaan" lain dan memasukkan "dokumen" dengan mudah.
Jadi jika Anda benar-benar tidak menyimpan "bucketloads" bandwidth dengan menghapus anggota array tambahan dalam array dokumen asli, maka cukup .filter()
mereka dalam kode pemrosesan pos:
Order.find({ "articles.quantity": { "$gte": 5 } })
.populate({
"path": "articles.article",
"match": { "price": { "$lte": 500 } }
}).exec(function(err,orders) {
orders = orders.filter(function(order) {
order.articles = order.articles.filter(function(article) {
return (
( article.quantity >= 5 ) &&
( article.article != null )
)
});
return order.aricles.length > 0;
})
// orders has non matching entries removed
}
)