如何在 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 中将子文档展平为根级别?的主要内容,如果未能解决你的问题,请参考以下文章