Как моноэкспортировать и моноимпортировать коллекции с новыми идентификаторами, но сохраняя связь - PullRequest
0 голосов
/ 21 мая 2018

Я экспортирую 2 коллекции, используя mongoexport:

mongoexport -h -u -p --db dev -c parent -q '{_id: ObjectId("5ae1b4b93b131f57b33b8d11")}' -o parent.json

и

mongoexport -h -u -p --db dev -c children -q '{parentId: ObjectId("5ae1b4b93b131f57b33b8d11")}' -o children.json

Из первой я получил одну запись (выбранную по идентификатору), а со второй я получил много записей(выбирается по parentId, который является идентификатором из первого.

Как я могу использовать mongoimport для импорта тех с новыми идентификаторами, но сохраняя соотношение -> parent.id = child.parentId

?

1 Ответ

0 голосов
/ 22 мая 2018

TLDR;Перейдите к деталям, чтобы решить эту проблему, или прочитайте, если хотите разобраться в заблуждении.

Фон

Давайте разберемся с базовым заблуждением здесь, в этом MongoDB «сам» не имеет никакой концепции отношений .«Отношение», как вы имеете в виду, это просто значение, записанное в свойстве в одной коллекции, которое «ссылается» на другое значение, присутствующее в другой коллекции.Ничто в этих «внешних ключах» не предусматривает каких-либо ограничений на MongoDB, которые определяют, что данные «связаны» , и они являются просто «значениями» .

Как только вы примете этот факт, станет очевидным, почему любой инструмент, такой как mongoexport или даже базовые операции запроса, такие как find(), действительно не имеют ни малейшего представленияэто просто потому, что вы помещаете значение куда-то, что оно «предназначено» для иди и получаю эти данные откуда-то еще.

Когда-то было (и этов меньшей степени) концепция, называемая DBRef, в которой хранится не только «значение», но и некоторые подробности относительно того, где это значение находится в «ссылочном» термине.Это может быть коллекция или база данных и коллекция.Однако эта концепция остается относительно недолгой и бесполезной в современном контексте.Даже с сохранением этих «метаданных» в самой базе данных отсутствует понятие «отношение» к данным.Извлечение все еще оставалось «клиентской концепцией», когда некоторые драйверы могли видеть DBRef, а затем «разрешать» его, отправляя другой запрос на сервер для извлечения «связанных» данных.

Естественно, это "дырявый", и концепция была отброшена в пользу более современных концепций, в частности $lookup.

Современное строительство

Что этона самом деле все сводится к тому, что для самой MongoDB «отношение» на самом деле является «концепцией клиента» , в том смысле, что «внешняя сущность» фактически принимает решениечто «это» связано с «тем», через какие данные вы определяете как «равные ссылки».

Не существует «правил базы данных», которые бы обеспечивали это, поэтому в отличие от различных традиционных решений СУБД, «объект»store "природа MongoDB по существу говорит " ... это не моя работа, делегировать кому-то другому ", и это обычно означает, что вы определяете это в логике" клиента "того, что используется для доступаs база данных.

Однако существуют "некоторые инструменты" , которые позволяют "серверу" фактически действовать в соответствии с этой логикой.По сути, они вращаются вокруг $lookup, что по сути является «современным методом» для выполнения «соединений» на сервере при работе с MongoDB.

Так что если у вас есть «связанные данные», вы хотите«экспортировать», тогда у вас есть пара опций:

  • Создание «представления» с использованием $ lookup

    Представление MongoDB «представления» с выпуском 3.2.По сути, это «конвейеры агрегации», которые «маскируют» как коллекцию.Для всех целей обычных операций, таких как .find() или даже mongoexport, этот конвейер выглядит как коллекция и может быть доступен как таковой.

    db.createView("related_view", "parent", [
      { "$lookup": {
        "from": "children",
        "localField": "_id",
        "foreignField": "parentId",
        "as": "children"
      }}
    ])
    

    Имея это "представление", вы можете просто вызвать mongoexport с использованием имени, определенного для «представления»:

    mongoexport -c related_view -o output.json
    

    Так же, как будет делать $lookup, каждый «родительский» элемент теперь будет содержать массив с «связанным»содержимое из «children» по внешнему ключу.

    Поскольку $lookup создает выходные данные в виде документа BSON, применяются те же ограничения, что и для всех MongoDB, поскольку результирующее соединение не можетпревышать 16 МБ в любом документе.Таким образом, если массив вызывает рост родительского документа за этот предел, то использование вывода в качестве «массива», встроенного в документ, не вариант.

    В этом случае вы обычно используете $unwind, чтобы «нормализовать» выходной контент так же, как это выглядит при обычном соединении SQL.В этом случае «родительский» документ будет скопирован для каждого «связанного» члена, а выходные документы соответствуют документам из связанных дочерних элементов, но со всей родительской информацией и «дочерними элементами» в качестве единственного встроенного свойства.

    Это просто означает добавление $unwind к такому «представлению»:

    db.createView("related_view", "parent", [
      { "$lookup": {
        "from": "children",
        "localField": "_id",
        "foreignField": "parentId",
        "as": "children"
      }},
      { "$unwind": "$children" }
    ])
    

    Поскольку мы просто выводим по существу один документ для каждого «связанного дочернего элемента», маловероятно, что предел BSON равенпробит.С очень большими документами для родителей и детей это все еще возможно, хотя и редко.Для этого случая будет другая обработка, как мы можем упомянуть позже.

  • Используйте $ lookup напрямую в сценарии

    Если у вас нет версии MongoDB, поддерживающей "представления"«но у вас все еще есть $lookup и нет ограничений по пределу BSON, вы по-прежнему можете, по сути,« запрограммировать »вызов конвейера агрегации с оболочкой mongo и вывести его в виде JSON.1114 * Процесс аналогичен, но вместо использования "view" и mongoexport, мы вручную переносим несколько команд, которые могут быть вызваны в оболочку из командной строки:

    mongo --quiet --eval '
        db.parent.aggregate([ 
          { "$lookup": {
            "from": "children",
            "localField": "_id",
            "foreignField": "parentId",
            "as": "children"
          }}
        ]).forEach(p => printjson(p))'
    

    И снова многоТо же самое, что и предыдущий процесс, при желании вы можете дополнительно $unwind, а также в конвейере, если это то, что вам нужно после

  • Сценарий "join"

    Если вы работаете с экземпляром MongoDB без поддержки $lookup (а этого не должно быть, поскольку ниже 3.0 больше нет официальной поддержки), или у вас действительно есть сценарий, в котором "join"будет создавать данные р«родительский» документ, который превысил предел BSON, тогда другой вариант заключается в «сценарии» всего процесса объединения путем выполнения запросов для получения «связанных» данных и их вывода.

    mongo --quiet --eval '
        db.parent.find().forEach(p =>
          printjson(
            Object.assign(p, {
            children: db.children.find({ parentId: p._id }).toArray()
            })
          )
        )'
    

    или даже в«развернутая» или «ненормализованная» форма:

    mongo --quiet --eval '
        db.parent.find().forEach(p =>
          db.children.find({ parentId: p._id }).forEach(child =>
            printjson(Object.assign(p,{ child }))
          )
        )'
    

Сводка

Суть в том, что «MongoDB сама» не знает о «отношениях», иэто действительно зависит от вас, чтобы предоставить эту деталь.Будь то в форме «представления», к которому вы можете получить доступ, или с помощью других средств определения «кода», необходимого для явного определения «условий» этого «отношения», поскольку в отношении самой базы данных это просто несуществуют в любой другой форме.

Кроме того, просто для того, чтобы обратиться к пункту в комментарии, если вы хотите «экспортировать» просто создать «новую коллекцию», то либо просто создайте «представление», либо используйте $out оператор агрегирования:

db.parent.aggregate([
  { "$lookup": {
    "from": "children",
    "localField": "_id",
    "foreignField": "parentId",
    "as": "children"
  }},
  { "$out": "related_collection" }
])

И если вы хотите «изменить родителя» с помощью «встроенных» данных, то выполните цикл и используйте bulkWrite():

var batch = [];

db.parent.aggregate([
  { "$lookup": {
    "from": "children",
    "localField": "_id",
    "foreignField": "parentId",
    "as": "children"
  }}
]).forEach(p => {
  batch.push({
    "updateOne": {
      "filter": { "_id": p._id },
      "update": {
        "$push": { "children": { "$each": p.children } }
      }
    }
  });

  if (batch.length > 1000) {
    db.parent.bulkWrite(batch);
    batch = [];
  })
});

if (batch.length > 0) {
  db.parent.bulkWrite(batch);
  batch = [];
}

Просто не нужно «экспортировать» просто для создания новой коллекции или изменения существующей.Конечно, вы должны делать это, когда хотите сохранить коллекцию как «встроенные» данные, и вам не нужны накладные расходы $lookup при каждом запросе.Но решение о том, «вставлять или ссылаться» - это совсем другая история.

...