对键及其值的出现进行计数和分组

时间:2017-08-29 23:40:02

标签: mongodb mongodb-query aggregation-framework

我有一个看起来像这样的MongoDB集合:

[{
        "installer": "anthony",
        "tester": "bob"
    }, {
        "installer": "chris",
        "tester": "anthony"
    }, {
        "installer": "bob",
        "tester": "dave"
    }, {
        "installer": "anthony",
        "tester": "chris"
    }, {
        "installer": "chris",
        "tester": "dave"
    }
]

我正在尝试使用aggregate,因此我可以计算每个字段在每个字段中出现的次数,并检索以下结果:

[{
        "name": "anthony",
        "installer": 2,
        "tester": 1
    }, {
        "name": "bob",
        "installer": 1,
        "tester": 1
    }, {
        "name": "chris",
        "installer": 2,
        "tester": 1
    }, {
        "name": "dave",
        "installer": 0,
        "tester": 2
    }
]

这是我到目前为止完成的查询,问题是它只返回nameinstaller计数而没有tester计数。我可以运行此查询两次(一个用于installer,一个用于tester)但我想找到一种方法如何一次返回两个计数。

db.data.aggregate([
    {
        "$group": {
            "_id": "$installer",
            "installer": { "$sum": 1 }
        },
        "$project": {
            "name": "$_id",
            "installer": 1,
            "_id": 0
        }
    }
])

我的查询需要进行哪些更改才能获得每个人的installertester点数?

1 个答案:

答案 0 :(得分:2)

您基本上希望$cond选择是将10传递到$sum管道中的$group累加器,将初始值作为"阵列"使用$unwind为两个字段创建每个人的文档副本。

db.data.aggregate([
  { "$addFields": {
    "val": ["$installer","$tester"]    
  }},
  { "$unwind": "$val" },
  { "$group": {
    "_id": { "_id": "$_id", "val": "$val" },
    "installer": {
      "$max": {
        "$cond": [
          { "$eq": ["$installer","$val"] },
          1,
          0
        ]
      }    
    },
    "tester": {
      "$max": {
        "$cond": [
          { "$eq": ["$tester","$val"] },
          1,
          0
        ]
      }    
    }
  }},
  { "$group": {
    "_id": "$_id.val",
    "installer": { "$sum": "$installer" },
    "tester": { "$sum": "$tester" }  
  }}
])

要对抗给定文档可以两者相同"安装程序"和"测试员"我们实际应该在"文件"上汇总的价值根据发出的" val"作为第一步。使用$cond累加器中的$max可以使此案例成为"单个"文档而不是"两个",每个数组条目一个。

另一种情况当然是简单地返回" set"通过对初始列表应用$setUnion来避免在这样的实例中出现重复:

db.data.aggregate([
  { "$addFields": {
    "val": { "$setUnion": [["$installer","$tester"]] }
  }},
  { "$unwind": "$val" },
  { "$group": {
    "_id": "$val",
    "installer": {
      "$sum": {
        "$cond": [
          { "$eq": ["$installer","$val"] },
          1,
          0
        ]
      }    
    },
    "tester": {
      "$sum": {
        "$cond": [
          { "$eq": ["$tester","$val"] },
          1,
          0
        ]
      }    
    }
  }}
])

我在源文件中添加了一个文档:

{ "installer": "jack", "tester": "jack" }

为了说明结果。

对于$cond,它是一个"三元"或if..then..else条件,其中参数为" first" if要求条件评估为布尔值,then是在true时返回的值,else是条件为false时要返回的值。

它可以交替写成:

"$cond": {
  "if": { "$eq": ["$installer","$val"] },
   "then": 1,
   "else":  0
}

但原来的"阵列"为简单表达式编写语法要简单一些。大多数人仍然会认识到"三元"它是什么,但如果你认为它使代码更清晰,那么你可以使用"命名键"而不是形式。

结果当然是1仅在文档中存在字段时返回,并给出正确的计数:

/* 1 */
{
    "_id" : "jack",
    "installer" : 1.0,
    "tester" : 1.0
}

/* 2 */
{
    "_id" : "dave",
    "installer" : 0.0,
    "tester" : 2.0
}

/* 3 */
{
    "_id" : "bob",
    "installer" : 1.0,
    "tester" : 1.0
}

/* 4 */
{
    "_id" : "chris",
    "installer" : 2.0,
    "tester" : 1.0
}

/* 5 */
{
    "_id" : "anthony",
    "installer" : 2.0,
    "tester" : 1.0
}

添加初始"数组"如果您的MongoDB版本不支持$project,则可以使用$addFields替换文档。唯一的区别是"明确"包括以后需要的其他字段:

{ "$project": {
  "tester": 1,
  "installer": 1,
  "val": { "$setUnion": [["$installer","$tester"]] }
}}

如果您的MongoDB实际上仍然比MongoDB 3.2更早,它允许使用#34;数组",那么您可以使用$map代替MongoDB 2.6及更高版本:

{ "$project": {
  "tester": 1,
  "installer": 1,
  "val": {
    "$setUnion": [
      { "$map": {
        "input": ["A","B"],
        "as": "a",
        "in": {
          "$cond": [{ "$eq": ["$$a", "A"] }, "$installer", "$tester"]
        }
      }
    ]
  }
}}

再次使用$cond交替选择要呈现为数组元素的值。

另外,你真的应该避免在语句结尾添加$project这样的事情。你当然可以这样做,但它确实意味着前一个流水线阶段的所有结果都是'#34;再次运行"为了进行额外的更改。对于将"_id"更改为"name"这样微不足道的事情,通常更好的做法是简单地接受"分组键"被称为_id并留在那里。

作为$group的结果,实际上 是"唯一标识符"其中_id是常用命名法。