聚合计数数组成员匹配条件

时间:2018-06-01 10:52:24

标签: mongodb aggregation-framework

如标题所述,我在使用MongoDB计算数组中的元素时遇到了一些麻烦。 我有一个只有一个文档的数据库,如下所示:

 {_id: ObjectId("abcdefghilmnopq"),
    "Array": [
      {field1: "val1",
       field2: "val2",
       field3: "val3",
       ...
       },
       {field1: "Value1",
        field2: "Value2",
        field3: "Value3",
       ...
       },
        ...
     ]
 }

我想计算具有特定条件的数组元素的数量(例如field1: "a",并计算所有具有field1 = a的元素。 我尝试使用此代码:

db.collection.aggregate([
{ $unwind : {path: "$Array", 
             includeArrayIndex: "arrayIndex"}},
{ $match : { "Array.field1" : "a"}},
{ $project : { _id : 0, 
               Array : 1, 
               arrayIndex: 1, 
               total: {$size: "$Array"}}}
])

但是我收到了这个错误:

  

命令失败,错误17124:' $ size的参数必须是a   数组,但类型为:object'在服务器上

我找了几个这个问题的答案,但我找不到解决问题的方法。我的意思是,'阵列'是一个阵列!

提前致谢

1 个答案:

答案 0 :(得分:1)

错误是因为它不再是$unwind之后的数组,因此不再是$size的有效参数。

您似乎试图“合并”一些现有答案而不了解他们正在做什么。您真正想要的是$filter$size

db.collection.aggregate([
  { "$project": {
    "total": {
      "$size": {
        "$filter": {
          "input": "$Array",
          "cond": { "$eq": [ "$$this.field1", "a" ] }
        }
      }
    }
  }}
])

或使用$reduce“重新发明轮子”:

db.collection.aggregate([
  { "$project": {
    "total": {
      "$reduce": {
        "input": "$Array",
        "initialValue": 0,
        "in": {
          "$sum": [
            "$$value", 
            { "$cond": [{ "$eq": [ "$$this.field1", "a" ] }, 1, 0] }
        }
      }
    }
  }}
])

或者您尝试使用$unwind做什么,实际上您再次$group以“计算”有多少匹配:

db.collection.aggregate([
  { "$unwind": "$Array" },
  { "$match": { "Array.field1": "a" } },
  { "$group": {
    "_id": "$_id",
    "total": { "$sum": 1 }
  }}
])

前两种形式是现代MongoDB环境的“最佳”形式。 $unwind$group的最终形式是一个“遗留”构造,自MongoDB 2.6以来,这种类型的操作实际上并不是必需的,但操作符略有不同。

在前两个中,我们基本上比较了每个数组元素的field1值,而它仍然是一个数组。 $filter$reduce都是现代运算符,旨在与现有数组配合使用。使用聚合$eq运算符对每个运算符进行相同的比较,该运算符根据给定的参数是否“相等”返回布尔值。在这种情况下,每个数组成员的预期值为"a"

$filter的情况下,数组实际上保持不变,除了"cond"中不符合提供条件的任何元素都从数组中删除。由于我们仍然有一个“数组”作为输出,我们可以使用$size运算符来测量处理过滤条件后剩下的数组元素的数量。

另一方面,$reduce通过数组元素工作,并在每个元素和存储的“累加器”值上提供表达式,我们使用"initialValue"初始化该值。在这种情况下,$eq运算符中会应用相同的$cond测试。这是一个“三元”或if/then/else条件运算符,它允许测试表达式返回一个布尔值,以便在then时返回true值,或在{{else时返回false值1}}。

在该表达式中,我们分别返回10,并提供将该返回值和当前“累加器”"$$value"$sum运算符相加的总体结果将这些加在一起。

最终形式在数组上使用$unwind。这实际上是解构数组成员为每个数组成员创建一个“新文档”,它是原始文档中的相关父字段。这有效地“复制”了每个阵列成员的主文档。

一旦$unwind文档的结构变为“更平坦”的形式。这就是为什么然后您可以执行后续$match管道阶段以删除未匹配的文档。

这将我们带到$group,它被应用于“重新组合”与公共密钥相关的所有信息。在这种情况下,它是原始文档的_id字段,当然会复制到$unwind生成的每个文档中。当我们将这个“公共密钥”作为单个文档返回时,我们可以使用$sum累加器“计算”从数组中提取的剩余“文档”。

如果我们想要剩下的“数组”,那么你可以$push并仅使用其余成员重建数组:

  { "$group": {
    "_id": "$_id",
    "Array": { "$push": "$Array" },
    "total": { "$sum": 1 }
  }}

但是当然不是在另一个管道阶段使用$size,我们可以像我们已经使用$sum

那样“统计”