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
при каждом запросе.Но решение о том, «вставлять или ссылаться» - это совсем другая история.