Встроить/объединить другую коллекцию в одну коллекцию

Я хочу объединить две коллекции mongodb.

В основном у меня есть коллекция, содержащая документы, которые ссылаются на один документ из другой коллекции. Теперь я хочу, чтобы это было встроенным/вложенным полем вместо отдельного документа.

Итак, просто для примера:

Коллекция А:

[{
    "_id":"90A26C2A-4976-4EDD-850D-2ED8BEA46F9E",
    "someValue": "foo"
  },
  {
    "_id":"5F0BB248-E628-4B8F-A2F6-FECD79B78354",
    "someValue": "bar"
  }]

Коллекция Б:

[{
    "_id":"169099A4-5EB9-4D55-8118-53D30B8A2E1A",
    "collectionAID":"90A26C2A-4976-4EDD-850D-2ED8BEA46F9E",
    "some":"foo",
    "andOther":"stuff"
  },
  {
    "_id":"83B14A8B-86A8-49FF-8394-0A7F9E709C13",
    "collectionAID":"90A26C2A-4976-4EDD-850D-2ED8BEA46F9E",
    "some":"bar",
    "andOther":"random"
   }]

В результате коллекция A должна выглядеть следующим образом:

[{
    "_id":"90A26C2A-4976-4EDD-850D-2ED8BEA46F9E",
    "someValue": "foo",
    "collectionB":[{
            "some":"foo",
            "andOther":"stuff"
            },{
            "some":"bar",
            "andOther":"random"
            }]
  },
  {
    "_id":"5F0BB248-E628-4B8F-A2F6-FECD79B78354",
    "someValue": "bar"
  }]

person lukaswelte    schedule 26.03.2014    source источник


Ответы (3)


Я бы предложил что-то простое из консоли:

db.collB.find().forEach(function(doc) {
    var aid = doc.collectionAID;
    if (typeof aid === 'undefined') { return; } // nothing
    delete doc["_id"]; // remove property
    delete doc["collectionAID"]; // remove property
    db.collA.update({_id: aid},   /* match the ID from B */
       { $push : { collectionB : doc }});
});

Он перебирает каждый документ в коллекцииB и, если определено поле collectionAID, удаляет ненужные свойства (_id и collectionAID). Наконец, он обновляет соответствующий документ в коллекции A, используя оператор $push для добавления документа из B в поле collectionB. Если поле не существует, оно автоматически создается как массив с вновь вставленным документом. Если он существует в виде массива, он будет добавлен. (Если он существует, но не является массивом, произойдет сбой). Поскольку вызов update не использует upsert, если _id в документе collectionB не существует, ничего не произойдет.

Вы можете расширить его, чтобы удалить другие поля по мере необходимости или, возможно, добавить более надежную обработку ошибок, если, например, документ из B не соответствует чему-либо в A.

Выполнение приведенного выше кода для ваших данных приводит к следующему:

{ "_id" : "5F0BB248-E628-4B8F-A2F6-FECD79B78354", "someValue" : "bar" }
{ "_id" : "90A26C2A-4976-4EDD-850D-2ED8BEA46F9E",
    "collectionB" : [
            {
                    "some" : "foo",
                    "andOther" : "stuff"
            },
            {
                    "some" : "bar",
                    "andOther" : "random"
            }
    ],
    "someValue" : "foo"
}
person WiredPrairie    schedule 27.03.2014

К сожалению, mapreduce не может создавать полные документы. https://jira.mongodb.org/browse/SERVER-2517

Понятия не имею, почему, несмотря на все внимание, нытье и голоса, они не изменили его. Поэтому вам придется сделать это вручную на выбранном вами языке.

Надеюсь, вы проиндексировали collectionAID, что должно повысить скорость ваших запросов. Просто напишите что-нибудь, что просматривает вашу коллекцию A по одному документу за раз, загружая _id, а затем добавляя массив из коллекции B.

person Will Shaver    schedule 26.03.2014
comment
MapReduce по-прежнему плохо подходит для этого, поскольку он пытается выполнить сложное слияние. - person WiredPrairie; 27.03.2014

Существует гораздо более быстрый способ, чем https://stackoverflow.com/a/22676205/1578508.

Вы можете сделать это наоборот и просмотреть коллекцию, в которую вы хотите вставить свои документы. (Гораздо меньше казней!)

db.collA.find().forEach(function (x) { var collBs = db.collB.find({"collectionAID":x._id},{"_id":0,"collectionA":0}); x.collectionB = collBs.toArray(); db.collA.save(x); })

person lukaswelte    schedule 01.04.2014
comment
Хотя это, возможно, быстрее (что было трудно сказать по предоставленной вами ограниченной информации), оно не так надежно и не очищает данные, как было предложено в примерах вашего вопроса. - person WiredPrairie; 01.04.2014
comment
Чтобы очистить, вы можете сделать db.collB.find({collectionAID:x._id}, {_id:0,collectionA:0}) вместо db.collB.find({collectionAID:x._id}) И if ( typeof help === 'undefined') { return; } можно сделать через db.collA.find(aid:{$exists:true})..... - person lukaswelte; 01.04.2014
comment
Я знаю, что по предоставленному ответу трудно сказать, но обычно, когда вы сокращаете массив документов из B в один документ в A, он должен быстрее извлекать массивы документов при итерации по A вместо итерации по каждому документу в B и нажатии его на документ в A - person lukaswelte; 01.04.2014
comment
Ваш ответ был абсолютно правильным и помог мне добраться до этого момента :) - person lukaswelte; 01.04.2014