如何在Mongo聚合中合并文档中的数组字段

17

我有一个需求,需要在两个记录上进行聚合,它们都有一个包含不同值的数组字段。我需要的是,当我对这些记录进行聚合时,结果应该有一个数组,其中包含来自两个不同数组的唯一值。以下是示例:

第一个记录:

 { Host:"abc.com" ArtId:"123", tags:[ "tag1", "tag2" ] }

第二个记录

{ Host:"abc.com" ArtId:"123", tags:[ "tag2", "tag3" ] }

在对主机和艺术品编号进行聚合后,我需要得到这样的结果:

 { Host: "abc.com", ArtId: "123", count :"2", tags:[ "tag1", "tag2", "tag3" ]}

我在组语句中尝试了$addToset,但它给我的标签是这样的:[["tag1","tag2"],["tag2","tag3"]]

请问如何在聚合操作中实现相同的结果。


@Neil Lunn,我已经将它移动到这里。如果文档中有多个这样的数组,您能否建议我选择哪种方法?对于每个数组,我都必须像您解释的那样重复聚合。但是,如果我在第一个组中为每个数组使用$addToSet,那么我将得到一个数组的数组,我可以在Java中解析它以创建每个字段的新数组,在这种情况下,我只需要运行一次所有记录的聚合。 - viren
我们似乎有些误解。您应该在 dba.stackexhange.com 上运行我呈现的代码,目的是将问题迁移。它并不会生成一个数组的数组。我会再次在这里发布,因为您已经做到了这一点。您需要查看“double” $unwind,并实际运行代码。 - Neil Lunn
@NeilLunn 抱歉我没有说这段代码不起作用,它很完美地解决了我的单个数组问题,但我是在寻求帮助,如果文档中有两个或更多的数组字段,并且所有字段都需要以相同的方式处理。因此,在这种情况下,我需要为每个数组字段重复这个过程。 - viren
这是我认为你误解的地方。这不是关于移动你自己提出的问题,而是关于在文档中询问你的问题“多个数组”。这是一个问题,而不是原始问题的补充。你还没有问过它。用所有细节提出你的问题。 - Neil Lunn
1个回答

37

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 on the compound key and get the occurrences first
    { "$group": {
        "_id": { "Host": "$Host", "ArtId": "$ArtId" },
        "tcount": { "$sum": 1 },
        "ttags": { "$push": "$tags" }
    }},

    // Unwind twice because "ttags" is now an array of arrays
    { "$unwind": "$ttags" },
    { "$unwind": "$ttags" },

    // Now use $addToSet to get the distinct values        
    { "$group": {
        "_id": "$_id",
        "tcount": { "$first": "$tcount" },
        "tags": { "$addToSet": "$ttags" }
    }},

    // Optionally $project to get the fields out of the _id key
    { "$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”数组中存在的每个值“复制”了该值。总之,这都是相同的值,因此无论如何都没有关系。 只需选择一个即可。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接