Переписать свойство во вложенных массивах - PullRequest
0 голосов
/ 02 июня 2018

У меня есть такая структура:

{
    "_id" : ObjectId("5af16cfdb508cf1a30ed0b38"),
    "messages" : [
        {
            "publicMessage" : {
                "message" : {
                    "includedMessages" : [
                        {
                            "image" : {
                                "url" : {
                                    "url" : "umT6Gsx6yO.jpg"
                                }
                            }
                        }
                    ]
                }
            }
        }
    ]
}

Я сделал ошибку и сохранил все в image.url.url вместо image.url.Как я могу переместить его в корень изображения?Конечно, есть много подобных документов, и не у всех есть image.url.url, поэтому обновите все с помощью «где».Я попробовал это:

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"
                                ]
                            ]
                        }
                    }
                ]
            }
        }
    }
]

}

1 Ответ

0 голосов
/ 02 июня 2018

Вместо этого вы хотите $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 таким образом, что это может стать очень сложным, и этого легко избежать, просто "сгладив" структуру массива, если не перейти к отдельной коллекции полностью.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...