我有这样的结构:
{
"_id" : ObjectId("5af16cfdb508cf1a30ed0b38"),
"messages" : [
{
"publicMessage" : {
"message" : {
"includedMessages" : [
{
"image" : {
"url" : {
"url" : "umT6Gsx6yO.jpg"
}
}
}
]
}
}
}
]
}
我犯了一个错误,把所有东西都存储在image.url.url中,而不是image.url中。如何才能将其上移到图像根目录?当然,像这样的文档还有很多,并不是每个人都有image.url.url,所以需要用"where“来更新所有的文档。我试过这个:
db.test.aggregate(
[
{ "$addFields": {
"messages.publicMessage.message.includedMessages.image.url": "$messages.publicMessage.message.includedMessages.image.url.url"
}},
{ "$out": "test" }
]
)
但它是错误的,并输出:
{
"_id" : ObjectId("5af16cfdb508cf1a30ed0b38"),
"messages" : [
{
"publicMessage" : {
"message" : {
"includedMessages" : [
{
"image" : {
"url" : [
[
"umT6Gsx6yO.jpg"
]
]
}
}
]
}
}
}
]
}
发布于 2018-06-02 08:20:43
您需要在这里使用$map
,因为它可以处理每个数组元素并重写内容。这也是一个真正的问题,它分成三个部分,尽管在最简单的形式中,它只是通过第一个清单来处理。
映射数组
db.test.aggregate([
{ "$addFields": {
"messages": {
"$map": {
"input": "$messages",
"as": "m",
"in": {
"publicMessage": {
"message": {
"includedMessages": {
"$map": {
"input": "$$m.publicMessage.message.includedMessages",
"as": "i",
"in": {
"image": {
"url": "$$i.image.url.url"
}
}
}
}
}
}
}
}
}
}},
{ "$out": "newtest" }
])
这会将文档返回为:
{
"_id" : ObjectId("5af16cfdb508cf1a30ed0b38"),
"messages" : [
{
"publicMessage" : {
"message" : {
"includedMessages" : [
{
"image" : {
"url" : "umT6Gsx6yO.jpg"
}
}
]
}
}
}
]
}
Simple projection将只作用于数组的最外层元素,并将投影的所有内容输出为“一个数组”。因此,您可以使用$map
来处理每个数组。
合并嵌套对象
请注意,如果在嵌套数组中的文档中实际有更多的字段,则需要在每个$map
中“显式”指定这些字段,因为这实际上是用您指定的新内容重写每个数组成员。
如果您实际使用的是MongoDB 3.6,那么您可以交替使用$mergeObjects
操作符,而不是显式地指定每个键和值。然而,您需要对“每个”嵌套级别执行此操作,因为您不能像使用$addFields
那样只使用“虚线字段路径”,而且实际上只“显式”指定每个键和值可能要简单得多:
db.test.aggregate([
{ "$addFields": {
"messages": {
"$map": {
"input": "$messages",
"as": "m",
"in": {
"$mergeObjects": [
"$$m",
{
"publicMessage": {
"$mergeObjects": [
"$$m.publicMessage",
{
"message": {
"$mergeObjects": [
"$$m.publicMessage.message",
{
"includedMessages": {
"$map": {
"input": "$$m.publicMessage.message.includedMessages",
"as": "i",
"in": {
"$mergeObjects": [
"$$i",
{ "image": { "url": "$$i.image.url.url" } }
]
}
}
}
}
]
}
}
]
}
}
]
}
}
}
}},
{ "$out": "newtest" }
])
写入输出
您可以将$out
添加到管道中,以便写入新集合,因为您不能写入正在从中读取的“同一集合”,也可以使用bulkWrite()
重写现有集合的元素:
var batch = [];
db.test.aggregate([
{ "$addFields": {
"messages": {
"$map": {
"input": "$messages",
"as": "m",
"in": {
"publicMessage": {
"message": {
"includedMessages": {
"$map": {
"input": "$$m.publicMessage.message.includedMessages",
"as": "i",
"in": {
"image": {
"url": "$$i.image.url.url"
}
}
}
}
}
}
}
}
}
}}
]).forEach(doc => {
doc.messages.forEach((m,msgIdx) => {
m.publicMessage.message.includedMessages.forEach((i,includeIdx) => {
batch.push({
"updateOne": {
"filter": { "_id": doc._id },
"update": {
"$set": {
[`messages.${msgIdx}.publicMessage.message.includedMessages.${includeIdx}.image.url`]:
i.image.url
}
}
}
});
});
if (batch.length >= 1000) {
db.test.bulkWrite(batch);
batch = [];
}
});
});
if (batch.length >= 0) {
db.test.bulkWrite(batch);
batch = [];
}
请注意,当使用bulkWrite()
“编写”时,没有必要担心像$mergeObjects
或指定每个其他可能的嵌套键这样的事情,因为改变集合的唯一东西是实际的"update“语句。事实上,由于聚合的唯一目的是将返回的数据“减少”为提供更新所需的形式,因此实际上需要返回“较少”而不是完整的文档。
关于“嵌套”数组的附录
嵌套数组并不是一个好主意,因为写入表明您在这里没有太多的选择,只能使用静态数组索引,以便在不覆盖所有其他现有内容的情况下以原子方式将新条目写入数组。
通常,你想要一个“扁平”的结构,即使在最好的情况下,你仍然希望在每个数组级别上有唯一的标识符,如下所示:
{
"_id" : ObjectId("5af16cfdb508cf1a30ed0b38"),
"messages" : [
{
"_id" : ObjectId("5b11e6b3492daf3e5df114b0"),
"publicMessage" : {
"message" : {
"includedMessages" : [
{
"_id" : ObjectId("5b11e6b3492daf3e5df114b1"),
"image" : {
"url" : {
"url" : "umT6Gsx6yO.jpg"
}
}
}
]
}
}
}
]
}
只要有一种方法可以唯一地匹配每个元素,那么您至少有机会执行原子更新,而不依赖于索引位置,并且假定数组内容没有随着额外的条目而改变:
var batch = [];
db.test.aggregate([
{ "$addFields": {
"messages": {
"$map": {
"input": "$messages",
"as": "m",
"in": {
"_id": "$$m._id",
"publicMessage": {
"message": {
"includedMessages": {
"$map": {
"input": "$$m.publicMessage.message.includedMessages",
"as": "i",
"in": {
"_id": "$$i._id",
"image": {
"url": "$$i.image.url.url"
}
}
}
}
}
}
}
}
}
}}
]).forEach(doc => {
var $set = { };
var arrayFilters = [];
doc.messages.forEach((m,mIdx) => {
arrayFilters.push({ [`m${mIdx}._id`]: m._id });
m.publicMessage.message.includedMessages.forEach((i,iIdx) => {
arrayFilters.push({ [`i${mIdx+iIdx}._id`]: i._id });
$set[`messages.$[m${mIdx}].publicMessage.message.includedMessages.$[i${mIdx+iIdx}].image.url`]
= i.image.url;
});
});
batch.push({
"updateOne": {
"filter": { "_id": doc._id },
"update": { $set },
arrayFilters
}
});
if (batch.length >= 1000) {
db.test.bulkWrite(batch);
batch = [];
}
})
if (batch.length > 0) {
db.test.bulkWrite(batch);
batch = [];
}
如果有一种方法可以唯一地匹配每个数组项,并且你有支持positional filtered $[]
更新的MongoDB 3.6,那么这就是可能的。它的写入次数更少,并且不依赖于固定的索引位置,因为这里的脚本只使用数组索引来命名位置更新的唯一标识符,但更新本身并不依赖于该索引位置。
即使有了这样的支持,“嵌套数组”也是出了名的难以查询。因此,您真正应该考虑的是简单地拥有包含所有"includedMesages"
的“一个级别”,并简单地重复父嵌套在每个项目上的细节。这可能看起来与您学到的关于反规范化的知识相反,但重复的程度往往被更新和查询的简易性所掩盖。
典型的“查询”以这种方式涉及$map
和$filter
的类似组合,这可能会变得非常复杂,如果不是完全移动到单独的集合,则可以通过简单地“扁平化”数组结构来轻松避免。
https://stackoverflow.com/questions/50651883
复制相似问题