MongoDB: объединить данные из нескольких коллекций в один.. как?
Как я могу (в MongoDB) объединить данные из нескольких коллекций в одну коллекцию?
Могу ли я использовать map-Reduce и если да, то как?
Я был бы очень признателен за пример, так как я новичок.
9 ответов
MongoDB 3.2 теперь позволяет объединять данные из нескольких коллекций в одну через этап агрегации $lookup. В качестве практического примера предположим, что у вас есть данные о книгах, разделенных на две разные коллекции.
Первая коллекция называется books
, имея следующие данные:
{
"isbn": "978-3-16-148410-0",
"title": "Some cool book",
"author": "John Doe"
}
{
"isbn": "978-3-16-148999-9",
"title": "Another awesome book",
"author": "Jane Roe"
}
И вторая коллекция под названием books_selling_data
, имея следующие данные:
{
"_id": ObjectId("56e31bcf76cdf52e541d9d26"),
"isbn": "978-3-16-148410-0",
"copies_sold": 12500
}
{
"_id": ObjectId("56e31ce076cdf52e541d9d28"),
"isbn": "978-3-16-148999-9",
"copies_sold": 720050
}
{
"_id": ObjectId("56e31ce076cdf52e541d9d29"),
"isbn": "978-3-16-148999-9",
"copies_sold": 1000
}
Чтобы объединить обе коллекции, достаточно использовать $lookup следующим образом:
db.books.aggregate([{
$lookup: {
from: "books_selling_data",
localField: "isbn",
foreignField: "isbn",
as: "copies_sold"
}
}])
После этой агрегации books
Коллекция будет выглядеть следующим образом:
{
"isbn": "978-3-16-148410-0",
"title": "Some cool book",
"author": "John Doe",
"copies_sold": [
{
"_id": ObjectId("56e31bcf76cdf52e541d9d26"),
"isbn": "978-3-16-148410-0",
"copies_sold": 12500
}
]
}
{
"isbn": "978-3-16-148999-9",
"title": "Another awesome book",
"author": "Jane Roe",
"copies_sold": [
{
"_id": ObjectId("56e31ce076cdf52e541d9d28"),
"isbn": "978-3-16-148999-9",
"copies_sold": 720050
},
{
"_id": ObjectId("56e31ce076cdf52e541d9d28"),
"isbn": "978-3-16-148999-9",
"copies_sold": 1000
}
]
}
Важно отметить несколько вещей:
- Коллекция "из", в данном случае
books_selling_data
не может быть осквернено. - Поле "as" будет массивом, как в примере выше.
- Параметры "localField" и "foreignField" на этапе $lookup будут рассматриваться как пустые для сопоставления, если они не существуют в соответствующих коллекциях ( документация $lookup имеет прекрасный пример по этому поводу).
Итак, в заключение, если вы хотите объединить обе коллекции, имея, в данном случае, плоское поле copy_sold с общим количеством проданных копий, вам придется работать немного больше, вероятно, используя промежуточную коллекцию, которая затем быть $ до окончательной коллекции.
Несмотря на то, что вы не можете сделать это в режиме реального времени, вы можете запустить map-Reduction несколько раз, чтобы объединить данные, используя опцию "Reduce" Out в MongoDB 1.8+ map/ Reduce (см. http://www.mongodb.org/display/DOCS/MapReduce). У вас должен быть ключ в обеих коллекциях, который вы можете использовать в качестве _id.
Например, скажем, у вас есть users
коллекция и comments
коллекция, и вы хотите иметь новую коллекцию, которая имеет демографические данные для каждого комментария.
Скажем users
Коллекция имеет следующие поля:
- _Я бы
- имя
- Фамилия
- страна
- Пол
- возраст
А потом comments
Коллекция имеет следующие поля:
- _Я бы
- Идентификатор пользователя
- комментарий
- созданный
Вы бы сделали эту карту / уменьшить:
var mapUsers, mapComments, reduce;
db.users_comments.remove();
// setup sample data - wouldn't actually use this in production
db.users.remove();
db.comments.remove();
db.users.save({firstName:"Rich",lastName:"S",gender:"M",country:"CA",age:"18"});
db.users.save({firstName:"Rob",lastName:"M",gender:"M",country:"US",age:"25"});
db.users.save({firstName:"Sarah",lastName:"T",gender:"F",country:"US",age:"13"});
var users = db.users.find();
db.comments.save({userId: users[0]._id, "comment": "Hey, what's up?", created: new ISODate()});
db.comments.save({userId: users[1]._id, "comment": "Not much", created: new ISODate()});
db.comments.save({userId: users[0]._id, "comment": "Cool", created: new ISODate()});
// end sample data setup
mapUsers = function() {
var values = {
country: this.country,
gender: this.gender,
age: this.age
};
emit(this._id, values);
};
mapComments = function() {
var values = {
commentId: this._id,
comment: this.comment,
created: this.created
};
emit(this.userId, values);
};
reduce = function(k, values) {
var result = {}, commentFields = {
"commentId": '',
"comment": '',
"created": ''
};
values.forEach(function(value) {
var field;
if ("comment" in value) {
if (!("comments" in result)) {
result.comments = [];
}
result.comments.push(value);
} else if ("comments" in value) {
if (!("comments" in result)) {
result.comments = [];
}
result.comments.push.apply(result.comments, value.comments);
}
for (field in value) {
if (value.hasOwnProperty(field) && !(field in commentFields)) {
result[field] = value[field];
}
}
});
return result;
};
db.users.mapReduce(mapUsers, reduce, {"out": {"reduce": "users_comments"}});
db.comments.mapReduce(mapComments, reduce, {"out": {"reduce": "users_comments"}});
db.users_comments.find().pretty(); // see the resulting collection
На данный момент у вас будет новая коллекция под названием users_comments
который содержит объединенные данные, и теперь вы можете использовать это. Эти уменьшенные коллекции все имеют _id
это ключ, который вы излучали в функциях вашей карты, и тогда все значения являются подобъектом внутри value
ключ - значения не находятся на верхнем уровне этих сокращенных документов.
Это несколько простой пример. Вы можете повторить это с большим количеством коллекций, сколько хотите, чтобы создать сокращенную коллекцию. Вы также можете сделать резюме и агрегацию данных в процессе. Скорее всего, вы бы определили более одной функции сокращения, поскольку логика для агрегирования и сохранения существующих полей становится более сложной.
Вы также заметите, что теперь существует один документ для каждого пользователя со всеми комментариями этого пользователя в массиве. Если бы мы объединяли данные, которые имеют отношение один-к-одному, а не один-ко-многим, это было бы бесполезно, и вы могли бы просто использовать функцию сокращения следующим образом:
reduce = function(k, values) {
var result = {};
values.forEach(function(value) {
var field;
for (field in value) {
if (value.hasOwnProperty(field)) {
result[field] = value[field];
}
}
});
return result;
};
Если вы хотите сгладить users_comments
коллекция, так что это один документ на комментарий, дополнительно запустите это:
var map, reduce;
map = function() {
var debug = function(value) {
var field;
for (field in value) {
print(field + ": " + value[field]);
}
};
debug(this);
var that = this;
if ("comments" in this.value) {
this.value.comments.forEach(function(value) {
emit(value.commentId, {
userId: that._id,
country: that.value.country,
age: that.value.age,
comment: value.comment,
created: value.created,
});
});
}
};
reduce = function(k, values) {
var result = {};
values.forEach(function(value) {
var field;
for (field in value) {
if (value.hasOwnProperty(field)) {
result[field] = value[field];
}
}
});
return result;
};
db.users_comments.mapReduce(map, reduce, {"out": "comments_with_demographics"});
Эта техника определенно не должна выполняться на лету. Он подходит для работы cron или чего-то подобного, который периодически обновляет объединенные данные. Вы, вероятно, захотите бежать ensureIndex
в новой коллекции, чтобы убедиться, что запросы, которые вы выполняете против нее, выполняются быстро (имейте в виду, что ваши данные все еще находятся внутри value
ключ, так что если вы должны индексировать comments_with_demographics
на комментарий created
время было бы db.comments_with_demographics.ensureIndex({"value.created": 1});
Создание объединений в MongoDB в режиме "SQL UNION" возможно с помощью агрегации и поиска в одном запросе. Вот пример, который я протестировал, который работает с MongoDB 4.0:
// Create employees data for testing the union.
db.getCollection('employees').insert({ name: "John", type: "employee", department: "sales" });
db.getCollection('employees').insert({ name: "Martha", type: "employee", department: "accounting" });
db.getCollection('employees').insert({ name: "Amy", type: "employee", department: "warehouse" });
db.getCollection('employees').insert({ name: "Mike", type: "employee", department: "warehouse" });
// Create freelancers data for testing the union.
db.getCollection('freelancers').insert({ name: "Stephany", type: "freelancer", department: "accounting" });
db.getCollection('freelancers').insert({ name: "Martin", type: "freelancer", department: "sales" });
db.getCollection('freelancers').insert({ name: "Doug", type: "freelancer", department: "warehouse" });
db.getCollection('freelancers').insert({ name: "Brenda", type: "freelancer", department: "sales" });
// Here we do a union of the employees and freelancers using a single aggregation query.
db.getCollection('freelancers').aggregate( // 1. Use any collection containing at least one document.
[
{ $limit: 1 }, // 2. Keep only one document of the collection.
{ $project: { _id: '$$REMOVE' } }, // 3. Remove everything from the document.
// 4. Lookup collections to union together.
{ $lookup: { from: 'employees', pipeline: [{ $match: { department: 'sales' } }], as: 'employees' } },
{ $lookup: { from: 'freelancers', pipeline: [{ $match: { department: 'sales' } }], as: 'freelancers' } },
// 5. Union the collections together with a projection.
{ $project: { union: { $concatArrays: ["$employees", "$freelancers"] } } },
// 6. Unwind and replace root so you end up with a result set.
{ $unwind: '$union' },
{ $replaceRoot: { newRoot: '$union' } }
]);
Вот объяснение того, как это работает:
Создать
aggregate
из любой коллекции вашей базы данных, в которой есть хотя бы один документ. Если вы не можете гарантировать, что любая коллекция вашей базы данных не будет пустой, вы можете обойти эту проблему, создав в своей базе данных своего рода "фиктивную" коллекцию, содержащую один пустой документ, который будет там специально для выполнения запросов на объединение.Сделать первый этап вашего конвейера, чтобы быть
{ $limit: 1 }
, Это лишит все документы коллекции кроме первого.Удалите все поля оставшегося документа, используя
$project
этап:{ $project: { _id: '$$REMOVE' } }
Ваш агрегат теперь содержит один пустой документ. Пришло время добавить поиск для каждой коллекции, которую вы хотите объединить вместе. Вы можете использовать
pipeline
поле для выполнения определенной фильтрации или оставитьlocalField
а такжеforeignField
как ноль, чтобы соответствовать всей коллекции.{ $lookup: { from: 'collectionToUnion1', pipeline: [...], as: 'Collection1' } }, { $lookup: { from: 'collectionToUnion2', pipeline: [...], as: 'Collection2' } }, { $lookup: { from: 'collectionToUnion3', pipeline: [...], as: 'Collection3' } }
Теперь у вас есть агрегат, содержащий один документ, который содержит 3 массива, например:
{ Collection1: [...], Collection2: [...], Collection3: [...] }
Затем вы можете объединить их в один массив, используя
$project
этап вместе с$concatArrays
оператор агрегации:{ "$project" : { "Union" : { $concatArrays: ["$Collection1", "$Collection2", "$Collection3"] } } }
Теперь у вас есть агрегат, содержащий один документ, в котором расположен массив, содержащий ваше объединение коллекций. Что еще нужно сделать, это добавить
$unwind
и$replaceRoot
Этап разделения вашего массива на отдельные документы:{ $unwind: "$Union" }, { $replaceRoot: { newRoot: "$Union" } }
Вуаля. Теперь у вас есть набор результатов, содержащий коллекции, которые вы хотите объединить вместе. Затем вы можете добавить дополнительные этапы для дальнейшей фильтрации, сортировки, применения skip() и limit(). Практически все, что вы хотите.
Запуск Mongo 4.4
, мы можем добиться этого соединения в конвейере агрегации, подключив новый $unionWith
стадия агрегирования с $group
новый $accumulator
оператор:
// > db.users.find()
// [{ user: 1, name: "x" }, { user: 2, name: "y" }]
// > db.books.find()
// [{ user: 1, book: "a" }, { user: 1, book: "b" }, { user: 2, book: "c" }]
// > db.movies.find()
// [{ user: 1, movie: "g" }, { user: 2, movie: "h" }, { user: 2, movie: "i" }]
db.users.aggregate([
{ $unionWith: "books" },
{ $unionWith: "movies" },
{ $group: {
_id: "$user",
user: {
$accumulator: {
accumulateArgs: ["$name", "$book", "$movie"],
init: function() { return { books: [], movies: [] } },
accumulate: function(user, name, book, movie) {
if (name) user.name = name;
if (book) user.books.push(book);
if (movie) user.movies.push(movie);
return user;
},
merge: function(userV1, userV2) {
if (userV2.name) userV1.name = userV2.name;
userV1.books.concat(userV2.books);
userV1.movies.concat(userV2.movies);
return userV1;
},
lang: "js"
}
}
}}
])
// { _id: 1, user: { books: ["a", "b"], movies: ["g"], name: "x" } }
// { _id: 2, user: { books: ["c"], movies: ["h", "i"], name: "y" } }
$unionWith
объединяет записи из данной коллекции в документы, уже находящиеся в конвейере агрегирования. Таким образом, после двух этапов объединения у нас есть все записи о пользователях, книгах и фильмах.Мы тогда
$group
записи$user
и накапливайте предметы, используя$accumulator
оператор, позволяющий произвольно накапливать документы по мере их группировки:- поля, которые мы хотим накапливать, определяются с помощью
accumulateArgs
. init
определяет состояние, которое будет накапливаться при группировании элементов.- то
accumulate
Функция позволяет выполнять настраиваемое действие с группируемой записью для построения накопленного состояния. Например, если группируемый элемент имеетbook
определено поле, затем мы обновляемbooks
часть государства. merge
используется для объединения двух внутренних состояний. Он используется только для агрегатов, работающих на сегментированных кластерах, или когда операция превышает ограничения памяти.
- поля, которые мы хотим накапливать, определяются с помощью
Очень простой пример с $lookup.
db.getCollection('users').aggregate([
{
$lookup: {
from: "userinfo",
localField: "userId",
foreignField: "userId",
as: "userInfoData"
}
},
{
$lookup: {
from: "userrole",
localField: "userId",
foreignField: "userId",
as: "userRoleData"
}
},
{ $unwind: { path: "$userInfoData", preserveNullAndEmptyArrays: true }},
{ $unwind: { path: "$userRoleData", preserveNullAndEmptyArrays: true }}
])
Здесь используется
{ $unwind: { path: "$userInfoData", preserveNullAndEmptyArrays: true }},
{ $unwind: { path: "$userRoleData", preserveNullAndEmptyArrays: true }}
Вместо
{ $unwind:"$userRoleData"}
{ $unwind:"$userRoleData"}
Потому что { $unwind:"$userRoleData"} возвращает пустой или 0 результат, если не найдена соответствующая запись с $lookup.
Использовать множественный поиск $ для нескольких коллекций в агрегации
запрос:
db.getCollection('servicelocations').aggregate([
{
$match: {
serviceLocationId: {
$in: ["36728"]
}
}
},
{
$lookup: {
from: "orders",
localField: "serviceLocationId",
foreignField: "serviceLocationId",
as: "orders"
}
},
{
$lookup: {
from: "timewindowtypes",
localField: "timeWindow.timeWindowTypeId",
foreignField: "timeWindowTypeId",
as: "timeWindow"
}
},
{
$lookup: {
from: "servicetimetypes",
localField: "serviceTimeTypeId",
foreignField: "serviceTimeTypeId",
as: "serviceTime"
}
},
{
$unwind: "$orders"
},
{
$unwind: "$serviceTime"
},
{
$limit: 14
}
])
результат:
{
"_id" : ObjectId("59c3ac4bb7799c90ebb3279b"),
"serviceLocationId" : "36728",
"regionId" : 1.0,
"zoneId" : "DXBZONE1",
"description" : "AL HALLAB REST EMIRATES MALL",
"locationPriority" : 1.0,
"accountTypeId" : 1.0,
"locationType" : "SERVICELOCATION",
"location" : {
"makani" : "",
"lat" : 25.119035,
"lng" : 55.198694
},
"deliveryDays" : "MTWRFSU",
"timeWindow" : [
{
"_id" : ObjectId("59c3b0a3b7799c90ebb32cde"),
"timeWindowTypeId" : "1",
"Description" : "MORNING",
"timeWindow" : {
"openTime" : "06:00",
"closeTime" : "08:00"
},
"accountId" : 1.0
},
{
"_id" : ObjectId("59c3b0a3b7799c90ebb32cdf"),
"timeWindowTypeId" : "1",
"Description" : "MORNING",
"timeWindow" : {
"openTime" : "09:00",
"closeTime" : "10:00"
},
"accountId" : 1.0
},
{
"_id" : ObjectId("59c3b0a3b7799c90ebb32ce0"),
"timeWindowTypeId" : "1",
"Description" : "MORNING",
"timeWindow" : {
"openTime" : "10:30",
"closeTime" : "11:30"
},
"accountId" : 1.0
}
],
"address1" : "",
"address2" : "",
"phone" : "",
"city" : "",
"county" : "",
"state" : "",
"country" : "",
"zipcode" : "",
"imageUrl" : "",
"contact" : {
"name" : "",
"email" : ""
},
"status" : "ACTIVE",
"createdBy" : "",
"updatedBy" : "",
"updateDate" : "",
"accountId" : 1.0,
"serviceTimeTypeId" : "1",
"orders" : [
{
"_id" : ObjectId("59c3b291f251c77f15790f92"),
"orderId" : "AQ18O1704264",
"serviceLocationId" : "36728",
"orderNo" : "AQ18O1704264",
"orderDate" : "18-Sep-17",
"description" : "AQ18O1704264",
"serviceType" : "Delivery",
"orderSource" : "Import",
"takenBy" : "KARIM",
"plannedDeliveryDate" : ISODate("2017-08-26T00:00:00.000Z"),
"plannedDeliveryTime" : "",
"actualDeliveryDate" : "",
"actualDeliveryTime" : "",
"deliveredBy" : "",
"size1" : 296.0,
"size2" : 3573.355,
"size3" : 240.811,
"jobPriority" : 1.0,
"cancelReason" : "",
"cancelDate" : "",
"cancelBy" : "",
"reasonCode" : "",
"reasonText" : "",
"status" : "",
"lineItems" : [
{
"ItemId" : "BNWB020",
"size1" : 15.0,
"size2" : 78.6,
"size3" : 6.0
},
{
"ItemId" : "BNWB021",
"size1" : 20.0,
"size2" : 252.0,
"size3" : 11.538
},
{
"ItemId" : "BNWB023",
"size1" : 15.0,
"size2" : 285.0,
"size3" : 16.071
},
{
"ItemId" : "CPMW112",
"size1" : 3.0,
"size2" : 25.38,
"size3" : 1.731
},
{
"ItemId" : "MMGW001",
"size1" : 25.0,
"size2" : 464.375,
"size3" : 46.875
},
{
"ItemId" : "MMNB218",
"size1" : 50.0,
"size2" : 920.0,
"size3" : 60.0
},
{
"ItemId" : "MMNB219",
"size1" : 50.0,
"size2" : 630.0,
"size3" : 40.0
},
{
"ItemId" : "MMNB220",
"size1" : 50.0,
"size2" : 416.0,
"size3" : 28.846
},
{
"ItemId" : "MMNB270",
"size1" : 50.0,
"size2" : 262.0,
"size3" : 20.0
},
{
"ItemId" : "MMNB302",
"size1" : 15.0,
"size2" : 195.0,
"size3" : 6.0
},
{
"ItemId" : "MMNB373",
"size1" : 3.0,
"size2" : 45.0,
"size3" : 3.75
}
],
"accountId" : 1.0
},
{
"_id" : ObjectId("59c3b291f251c77f15790f9d"),
"orderId" : "AQ137O1701240",
"serviceLocationId" : "36728",
"orderNo" : "AQ137O1701240",
"orderDate" : "18-Sep-17",
"description" : "AQ137O1701240",
"serviceType" : "Delivery",
"orderSource" : "Import",
"takenBy" : "KARIM",
"plannedDeliveryDate" : ISODate("2017-08-26T00:00:00.000Z"),
"plannedDeliveryTime" : "",
"actualDeliveryDate" : "",
"actualDeliveryTime" : "",
"deliveredBy" : "",
"size1" : 28.0,
"size2" : 520.11,
"size3" : 52.5,
"jobPriority" : 1.0,
"cancelReason" : "",
"cancelDate" : "",
"cancelBy" : "",
"reasonCode" : "",
"reasonText" : "",
"status" : "",
"lineItems" : [
{
"ItemId" : "MMGW001",
"size1" : 25.0,
"size2" : 464.38,
"size3" : 46.875
},
{
"ItemId" : "MMGW001-F1",
"size1" : 3.0,
"size2" : 55.73,
"size3" : 5.625
}
],
"accountId" : 1.0
},
{
"_id" : ObjectId("59c3b291f251c77f15790fd8"),
"orderId" : "AQ110O1705036",
"serviceLocationId" : "36728",
"orderNo" : "AQ110O1705036",
"orderDate" : "18-Sep-17",
"description" : "AQ110O1705036",
"serviceType" : "Delivery",
"orderSource" : "Import",
"takenBy" : "KARIM",
"plannedDeliveryDate" : ISODate("2017-08-26T00:00:00.000Z"),
"plannedDeliveryTime" : "",
"actualDeliveryDate" : "",
"actualDeliveryTime" : "",
"deliveredBy" : "",
"size1" : 60.0,
"size2" : 1046.0,
"size3" : 68.0,
"jobPriority" : 1.0,
"cancelReason" : "",
"cancelDate" : "",
"cancelBy" : "",
"reasonCode" : "",
"reasonText" : "",
"status" : "",
"lineItems" : [
{
"ItemId" : "MMNB218",
"size1" : 50.0,
"size2" : 920.0,
"size3" : 60.0
},
{
"ItemId" : "MMNB219",
"size1" : 10.0,
"size2" : 126.0,
"size3" : 8.0
}
],
"accountId" : 1.0
}
],
"serviceTime" : {
"_id" : ObjectId("59c3b07cb7799c90ebb32cdc"),
"serviceTimeTypeId" : "1",
"serviceTimeType" : "nohelper",
"description" : "",
"fixedTime" : 30.0,
"variableTime" : 0.0,
"accountId" : 1.0
}
}
Если нет массовой вставки в mongodb, мы зациклим все объекты в small_collection
и вставьте их один за другим в big_collection
:
db.small_collection.find().forEach(function(obj){
db.big_collection.insert(obj)
});
Mongorestore имеет эту функцию добавления поверх того, что уже есть в базе данных, поэтому это поведение можно использовать для объединения двух коллекций:
- mongodump collection1
- collection2.rename (collection1)
- mongorestore
Пока не пробовал, но он может работать быстрее, чем подход "карта / уменьшение".
Да, вы можете: Возьмите эту служебную функцию, которую я написал сегодня:
function shangMergeCol() {
tcol= db.getCollection(arguments[0]);
for (var i=1; i<arguments.length; i++){
scol= db.getCollection(arguments[i]);
scol.find().forEach(
function (d) {
tcol.insert(d);
}
)
}
}
Вы можете передать в эту функцию любое количество коллекций, первая из которых будет целевой. Все остальные коллекции являются источниками, которые необходимо перенести в целевой.
Фрагмент кода. Courtesy-Несколько сообщений о переполнении стека, включая этот.
db.cust.drop();
db.zip.drop();
db.cust.insert({cust_id:1, zip_id: 101});
db.cust.insert({cust_id:2, zip_id: 101});
db.cust.insert({cust_id:3, zip_id: 101});
db.cust.insert({cust_id:4, zip_id: 102});
db.cust.insert({cust_id:5, zip_id: 102});
db.zip.insert({zip_id:101, zip_cd:'AAA'});
db.zip.insert({zip_id:102, zip_cd:'BBB'});
db.zip.insert({zip_id:103, zip_cd:'CCC'});
mapCust = function() {
var values = {
cust_id: this.cust_id
};
emit(this.zip_id, values);
};
mapZip = function() {
var values = {
zip_cd: this.zip_cd
};
emit(this.zip_id, values);
};
reduceCustZip = function(k, values) {
var result = {};
values.forEach(function(value) {
var field;
if ("cust_id" in value) {
if (!("cust_ids" in result)) {
result.cust_ids = [];
}
result.cust_ids.push(value);
} else {
for (field in value) {
if (value.hasOwnProperty(field) ) {
result[field] = value[field];
}
};
}
});
return result;
};
db.cust_zip.drop();
db.cust.mapReduce(mapCust, reduceCustZip, {"out": {"reduce": "cust_zip"}});
db.zip.mapReduce(mapZip, reduceCustZip, {"out": {"reduce": "cust_zip"}});
db.cust_zip.find();
mapCZ = function() {
var that = this;
if ("cust_ids" in this.value) {
this.value.cust_ids.forEach(function(value) {
emit(value.cust_id, {
zip_id: that._id,
zip_cd: that.value.zip_cd
});
});
}
};
reduceCZ = function(k, values) {
var result = {};
values.forEach(function(value) {
var field;
for (field in value) {
if (value.hasOwnProperty(field)) {
result[field] = value[field];
}
}
});
return result;
};
db.cust_zip_joined.drop();
db.cust_zip.mapReduce(mapCZ, reduceCZ, {"out": "cust_zip_joined"});
db.cust_zip_joined.find().pretty();
var flattenMRCollection=function(dbName,collectionName) {
var collection=db.getSiblingDB(dbName)[collectionName];
var i=0;
var bulk=collection.initializeUnorderedBulkOp();
collection.find({ value: { $exists: true } }).addOption(16).forEach(function(result) {
print((++i));
//collection.update({_id: result._id},result.value);
bulk.find({_id: result._id}).replaceOne(result.value);
if(i%1000==0)
{
print("Executing bulk...");
bulk.execute();
bulk=collection.initializeUnorderedBulkOp();
}
});
bulk.execute();
};
flattenMRCollection("mydb","cust_zip_joined");
db.cust_zip_joined.find().pretty();
Вы должны сделать это на уровне приложения. Если вы используете ORM, он может использовать аннотации (или что-то подобное) для извлечения ссылок, которые существуют в других коллекциях. Я работал только с Морфией, и @Reference
аннотация получает запрашиваемую сущность при запросе, поэтому я могу избежать этого в коде.