如何在 MongoDB 中将子文档展平为根级别?

Posted

技术标签:

【中文标题】如何在 MongoDB 中将子文档展平为根级别?【英文标题】:How to flatten a subdocument into root level in MongoDB? 【发布时间】:2014-05-19 04:33:58 【问题描述】:

例如,如果我有这样的文档


  a: 1,
  subdoc: 
    b: 2,
    c: 3
  

我怎样才能把它转换成这样的格式? (不使用project


  a: 1,
  b: 2,
  c: 3

【问题讨论】:

你为什么坚持不project做呢?请回答这个问题,因为您的理由也可能排除其他可能的答案。 @Philipp 子文档中有 30 多个元素,所以project 意味着需要大量输入。 一个很好的理由可能是子文档会随着时间而改变,因此,如果稍后在在那里,您希望所有子文档都进入根目录。 从 MongoDB 3.4 开始,有一个名为 $replaceRoot 的聚合管道运算符,它允许您将 subdoc 设为新的 $$ROOT。但是,它完全取代了根。我实际上不知道如何将subdoc 与原始根目录中已经存在的字段合并。也许这暗示了某人…… $replaceRoot 的任何替代品,版本 3.2....mongodb 【参考方案1】:

您可以使用$set$unset 来做到这一点

db.collection.update(, $set:  "b": 2, "c": 3 , $unset:  "subdoc": 1  )

当然,如果集合中有很多文档,那么就像上面一样,您需要循环集合文档以获取值。 MongoDB 无法单独在更新操作中引用字段的值:

db.collection.find( "subdoc": "$exists": 1  ).forEach(function(doc) 
    var setDoc = ;
    for ( var k in doc.subdoc ) 
        setDoc[k] = doc.subdoc[k];
    
    db.collection.update(
         "_id": doc._id ,
         "$set": setDoc, "$unset": "subdoc" 
    );
)

这还采用了$exists 的一些安全用法,以确保您选择的文档实际上存在“subdoc”键。

【讨论】:

所以 mongoDB 中没有内置的操作符/函数可以做到这一点,对吧? @svg 运算符就是我提到的。不能单独在更新中获取字段的值,这也是我说的。 好的,知道了,谢谢。我实际上是指一个操作员,一次拍摄。对不起,我没有说得很清楚。 顺便说一句,$set&$unset 的创造性使用。恐怕循环的性能会低于$project,对吧?如果是这样,我会坚持使用$project,但输入更多内容(子文档中有 30 多个元素)。 @svg 这两件事完全不同。您要么操作实际文档,要么进行投影以重新塑造特定结果,而这只能通过聚合来完成。决定你想做哪一个。更改您的文档或以不同的方式呈现结果。【参考方案2】:

您也可以使用MongoDB projection i.e $project aggregation framework pipeline operators。(推荐方式)。如果您不想使用project,请检查此link

db.collection.aggregation([$project . . ]);

以下是您的案例:

db.collectionName.aggregate
([
     $project:  a: 1, 'b': '$subdoc.b', 'c': '$subdoc.c' 
]);

为您提供预期的输出,即

    
        "a" : 1,
        "b" : 2,
        "c" : 3
    

【讨论】:

【参考方案3】:

您可以将 $replaceRoot$addFields 阶段一起使用,如下所示:

db.collection.aggregate([
     "$addFields":  "subdoc.a": "$a"  ,
     "$replaceRoot":  "newRoot": "$subdoc"   
])

【讨论】:

如果根目录下有很多字段,可以使用mergeObjects 和特殊变量$$ROOT,例如:db.collection.aggregate([ $replaceRoot: newRoot: $mergeObjects: ["$$ROOT", "$subdoc"] , $project: subdoc: 0 , ])【参考方案4】:

我想最简单的方法是在结果返回后更改结果,使用map

collection.mapFunction = function(el) 
  el.b = el.subdoc.b;
  el.c = el.subdoc.c
  delete(el.subdoc);
  return el;


...

var result = await collection.aggregate(...).toArray();
result = result.map(collection.mapFunction);

【讨论】:

我认为你不应该这样做。 好吧,这很糟糕,因为它在应用程序内存上而不是在数据库级别上运行,但我不必在 $project 子句上重写数据库的每个字段【参考方案5】:

我们可以使用$replaceWith 的别名$replaceRoot$mergeObjects 运算符来做到这一点。

let pipeline = [
   
      "$replaceWith": 
         "$mergeObjects": [  "a": "$a" , "$subdoc" ]
      
   
];

let pipeline = [
    
        "$replaceRoot": 
            "newRoot": 
                "$mergeObjects": [ "a": "$a" , "$subdoc" ]
            
        
    
];

db.collection.aggregate(pipeline)

【讨论】:

【参考方案6】:

Mongo 4.2 开始,$replaceWith 聚合运算符可用于将文档替换为另一个文档(在我们的示例中为子文档),作为@chridam 提到的$replaceRoot 的语法糖。

因此,我们可以首先在子文档中包含根字段以继续使用$set 运算符(在Mongo 4.2 中作为$addFields 的别名引入),然后用子文档替换整个文档使用$replaceWith:

//  a: 1, subdoc:  b: 2, c: 3  
db.collection.aggregate([
   $set:  "subdoc.a": "$a"  ,
   $replaceWith: "$subdoc" 
])
//  b: 2, c: 3, a: 1 

【讨论】:

【参考方案7】:

如果要查询嵌套数组元素并在根级别显示 如果你有 2 个嵌套数组,你应该使用 $unwind 两次

db.mortgageRequests.aggregate( [
        $unwind: "$arrayLevel1s" ,
        $unwind: "$arrayLevel1s.arrayLevel2s" ,
        $match:  "arrayLevel1s.arrayLevel2s.documentid" :  $eq: "6034bceead4ed2ce3951f38e"   ,
        $replaceRoot:  newRoot: "$arrayLevel1s.arrayLevel2s"  
    ] )

【讨论】:

【参考方案8】:

你也可以在$mergeObjects中使用$$ROOT

 $replaceWith: 
        $mergeObjects: [ "$$ROOT", "$subdoc" ]
 

【讨论】:

以上是关于如何在 MongoDB 中将子文档展平为根级别?的主要内容,如果未能解决你的问题,请参考以下文章

在 SQL 中将父/子关系记录展平为 1 行(更新)

在 Mongodb 中查找和聚合多个级别的子文档

如何在 PHP 中将多维数组“展平”为简单数组? [复制]

如何在 PHP 中将多维数组“展平”为简单数组? [复制]

如何替换mongodb文档中的子字符串

如何匹配 MongoDB 中的子文档数组?