【问题标题】:MongoDb $elemMatch with $lookup variableMongoDb $elemMatch 与 $lookup 变量
【发布时间】:2021-03-24 11:17:17
【问题描述】:

我有一个给定的 MongoDb 集合,其中包含与此类似的文档:

{
items: [
  { id: 1, data: 'a' },
  { id: 2, data: 'b' },
  { id: 3, data: 'c' },
]
}

现在我必须从这个集合中$lookup。我需要来自 id = 2 元素的数组数据,其中 id = 1 数据与查找值 $$value 匹配。我第一次天真地尝试找到正确的文档如下所示:

$match {
  items: { $elemMatch: {
    id: 1,
    data: '$$value'
  } }
}

但是 $$value 没有被评估,所以数据与文字而不是它的值进行比较。我还尝试使用$expr 评估该值,但我无法获得正确的语法(如果可能的话?)。

我能够开始工作的唯一工作方式是先提取数据,然后进行匹配:

[
    {
        $project: {
            _id: 0,
            data1: {
                $filter: {
                    input: '$items', as: 'item',
                    cond: {$eq: ['$$item.id', 1]}
                }
            },
            data2: {
                $filter: {
                    input: '$items', as: 'item',
                    cond: {$eq: ['$$item.id', 2]}
                }
            },
        }
    },
    {
        $set: {
            data1: {$arrayElemAt: ['$data1.data', 0]},
            data2: {$arrayElemAt: ['$data2.data', 0]}
        }
    },
    {
        $match: {
            $expr: {$eq: ['$data1', '$$value']}
        }
    }
];

但正如人们所预料的那样,这种方法要慢得多。对于涉及的数据,查询需要 7 秒,而上层方法(使用常量而不是变量 $$value)快 3 倍以上。

是否可以在$elemMatch 运算符中直接使用变量$$value?或者是否有任何其他优化可用于加速集合查找?

【问题讨论】:

    标签: mongodb query-optimization


    【解决方案1】:

    首先让我们了解一下为什么您的幼稚方法会失败,Mongo 的 pipelined lookup 文档指出:

    $match 阶段需要使用 $expr 运算符来访问变量。 $expr 允许在 $match 语法中使用聚合表达式。

    因此,您在 $lookup 开头定义的变量 value 只能通过使用 $expr 访问,现在也在 $expr 文档中指定:

    参数可以是任何有效的聚合表达式

    遗憾的是,$elemMatch 不是“聚合表达式”,因为它属于“查询语言”。这就是您的第一种方法失败的原因。只是不允许在 $expr 中使用 $elemMatch,这是访问 $$value 字段所必需的。

    那么我们能做些什么呢?好吧,你可以像你已经开始做的那样使用$filter

    db.collection.aggregate([
      {
        $lookup: {
          from: "collection2",
          let: {
            value: "$data"
          },
          pipeline: [
            {
              $match: {
                $expr: {
                  $gt: [
                    {
                      $size: {
                        $filter: {
                          input: "$items",
                          as: "item",
                          cond: {
                            $and: [
                              {
                                $eq: [
                                  "$$item.id",
                                  1
                                ]
                              },
                              {
                                $eq: [
                                  "$$item.data",
                                  "$$value"
                                ]
                              }
                            ]
                          }
                        }
                      }
                    },
                    0
                  ],
                  
                }
              }
            }
          ],
          as: "res"
        }
      }
    ])
    

    如果您提供两个集合的完整结构和所需的最终结果,则更容易完整回答,因为我必须在这里猜测一些内容才能编写此管道。

    Mongo Playground

    【讨论】:

    • 感谢您的详细解释。您的演示结构足以满足我的需求。如果可以的话,我会再为 Mongo Playground 链接 +1!不幸的是,我根据您的解决方案更新的查询比我的第一种方法慢...
    • 没问题,常量值总是最有效的,因为它不需要像$filter 这样的任何结构操作用法。
    猜你喜欢
    • 1970-01-01
    • 2018-07-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-14
    • 1970-01-01
    • 2013-11-24
    相关资源
    最近更新 更多