MongoDB: Об’єднайте дані з кількох колекцій в одне .. як?


229

Як я можу (в MongoDB) об'єднати дані з кількох колекцій в одну колекцію?

Чи можу я використати зменшення карт і якщо так, то як?

Я б дуже вдячний за приклад, оскільки я є початківцем.


18
Ви просто хочете скопіювати документи з різних колекцій в одну колекцію чи який у вас план? Чи можете ви вказати "комбінувати"? Якщо ви просто хочете скопіювати через оболонку монго db.collection1.find().forEach(function(doc){db.collection2.save(doc)});, достатньо. Будь ласка, вкажіть використаний драйвер (java, php, ...), якщо ви не використовуєте оболонку mongo.
проксима

тож у мене є колекція (скажімо, користувачі), ніж у інших колекціях, йдеться про колекцію адресних книг, перелік колекцій книг тощо. Як я можу на основі клавіші сказати user_id об'єднати ці колекції лише в одну колекцію. ?
user697697

Відповіді:


147

Хоча ви не можете цього зробити в режимі реального часу, ви можете запустити зменшення карти в кілька разів, щоб об’єднати дані разом, використовуючи опцію "зменшити" на карті MongoDB 1.8+ / зменшити (див. Http://www.mongodb.org/ display / DOCS / MapReduce # MapReduce-Outputoptions ). Потрібно мати ключ в обох колекціях, який можна використовувати як _id.

Наприклад, скажімо, що у вас є usersколекція та commentsколекція, і ви хочете мати нову колекцію, яка містить певну демографічну інформацію користувача для кожного коментаря.

Скажімо, usersколекція має такі поля:

  • _id
  • ім'я
  • прізвище
  • країна
  • Стать
  • вік

І тоді commentsколекція має такі поля:

  • _id
  • ідентифікатор користувача
  • коментар
  • створено

Ви б зробили цю карту / зменшили:

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"});

Цю техніку точно не слід виконувати з льоту. Він підходить для роботи з крон або чогось подібного, що періодично оновлює об'єднані дані. Ви, ймовірно, захочете запустити ensureIndexнову колекцію, щоб переконатися, що запити, які ви виконуєте проти неї, запускаються швидко (майте на увазі, що ваші дані все ще знаходяться в valueключі, тому якщо ви індексували comments_with_demographicsчас коментарів created, було бdb.comments_with_demographics.ensureIndex({"value.created": 1});


1
Я б, мабуть, ніколи цього не робив у виробництві програмного забезпечення, але це все-таки злий класний прийом.
Дейв Гріффіт

3
Спасибі, Дейв. Я використовував цю методику для створення таблиць експорту та звітності для сайту з високим трафіком у виробництві протягом останніх 3 місяців без проблем. Ось ще одна стаття, яка описує подібне використання методики: tebros.com/2011/07/…
rmarscher

1
Дякую @rmarscher, твої додаткові деталі справді допомогли мені краще зрозуміти все.
benstr

5
Я повинен оновити цю відповідь на прикладі, використовуючи конвеєрний конвеєр та нову операцію пошуку $. Згадування про це, поки я не зможу скласти належну списання. docs.mongodb.org/manual/reference/operator/aggregation/lookup
rmarscher

1
FYI для тих, хто хоче швидко підкорити, що це робить, ось що в users_commentsколекції після першого блоку коду gist.github.com/nolanamy/83d7fb6a9bf92482a1c4311ad9c78835
Нолан Емі,

127

MongoDB 3.2 тепер дозволяє комбінувати дані з декількох колекцій в одну через етап агрегації $ пошуку . Як практичний приклад, скажімо, що у вас є дані про книги, розділені на дві різні колекції.

Перша колекція, що називається 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
        }
    ]
}

Важливо зазначити кілька речей:

  1. Колекцію "від" в цьому випадку books_selling_dataне можна оштрафувати.
  2. Поле "як" буде масивом, як у прикладі вище.
  3. На сцені $ пошуку як "localField", так і "ForeignField" параметри вважатимуться нульовими для відповідних цілей, якщо вони не існують у відповідних колекціях (документи $ lookup мають ідеальний приклад цього).

Отже, як висновок, якщо ви хочете об'єднати обидві колекції, маючи в цьому випадку плоске поле copy_sold із загальною кількістю проданих копій, вам доведеться трохи більше попрацювати, ймовірно, використовуючи посередницьку колекцію, яка буде, тоді, бути $ out до остаточної колекції.


привіт, люб'язно, ви можете сказати, яким буде оптимізований спосіб управління даними, як це: Користувач, file.files та file.chunks - це три колекції, я хочу, щоб конкретний користувач із усіма пов'язаними з ним файлами у відповідь це можливо.? {"name": "batMan", "email": "bt@gmail.com", "files": [{file1}, {file2}, {file3}, .... так далі]}
mfaisalhyder

Офіційні приклади документації для вищезазначеного рішення можна знайти тут: docs.mongodb.com/manual/reference/operator/aggregation/lookup
Якуб

4
Ну, насправді моя відповідь уже мала три посилання на офіційну документацію. Але все одно дякую за ваш внесок. @JakubCzaplicki
Бруно Кребс

2
У мене може виникнути загальна несправність мозку (швидше за все), але $lookupчи не повинні всі "localField" і "ForeignField" дорівнювати "isbn"? не "_id" і "isbn"?
Dev01

13

Якщо в mongodb немає об'ємної вставки, ми петлю всі об'єкти в small_collectionі вставляємо їх по одному в big_collection:

db.small_collection.find().forEach(function(obj){ 
   db.big_collection.insert(obj)
});

db.colleciton.insert ([{}, {}, {}]) Вставка приймає масиви.
аугурон

2
це добре працює для невеликих колекцій, але не забувайте мігрувати індекси :)
Себастьян Лорбер

12

Дуже базовий приклад з $ пошуку.

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

Тому що {$ unind: "$ userRoleData"} це поверне порожній або 0 результат, якщо не знайдено відповідного запису з $ пошуку.


11

Робити об'єднання в 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' } }
  ]);

Ось пояснення, як це працює:

  1. Інстанціювати в aggregateвід'їзд з будь- колекції вашої бази даних , яка містить , щонайменше , один документ в ньому. Якщо ви не можете гарантувати, що будь-яка колекція вашої бази даних не буде порожньою, ви можете вирішити цю проблему, створивши у вашій базі даних якусь "фіктивну" колекцію, що містить в ній єдиний порожній документ, який буде спеціально для виконання запитів об'єднання.

  2. Зробіть перший етап свого газопроводу { $limit: 1 }. Це позбавить усіх документів колекції, крім першого.

  3. Складіть усі поля документа, що залишився, використовуючи $projectетап:

    { $project: { _id: '$$REMOVE' } }
  4. Тепер ваша сукупність містить один порожній документ. Настав час додати огляди для кожної колекції, яку ви хочете об'єднати разом. Ви можете використовувати pipelineполе, щоб виконати певну фільтрацію, або залишити localFieldта foreignFieldяк null відповідати всій колекції.

    { $lookup: { from: 'collectionToUnion1', pipeline: [...], as: 'Collection1' } },
    { $lookup: { from: 'collectionToUnion2', pipeline: [...], as: 'Collection2' } },
    { $lookup: { from: 'collectionToUnion3', pipeline: [...], as: 'Collection3' } }
  5. Тепер у вас є агрегат, що містить один документ, який містить 3 масиви на зразок цього:

    {
        Collection1: [...],
        Collection2: [...],
        Collection3: [...]
    }

    Потім ви можете об'єднати їх разом в один масив, використовуючи $projectетап разом з $concatArraysоператором агрегації:

    {
      "$project" :
      {
        "Union" : { $concatArrays: ["$Collection1", "$Collection2", "$Collection3"] }
      }
    }
  6. Тепер у вас є сукупність, що містить один документ, в який розміщений масив, який містить ваш об'єднання колекцій. Залишилося лише додати $unwindі $replaceRootетап для розділення масиву на окремі документи:

    { $unwind: "$Union" },
    { $replaceRoot: { newRoot: "$Union" } }
  7. Voilà. Тепер у вас є набір результатів, що містить колекції, які ви хотіли об'єднати разом. Потім ви можете додати ще кілька етапів, щоб додатково його фільтрувати, сортувати, застосувати skip () та limit (). Практично все, що завгодно.


Помилка запиту з повідомленням "$ projection вимагає принаймні одне вихідне поле".
abhishek_ganta

@abhishek Якщо ви зрозумієте, це тому, що ви спробували зняти всі поля з одного документа на одному етапі проекції. MongoDB не дозволить вам це зробити. Щоб вирішити це, вам потрібно зробити 2 послідовних проекції, де перший знімає все, крім _id, а другий знімає решту _id.
sboisse

@abhishek Я ще більше спростив запит, замінивши етапи $ project на один, який використовує змінну '$$ REMOVE'. Я також додав конкретний приклад того, що ви можете просто скопіювати та вставити безпосередньо у тестер запитів, щоб переконатися, що він працює.
sboisse

@sboisse, це рішення працює для менших колекцій, проте, якщо я хочу це зробити у великих колекціях, (100 000+ документів), я стикаюся з "Загальний розмір документів у collectionToUnion1 перевищує максимальний розмір документа". У документах пропонується встановити $ unind безпосередньо після пошуку $, щоб уникнути створення великих проміжних документів. Мені не вдалося змінити це рішення за допомогою цього методу. Ви стикалися з цим питанням і вам довелося використовувати цей метод? Посилання на документи, на які я відмовляюсь : [посилання] ( docs.mongodb.com/manual/core/aggregation-pipeline-optimization/… )
lucky7samson

@ lucky7samson, на жаль, кількість даних, з якими мені довелося мати справу, була не такою великою. Тому мені не довелося стикатися з проблемою, про яку ви звертаєтесь. У моєму випадку я міг застосувати фільтрування на колекції для пошуку, перш ніж об'єднати записи з рештою, тому кількість даних для об'єднання була досить невеликою.
sboisse

9

використовувати декілька $ пошуку для декількох колекцій у сукупності

запит:

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

1

У Монгоресторе є така особливість додавання до того, що вже є в базі даних, тому така поведінка може бути використана для поєднання двох колекцій:

  1. колекція mongodump1
  2. collection2.rename (колекція1)
  3. Монгоресторе

Ще не спробував, але він може працювати швидше, ніж підхід до карти / зменшення.


1

Починаючи 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поєднує записи з даної колекції в документах, які вже є в конвеєрному конвеєрі. Після 2 етапів об'єднання, таким чином, у нас є всі записи користувачів, книг та фільмів.

  • Потім ми $groupзаписуємо $userта накопичуємо елементи за допомогою $accumulatorоператора, що дозволяє користувацьким накопиченням документів під час їх групування:

    • поля, які ми зацікавлені в накопиченні, визначаються accumulateArgs .
    • init визначає стан, який буде накопичуватися під час групування елементів.
    • accumulateфункція дозволяє виконувати користувальницькі дії із записом групується, щоб побудувати накопичене стан. Наприклад, якщо елемент, який групується, маєbook визначено поле, ми оновлюємо йогоbooks частину стану.
    • mergeвикористовується для злиття двох внутрішніх станів. Він використовується лише для агрегацій, що працюють на клаптикових кластерах або коли операція перевищує обмеження пам'яті.

чи можливо отримати аналогічний вихід для: 4.2.6 версія
Nixit Patel

0

Так, ви можете: Візьміть цю корисну функцію, про яку я написав сьогодні:

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);
        }
    )
  }
}

Ви можете передавати цій функції будь-яку кількість колекцій, перша буде цільовою. Усі решти колекцій є джерелами для передачі цільовій.


-1

Фрагмент коду Ввічливість - кілька повідомлень про переповнення стека, включаючи цю.

 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();

-2

Це потрібно зробити у вашому додатковому шарі. Якщо ви використовуєте ORM, він може використовувати анотації (або щось подібне) для витягування посилань, які існують в інших колекціях. Я працював лише з Morphia , і @Referenceанотація отримує посилання, коли воно запитується, тому я можу уникати цього робити в коді.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.