Mongo聚合如何在不使用$expr的情况下在查找内匹配数组

bvjxkvbb  于 2022-10-22  发布在  Go
关注(0)|答案(1)|浏览(145)

我有一个聚合管道查询(我已经删除了不必要的内容),它在使用$expr时可以使用,但在不使用$expr时就不能使用。但是,为了获得更好的性能,我希望避免使用$expr,因此将使用索引。从逻辑上讲,规则和资源之间存在多对多的关系。我想总结一下每个规则的资源成本。这里的问题是在不使用表达式的情况下匹配分组数组内的资源。
使用$Expr:

db.collection.aggregate([ 
{'$group': {'_id': {'rule_id': '$rule_id'}, 'rule_id': {'$first': '$rule_id'}, 'resources_ids': {'$push': '$resource_id'}}}, 
{'$lookup': 
    {'from': 'other_collection', 
     'let': {'resources_ids': '$resources_ids'}, 
         'pipeline': [
             {'$match': 
                 {'$expr":
                     {'$and': [
                         {'$in':['$resource_id', '$$resources_ids']}
                     ]}
                 }
             }, 
             {'$group': {'_id': {}, 'total_cost': {'$sum': '$cost'}}}], 'as': 'results'}}])

不使用$expr:

db.collection.aggregate([ 
{'$group': {'_id': {'rule_id': '$rule_id'}, 'rule_id': {'$first': '$rule_id'}, 'resources_ids': {'$push': '$resource_id'}}}, 
{'$lookup': 
    {'from': 'cost_data', 
     'let': {'resources_ids': '$resources_ids'}, 
         'pipeline': [
             {'$match': 
                 {'$and': [
                      {'resource_id': {'$in': '$$resources_ids'}}, 
                  ]}
             }, 
             {'$group': {'_id': {}, 'total_cost': {'$sum': '$cost'}}}], 'as': 'results'}}])
qltillow

qltillow1#

我认为@rickhg12hs的评论给出了正确的答案。这里应该没有任何需要$expr的必要。当localField是阵列时,localField/foreignField语法将正常工作,而无需使用$unwind(或使用$expr),如此处所述。因此,$lookup的匹配组件实际上可以看起来如下所示:

$lookup: {
      from: "foreign",
      localField: "resources_ids",
      foreignField: "resource_id",
      as: "result"
    }

您可以比较this syntax abovemore verbose pipeline / $expr version的输出,以发现它们是相同的。
我脑海中浮现出一些其他的想法。首先,您可以将localField/foreignField语法与pipeline语法结合使用,以便第二个$group仍然可以嵌套在$lookup中。这将使$lookup阶段的最终版本结构如下:

{
    $lookup: {
      from: "foreign",
      localField: "resources_ids",
      foreignField: "resource_id",
      pipeline: [
        {
          "$group": {
            "_id": {},
            "total_cost": {
              "$sum": "$cost"
            }
          }
        }
      ],
      as: "result"
    }
  }

该组件的操场演示是here
第二件事是,使用索引执行$lookup很重要,可能会提高性能,但它可能不会使此操作变得“快速”。如上所述,该聚合将执行完整集合扫描,以处理源集合中的所有文档。(即使另一个集合中的索引用于$lookup,您仍将在此源集合的explain输出中看到COLLSCAN)。
最后,另一个集合上的索引可能应该是{ resource_id: 1, cost: 1 }。这应该允许数据库在执行$lookup时覆盖查询,并避免完全获取这些文档。
编辑以解决此评论:
在匹配中不需要$expr。我已经做过了。然而,在这里,因为我通过管道构建的数组不允许我在没有$expr的情况下在匹配中使用它。
这是不正确的。具体地说,数组的来源在这里并不相关。对于$lookup阶段,该数组是源文档中的一个字段,还是在较早的管道阶段中生成的,这一点都不重要。事实上,它甚至不知道该数组来自哪里,只知道它是传递给它的生成文档中的一个字段。
相反,您所描述的是$match本身的行为。来自the documentation
$match获取指定查询条件的文档。查询语法与读操作查询语法相同;即$match不接受原始聚合表达式。相反,可以使用$expr查询表达式在$match中包含聚合表达式。
换句话说,如果没有$expr,您目前不能从文档中引用任何字段(无论它们来自哪里,或者它们具有什么类型和值)。
但这一事实应该与您的用例基本无关。您可以使用localField/foreignField语法进行此数组匹配。如果需要匹配其他筛选器,则还可以在相同的$lookup中使用let/pipeline语法。Here is an arbitrary demonstration of that(请注意,由于otherVal不匹配,_id: 4文档不匹配)。
还值得注意的是,$expr本身通常并不排除索引的使用。不幸的是,目前的一个例外似乎是$in(reference)。不过,如果您将$lookup匹配的那部分放入localField/foreignField参数中,这对您来说应该不重要。

相关问题