如何在mongoDB中展平双重数组?

10

我的mongoDB文档中有一些字段看起来像这样:

{
...
Countries: [["Spain", "France"]]
...
}

或者这个:

{
...
Countries: [["Spain"],["Russia", "Egypt"]]
...
}
我想做的是将[["Spain", "France"]]转换为["Spain", "France"],将[["Spain"], ["Russia", "Egypt"]]转换为["Spain", "Russia", "Egypt"],类似于在Ruby中使用flatten方法。
是否有一种方法可以在mongoDB中展开数组?我需要展开整个集合中所有文档中的数组,而不仅仅是单个文档,如果这很重要,同时,数组中的值及其数量在文档之间也有所不同。
我还使用Ruby作为mongo的驱动程序,因此使用Ruby驱动程序的方法对我也很有用。
5个回答

18

7
你需要使用两个unwind阶段和一个group阶段执行聚合操作。基本规则是展开的次数与嵌套深度级别相同。在这里,嵌套级别为2,所以我们要展开两次。
 collection.aggregate([
 {$unwind => "$Countries"},
 {$unwind => "$Countries"},
 {$group => {"_id":"$_id","Countries":{$push => "$Countries"}}}
 ])

第一个$unwind阶段产生的结果如下:
{
        "_id" : ObjectId("54a32e0fc2eaf05fc77a5ea4"),
        "Countries" : [
                "Spain",
                "France"
        ]
}
{
        "_id" : ObjectId("54a32e4ec2eaf05fc77a5ea5"),
        "Countries" : [
                "Spain"
        ]
}
{
        "_id" : ObjectId("54a32e4ec2eaf05fc77a5ea5"),
        "Countries" : [
                "Russia",
                "Egypt"
        ]
}

第二个 $unwind 阶段进一步展开了 Countries 数组。
{ "_id" : ObjectId("54a32e0fc2eaf05fc77a5ea4"), "Countries" : "Spain" }
{ "_id" : ObjectId("54a32e0fc2eaf05fc77a5ea4"), "Countries" : "France" }
{ "_id" : ObjectId("54a32e4ec2eaf05fc77a5ea5"), "Countries" : "Spain" }
{ "_id" : ObjectId("54a32e4ec2eaf05fc77a5ea5"), "Countries" : "Russia" }
{ "_id" : ObjectId("54a32e4ec2eaf05fc77a5ea5"), "Countries" : "Egypt" }

现在最后的$group阶段会根据_id对记录进行分组,并将国家名称累加到一个单一数组中。
{
        "_id" : ObjectId("54a32e4ec2eaf05fc77a5ea5"),
        "Countries" : [
                "Spain",
                "Russia",
                "Egypt"
        ]
}
{
        "_id" : ObjectId("54a32e0fc2eaf05fc77a5ea4"),
        "Countries" : [
                "Spain",
                "France"
        ]
}

如果您希望保留文档中的其他字段,则需要使用$first运算符明确指定除国家字段(field1、field2等)以外的字段名称。您可以通过在$out阶段指定集合名称来编写/覆盖集合。

collection.aggregate([
 {$unwind => "$Countries"},
 {$unwind => "$Countries"},
 {$group => {"_id":"$_id","Countries":{$push => "$Countries"},
             "field1":{$first => "$field1"}}},
 {$out => "collection"}
 ])

您需要明确指定字段,以避免出现冗余的Countries字段。

您可以使用$$ROOT系统变量来存储整个文档,但这将使Countries字段变得多余。一个在doc外部,一个在doc内部。

collection.aggregate([
 {$unwind => "$Countries"},
 {$unwind => "$Countries"},
 {$group => {"_id":"$_id","Countries":{$push => "$Countries"},
             "doc":{$first => "$$ROOT"}}},
 {$out => "collection"}
 ])

我比你提前了3秒钟写出了结果;),这只是有趣的。无论如何+1 - Disposer
@Disposer 是的 - 你比我快。花了一些时间来格式化结果 :), +1 为你的聚合。 :) - BatScream
谢谢。我可以再澄清两个问题吗?我注意到这个分组操作符只返回指定的字段(id和countries)。是否有可能包含所有其他字段而不手动指定每个字段。因为有许多其他字段,而且数据库尚未优化,甚至有一些唯一的字段仅由非常少量的文档共享,搜索和指定所有这些字段将非常困难。 第二个问题可能很愚蠢:如何使用聚合输出覆盖原始集合? - A.V. Arno
@AntonDinoMois,你应该明确指定所有其他字段。你可以列出字段并提出统一的结构,将必填和可选字段分开并进行条目。这是我的建议。您需要使用管道的 $out 阶段来覆盖集合。请查看我的更新答案。 - BatScream

4

您的国家数据格式不太好,因此您可能需要进行转换。以下是一个脚本,用于将“国家”字段中的数组展平并将其保存到原始文档中,您可以在Mongo shell中运行:

function flattenArray(inArr) {
    var ret = [];
    inArr.forEach(function(arr) {
        if (arr.constructor.toString().indexOf("Array") > -1) {
           ret = ret.concat(flattenArray(arr));
        } else {
           ret.push(arr);                   
        }
    });
    return ret;
}


db.collection.find({
  'Countries': {
    '$exists': true
  }
}).forEach(function(doc){
  doc.Countries = flattenArray(doc.Countries);
  db.collection.save(doc);
});

3

试试这个:

db.test2.aggregate([
   {"$unwind" : "$Countries"},
   {"$unwind" : "$Countries"},
   {$group : { _id : '$_id', Countries: { $addToSet: "$Countries" }}},
]).result

0

自从版本5之后,我们有一种替代方法,可以使用$function运算符,像这样:

db.collection.aggregate([
   {
      $addFields: { 
        Countries: {
          $function: {
            body: function(items) {
              return [].concat(...items)
            },
            args: [ "$Countries" ],
            lang: "js"
          }
       }
     }
   }
])

理想的解决方案是能够使用$concatArrays,但不幸的是,该运算符无法考虑数组字段(在撰写本文时)。关于数组字段的这种限制,mongodb后续有一个票据:https://jira.mongodb.org/browse/SERVER-31991。如果您对此增强功能带来的灵活性感兴趣,请投票支持。这个票据自2019年以来一直存在,因此似乎需要帮助来确定优先级;-)


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