Вместо этого вы хотите $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"
}
}
]
}
}
}
]
}
Простая проекция будет воздействовать только на самые внешние элементы массива и выводить все проецируемые «в виде массива».Таким образом, вы используете $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
или указывать каждый второй возможный вложенный ключ, поскольку единственное, что изменяет коллекцию, это фактическое«обновить» заявления.Фактически, поскольку единственная точка агрегации состоит в том, чтобы «сводить» возвращаемые данные в ту форму, которая вам необходима для предоставления обновлений, на самом деле тогда желательно возвращать «меньше», а не полный документ.
Дополнение к «вложенным» массивам
Вложенные массивы - не очень хорошая идея, поскольку записи показывают, что у вас здесь нет особого выбора, кроме как использовать индексы статических массивов для записиновые записи в массиве атомарным способом без перезаписи всего другого существующего контента.
Как правило, вам нужна более «плоская» структура, и даже в лучшем случае вы все еще действительно хотите иметь уникальные идентификаторы на каждом уровне массива, как показано ниже.:
{
"_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 с поддержкой позиционно-отфильтрованных $[<identifier>]
обновлений.Это меньше записей и не полагается на фиксированные позиции индекса, поскольку сценарии здесь используют только индексы массива для присвоения уникальных идентификаторов для позиционных обновлений, но сами обновления не зависят от этой позиции индекса.
Даже с такой поддержкой, хотя «вложенные массивы» общеизвестно трудны для запроса.Поэтому, что вы действительно должны рассмотреть, так это просто иметь «один уровень», который содержит все ваши "includedMesages"
, и просто повторять детали родительского вложения на каждый из этих элементов.Это может показаться противоречащим тому, чему вас учили о денормализации, но уровень дублирования часто перевешивается простотой обновления и запросов.
Типичные "запросы" включают аналогичные комбинации $map
и $filter
таким образом, что это может стать очень сложным, и этого легко избежать, просто "сгладив" структуру массива, если не перейти к отдельной коллекции полностью.