我的结构如下:
{
"_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"
]
]
}
}
]
}
}
}
]
}
答案 0 :(得分:0)
你想要$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"
}
}
]
}
}
}
]
}
简单投影将作用于数组的最外层元素,并将所有投影的内容输出为#34;作为数组"。因此,您使用$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
这样的事情或者指定其他可能的嵌套密钥,因为改变集合的唯一方法就是实际的"更新"声明。实际上,因为聚合的唯一要点是"减少"返回的数据只是您提供更新所需的表单,然后实际上需要返回" less"而不是一份完整的文件。
嵌套数组并不是一个好主意,因为写入显示你在这里除了使用静态数组索引以便以原子方式将新条目写入数组而没有覆盖时,你真的没有多少选择。所有其他现有内容。
一般来说,你想要一个更平坦的"结构,即使在最好的情况下,你仍然真的想要在每个数组级别上使用唯一标识符,如下所示:
{
"_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 = [];
}
如果有一种方法可以唯一匹配每个数组项,并且MongoDB 3.6支持positional filtered $[<identifier>]
更新,那么这就是可能的。它的写入次数较少,并且不依赖于固定索引位置,因为此处的脚本仅使用数组索引来为位置更新命名唯一标识符,但更新本身并不依赖于该索引位置。 / p>
即使有这样的支持,也可以使用#34;嵌套数组&#34;众所周知难以查询。因此,你真正应该考虑的是简单地只有一个级别&#34;其中包含您的所有"includedMesages"
,只需在每个项目的父嵌套上重复细节。对于你可能在非规范化方面所教授的内容,这似乎是相反的,但更新和查询的简易性往往超过了重复的程度。
典型&#34;查询&#34;涉及$map
和$filter
的类似组合,这种方式可能变得非常复杂,并且可以通过简单的“扁平化”来轻松避免。数组结构,如果没有完全转移到单独的集合。