別のスキーマへの参照といくつかの追加データの両方の配列であるプロパティをクエリしようとしています。より明確にするために、ここにスキーマがあります:
var orderSchema = new Schema({
orderDate: Date,
articles: [{
article: {
type: Schema.Types.ObjectId,
ref: 'Article'
},
quantity: 'Number'
}]
}),
Order = mongoose.model('Order', orderSchema);
参照を正常にクエリすることができましたが、つまり:
Order.find({}).populate('articles.article', null, {
price: {
$lte: 500
}
}).exec(function(err, data) {
for (var order of data) {
for (var article of order.articles) {
console.log(article);
}
}
});
quantity
属性のクエリに問題があります。つまり、これは機能しません。
Order.find({}).where({
'articles.quantity': {
$gte: 5
}
}).populate('articles.article', null, {
/*price: {
$lte: 500
}*/
}).exec(function(err, data) {
for (var order of data) {
for (var article of order.articles) {
console.log(article);
}
}
});
quantity
に基づいてクエリを実行することもできますか?もしそうなら、何が最善のアプローチでしょうか?
ありがとうございました!
更新:
問題は、結果が完全な配列であるか、何もないことです(更新された質問を参照)。 5と同じかそれ以上の数のレコードのみを取得したいと思います。あなた(と私の)のアプローチでは、レコードがまったくない($ gte:5001を設定した場合)か、両方のレコード($ gteを設定した場合)を取得します。 5000)
{
"_id": ObjectId('56fe76c12f7174ac5018054f'),
"orderDate": ISODate('2016-04-01T13:25:21.055Z'),
"articles": [
{
"article": ObjectId('56fe76c12f7174ac5018054b'),
"quantity": 5000,
"_id": ObjectId('56fe76c12f7174ac50180551')
},
{
"article": ObjectId('56fe76c12f7174ac5018054c'),
"quantity": 1,
"_id": ObjectId('56fe76c12f7174ac50180552')
}
],
"__v": 1
}
MongoDBクエリが行う「ドキュメント」は「少なくとも1つの要素」、つまりである「ドキュメント」を探すため、ここで一致を「投影」する必要があります。 )「より大きい」要求した状態。
したがって、「配列」のフィルタリングは、「クエリ」条件と同じではありません。
単純な「投影」では、「最初に」一致したアイテムをその条件に戻すだけです。したがって、おそらくあなたが望むものではありませんが、例として:
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
}
)
その「並べ替え」はあなたが望むことを行いますが、問題は実際には"articles"
配列内の多くてもone要素しか返さないことです。
これを適切に行うには、.aggregate()
で配列の内容をフィルタリングする必要があります。理想的には、これはMongoDB 3.2と $filter
で行われます。しかし、ここにも.populate()
への特別な方法があります:
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
}
)
}
)
したがって、ここで行われるのは、配列の実際の「フィルタリング」が.aggregate()
ステートメント内で行われることですが、もちろん、.aggregate()
の1つの側面は、ドキュメント構造を「変更」できるため、マングースはそれを「想定」し、単に「プレーンオブジェクト」を返します。
$project
ステージが表示されたとき、実際には、定義されたスキーマに従ってドキュメントに存在する同じフィールドすべてを要求しているので、それは実際には問題ではありません。したがって、単なる「プレーンオブジェクト」であっても、マングースドキュメントに「キャスト」しても問題はありません。
これが.map()
の出番です。変換された「ドキュメント」の配列を返すため、次のステージで重要になります。
ここで Model.populate()
を呼び出すと、「マングースドキュメントの配列」でさらに「母集団」を実行できます。
結果は最終的にあなたが望むものです。
ここで実際に変化するのは集約パイプラインだけなので、簡潔にするために含める必要があるのはそれだけです。
MongoDB 2.6- $map
と $setDifference
の組み合わせで配列をフィルタリングできます。結果は「セット」ですが、mongooseがデフォルトですべてのサブドキュメント配列に_id
フィールドを作成する場合は問題ありません。
[
{ "$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
}}
],
それより古いリビジョンでは $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" }
}}
],
別の方法は、代わりに「サーバー」ですべてを実行することです。これは、MongoDB 3.2以降の $lookup
を使用するオプションです。
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) {
}
)
これらは単なるプレーンドキュメントですが、.populate()
アプローチから得られる結果と同じです。もちろん、本当に必要な場合は、いつでも、いつでもマングースドキュメントに「キャスト」して再びマングースドキュメントにキャストできます。
これは本当に元のステートメントに戻り、基本的には、「クエリ」は配列のコンテンツを「フィルタリング」することを目的としていないことを「受け入れる」だけです。 .populate()
は幸いにもそうすることができます。これは単なる「クエリ」であり、便宜上「ドキュメント」に詰め込まれているためです。
したがって、元のドキュメント配列から追加の配列メンバーを削除して、帯域幅の「バケットロード」を本当に保存していない場合は、後処理コードでそれらを.filter()
にしてください。
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
}
)