【问题标题】:Using conditions for both collections (original and foreign) in lookup $match在查找 $match 中使用两个集合(原始和外国)的条件
【发布时间】:2022-11-10 22:55:16
【问题描述】:

我不确定这是一个真正的问题还是只是缺乏文件。 您可以在查找$match 中为外国集合中的文档添加条件。 您还可以在 $match$expr 中查找原始集合文档的条件。

但是当我想同时使用这两个功能时,它就不起作用了。这是聚合中的示例查找

  { $lookup:
    {
      from: 'books',
      localField: 'itemId',
      foreignField: '_id',
      let: { "itemType": "$itemType" },
      pipeline: [
         { $match: { $expr: { $eq: ["$$itemType", "book"] } }}
      ],
      as: 'bookData'
    }
  }

$expr 正在为原始文件设置条件。但是,如果我只想获取带有 status: 'OK' 的外国文件怎么办?就像是:

{ $match: { status: "OK", $expr: { $eq: ["$$itemType", "book"] } }}

不工作。

【问题讨论】:

  • 错误信息到底是什么?您可以尝试将这两个条件包装在 $and 运算符中吗?
  • @FabianStrathaus 抱歉,这是我的错误。它不会给出错误。只是不工作。我试过 $and,还是一样。发生的情况是,在将status: "OK" 添加到$match 之后,$expr 似乎不再起作用。
  • 如果$match 对象包含其他查询条件,那么您的$expr 条件是否会被忽略?您可以过去示例文档和此查询的预期输出吗?

标签: mongodb mongoose


【解决方案1】:

我试着玩你提供的情况。 尝试将$expr 作为$match 对象的第一个键。它应该做的事情。

   { $lookup:
    {
      from: 'books',
      localField: 'itemId',
      foreignField: '_id',
      let: { "itemType": "$itemType" },
      pipeline: [
         { $match: { $expr: { $eq: ["$$itemType", "book"] }, status: 'OK' }}
      ],
      as: 'bookData'
    }
  }

【讨论】:

  • 这很奇怪,但确实有效!谢谢!
【解决方案2】:

当前接受的答案是“错误的”,因为它实际上并没有改变任何东西。 $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。这个管道:

  1. itemType 过滤原始集合 (orders) 中的数据。从样本数据中,它删除了带有_id: 3 的文档,因为它的itemType 与我们正在寻找的文档("book")不同。
  2. 它使用localField/foreignField 语法在books 中查找数据,其中books 文档的_idorders 集合中源文档的itemId 匹配.
  3. 进一步使用let/pipeline语法表示books文档的status"OK"的附加条件。这就是为什么带有"BAD"statusbooks 文档不会被拉入带有_id: 2orders 文档的bookData

    (合并的)第二和第三部分的文档是here

【讨论】:

    猜你喜欢
    • 2019-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-01
    • 1970-01-01
    • 2021-12-11
    • 2013-06-27
    相关资源
    最近更新 更多