TLDR;
在现代版本中,应该按照以下方式使用 $reduce
和 $setUnion
,在初始的$group
之后:
db.collection.aggregate([
{ "$group": {
"_id": { "Host": "$Host", "ArtId": "$ArtId" },
"count": { "$sum": 1 },
"tags": { "$addToSet": "$tags" }
}},
{ "$addFields": {
"tags": {
"$reduce": {
"input": "$tags",
"initialValue": [],
"in": { "$setUnion": [ "$$value", "$$this" ] }
}
}
}}
])
你正确地找到了
$addToSet
运算符,但是在处理数组内容时,通常需要先使用
$unwind
进行处理。这会“去规范化”数组条目,并将每个数组条目作为字段中的单个值,“复制”父文档。这就是你需要避免使用它所看到的行为的原因。
不过,“计数”可能会带来一些有趣的问题,但可以通过在初始
$group
操作后使用“双重展开”轻松解决。
db.collection.aggregate([
{ "$group": {
"_id": { "Host": "$Host", "ArtId": "$ArtId" },
"tcount": { "$sum": 1 },
"ttags": { "$push": "$tags" }
}},
{ "$unwind": "$ttags" },
{ "$unwind": "$ttags" },
{ "$group": {
"_id": "$_id",
"tcount": { "$first": "$tcount" },
"tags": { "$addToSet": "$ttags" }
}},
{ "$project": {
"_id": 0,
"Host": "$_id.Host",
"ArtId": "$_id.ArtId",
"count": "$tcount",
"tags": "$ttags"
}}
])
使用
$project
的最后一部分也在那里,是因为我在聚合流水线的其他阶段中使用了“临时”字段名称。这是因为在
$project
中有一个优化选项,它会从已存在的阶段中“复制”字段,并按照它们在文档中出现的顺序“在”添加任何“新”字段之前。否则输出将如下所示:
{ "count":2 , "tags":[ "tag1", "tag2", "tag3" ], "Host": "abc.com", "ArtId": "123" }
字段的顺序可能与您想象的不同,这其实很琐碎,但对一些人来说很重要,因此值得解释一下为什么以及如何处理。
所以$unwind
执行的工作是将项目保持分开而不是在数组中,并且首先执行$group
,允许您获取“分组”键发生次数的“计数”。
稍后使用的$first
运算符“保留”了该“计数”值,因为它刚刚为“tags”数组中存在的每个值“复制”了该值。总之,这都是相同的值,因此无论如何都没有关系。 只需选择一个即可。
$unwind
,并实际运行代码。 - Neil Lunn