当前接受的答案是“错误的”,因为它实际上并没有改变任何东西。 $match 谓词的字段表示的顺序没有区别。我会用你的具体情况来证明这一点,但还有一个额外的复杂性,我们稍后会解决。同时,请考虑以下文档:
{
_id: 1,
status: "OK",
key: 123
}
这个查询:
db.collection.find({
status: "OK",
$expr: {
$eq: [
"$key",
123
]
}
})
而这个查询,它只是将谓词的顺序颠倒了:
db.collection.find({
$expr: {
$eq: [
"$key",
123
]
},
status: "OK"
})
将找到并返回该文档。第一个的操场演示可以在here 找到,第二个是here。
同样,您原来的$match:
{ $match: { status: "OK", $expr: { $eq: ["$$itemType", "book"] } }}
将与接受的答案中的行为相同:
{ $match: { $expr: { $eq: ["$$itemType", "book"] }, status: 'OK' }}
换句话说,基于是否首先使用$expr,行为没有区别。但是,我怀疑整体聚合并未表达您想要的逻辑。让我们进一步探讨一下。首先,我们需要解决这个问题:
$expr 为原始文件设置条件。
这不是真的。根据the documentation for $expr,那个运营商“允许在查询语言中使用聚合表达式。"
此功能的主要用途,实际上是文档中列出的第一个功能,是compare two fields from a single document。在$lookup 的上下文中,这种从原始文档中引用字段的能力允许您将它们的值与您正在加入的集合进行比较。该文档有一些示例,例如here 和该页面上引用$expr 的其他地方。
考虑到这一点,让我们回到你的聚合。如果我理解正确,您使用 { $expr: { $eq: ["$$itemType", "book"] } 谓词的意图是从原来的收藏。那正确吗?
如果是这样,那么这不是您的聚合当前正在做的事情。您可以在 this playground example 中看到 $match 嵌套在 $lookup pipeline 内不是影响原始集合中的文档。相反,您应该通过基于pipeline 的初始$match 进行过滤。所以像this:
db.orders.aggregate([
{
$match: {
$expr: {
$eq: [
"$itemType",
"book"
]
}
}
}
])
或者,更简单地说,this:
db.orders.aggregate([
{
$match: {
"itemType": "book"
}
}
])
基于所有这些,您的最终管道应该类似于以下内容:
db.orders.aggregate([
{
$match: {
"itemType": "book"
}
},
{
$lookup: {
from: "books",
localField: "itemId",
foreignField: "_id",
let: {
"itemType": "$itemType"
},
pipeline: [
{
$match: {
status: "OK"
}
}
],
as: "bookData"
}
}
])
Playground example here。这个管道:
- 按
itemType 过滤原始集合 (orders) 中的数据。从样本数据中,它删除了带有_id: 3 的文档,因为它的itemType 与我们正在寻找的文档("book")不同。
- 它使用
localField/foreignField 语法在books 中查找数据,其中books 文档的_id 与orders 集合中源文档的itemId 匹配.
- 进一步使用
let/pipeline语法表示books文档的status为"OK"的附加条件。这就是为什么带有"BAD" 的status 的books 文档不会被拉入带有_id: 2 的orders 文档的bookData。
(合并的)第二和第三部分的文档是here。