尽管前一阶段输出了文档,$group 阶段仍返回零文档

Aur*_*ast 5 mongodb aggregation-framework

Mongo Playground 中的复制

我有一个聚合管道,其中包含一个$lookup阶段,然后是一个$match阶段,然后是一个$group阶段。

奇怪的是(无论如何对我来说),虽然$match阶段输出一个文档(可以通过暂时删除$group阶段来看到),但$group它之后的阶段输出零个文档。如果一个$group阶段有任何输入文档,我希望它始终至少有一个输出文档。为什么这里的情况不是这样呢?

上面的 Mongo Playground 链接重现了相关行为。

这是输出零文档的聚合管道:

db.orders.aggregate([
  {
    "$lookup": {
      "from": "products",
      "localField": "products.0.productId",
      "foreignField": "_id",
      "as": "firstProduct"
    }
  },
  {
    "$match": {
      "firstProduct.0.name": "Apron"
    }
  },
  {
    "$group": {
      "_id": null,
      "numOrders": {
        $sum: 1
      }
    }
  }
]);
Run Code Online (Sandbox Code Playgroud)

这是删除了小组赛阶段的相同流程。它返回一份文档。

db.orders.aggregate([
  {
    "$lookup": {
      "from": "products",
      "localField": "products.0.productId",
      "foreignField": "_id",
      "as": "firstProduct"
    }
  },
  {
    "$match": {
      "firstProduct.0.name": "Apron"
    }
  }
]);
Run Code Online (Sandbox Code Playgroud)

以下是合集内容orders

[
  {
    "products": [
      {
        "productId": {
          "$oid": "00000001f1438712ca040fca"
        }
      }
    ]
  }
]
Run Code Online (Sandbox Code Playgroud)

以下是合集内容products

[
  {
    "name": "Apron",
    "_id": {
      "$oid": "00000001f1438712ca040fca"
    }
  }
]
Run Code Online (Sandbox Code Playgroud)

MongoDB 版本是 6.0.4。我也在 4.4.19 中尝试过,行为是相同的。

Noe*_*oel 1

我可以在 v5.0.2 上看到相同的行为,当我查看解释计划时,只要管道中存在小组阶段,就会添加投影阶段。

"winningPlan" : {
    "stage" : "PROJECTION_DEFAULT",
    "transformBy" : {
        "firstProduct" : 1,
        "products.0.productId" : 1,
        "_id" : 0
    },
    "inputStage" : {
        "stage" : "COLLSCAN",
        "direction" : "forward"
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以通过在聚合查询中添加此阶段来重新创建问题。

db.orders.aggregate([
  {
    "$lookup": {
      "from": "products",
      "localField": "products.0.productId",
      "foreignField": "_id",
      "as": "firstProduct"
    }
  },
  {
    "$project": {
       "firstProduct" : 1,
       "products.0.productId" : 1,
       "_id" : 0
    }
  }
])
Run Code Online (Sandbox Code Playgroud)

结果

{
    "products" : [ 
        {}
    ],
    "firstProduct" : []
}
Run Code Online (Sandbox Code Playgroud)

我认为,指定 0 索引会扰乱底层的一些优化。删除索引后,它按预期工作。

db.orders.aggregate([
  {
    "$lookup": {
      "from": "products",
      "localField": "products.productId",
      "foreignField": "_id",
      "as": "firstProduct"
    }
  },
  {
    "$match": {
      "firstProduct.name": "Apron"
    }
  },
  {
    "$group": {
      "_id": null,
      "numOrders": {$sum: 1}
    }
  }
]);
Run Code Online (Sandbox Code Playgroud)