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

Mongodb 对出现的关键点及其值进行计数和分组,mongodb,mongodb-query,aggregation-framework,Mongodb,Mongodb Query,Aggregation Framework,我有一个MongoDB集合,看起来像这样: [{ "installer": "anthony", "tester": "bob" }, { "installer": "chris", "tester": "anthony" }, { "installer": "bob", "tester": "dave" }, { "installer": "anthony",

我有一个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
    }
]
这是我到目前为止完成的查询,问题是它只返回
名称
安装程序
计数,而不返回
测试仪
计数。我可以运行此查询两次(一次用于
installer
,另一次用于
tester
),但我想找到一种方法,可以同时返回两个计数

db.data.aggregate([
    {
        "$group": {
            "_id": "$installer",
            "installer": { "$sum": 1 }
        },
        "$project": {
            "name": "$_id",
            "installer": 1,
            "_id": 0
        }
    }
])
需要对我的查询进行哪些更改,以便我可以获得每个人的
安装程序
测试仪
计数?

您基本上需要选择是将
1
还是
0
传递到管道中的累加器,以及作为“数组”的初始值用于两个字段,用于为每个人创建文档副本

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" }  
  }}
])
为了应对给定文档可能同时具有相同的“installer”和“tester”值的情况,我们实际上应该首先根据发出的“val”在“document”上进行聚合。在累加器内部使用将使本例成为“单个”文档,而不是“两个”,即每个数组条目对应一个文档

当然,另一种情况是通过对初始列表应用来简单地返回“集合”值,以避免在这种情况下重复:

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" }
为了说明结果

至于,它是一个“三元”或
if..then..else
条件,其中参数为“first”
if
,用于将条件计算为布尔值,
then
是当
true
时返回的值,
else
是当条件为
false
时返回的值

它可以交替写为:

"$cond": {
  "if": { "$eq": ["$installer","$val"] },
   "then": 1,
   "else":  0
}
但是对于简单表达式来说,最初的“array”语法要简短一些。大多数人仍然会认识到“三元”是什么,但如果您认为它使代码更清晰,那么您可以使用“命名键”形式

当然,结果是
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": {
  "tester": 1,
  "installer": 1,
  "val": { "$setUnion": [["$installer","$tester"]] }
}}
如果您的MongoDB实际上仍然比MongoDB 3.2旧,后者允许使用“数组”符号,那么您可以使用MongoDB 2.6及更高版本:

{ "$project": {
  "tester": 1,
  "installer": 1,
  "val": {
    "$setUnion": [
      { "$map": {
        "input": ["A","B"],
        "as": "a",
        "in": {
          "$cond": [{ "$eq": ["$$a", "A"] }, "$installer", "$tester"]
        }
      }
    ]
  }
}}
再次使用以交替选择要显示为数组元素的值

此外,您确实应该避免在语句末尾添加a之类的操作。您当然可以这样做,但这确实意味着前一管道阶段的所有结果都将“再次运行”,以便进行其他更改。对于像将
“\u id”
更改为
“name”
这样琐碎的事情,通常更好的做法是简单地接受“分组键”被称为
\u id
,并保持不变


因此,实际上是
\u id
是常用术语的“唯一标识符”。

这应该会对您有所帮助:感谢您的回复和详细解释。查询似乎是实际数字的两倍,因此不是得到
“安装程序”:2
“测试仪”:1
对于
anthony
,它分别返回
4
2
。当
安装程序
测试员
是同一个人时会发生这种情况,例如
安装程序“:“bob”,“tester”:bob
@dat3450当然正确,因为这种情况会产生“两个”由于
$unwind
结果而导致的文档。解决方案是添加一个中介
$group
阶段,使这样的结果成为一个文档而不是两个文档。请参阅添加的修改和解释。谢谢,我还提出了另一个解决方案:在
$addToFields.val
中包含
$cond
,它将检查
$installer
$tester
是否相同,并仅在相同的情况下输入一次。