Свяжите каждый элемент массива в документе с соответствующим элементом в массиве другого документа с MongoDB - PullRequest
1 голос
/ 13 февраля 2020

Использование MongoDB 4.2 и MongoDB Atlas для тестирования конвейеров агрегации.

У меня есть коллекция products , содержащая документы с этой схемой:

 {
    "name": "TestProduct",
    "relatedList": [
      {id:ObjectId("someId")},
      {id:ObjectId("anotherId")}
    ]
 }

Тогда есть эта коллекция городов , содержащая документы с этой схемой:

{
        "name": "TestCity",
        "instructionList": [
          { related_id: ObjectId("anotherId"), foo: bar},
          { related_id: ObjectId("someId"), foo: bar}
          { related_id: ObjectId("notUsefulId"), foo: bar}
          ...
        ]
 }

Моя цель - объединить обе коллекции, чтобы вывести что-то вроде этого (операция - выбор каждого связанного объекта из инструкции в городской документ, чтобы поместить его в relatedList документа продукта):

{
        "name": "TestProduct",
        "relatedList": [
          { related_id: ObjectId("someId"), foo: bar},
          { related_id: ObjectId("anotherId"), foo: bar},
        ]
}

Я пытался использовать оператор поиска $ для агрегирования, например this :

$lookup:{
  from: 'cities',
  let: {rId:'$relatedList._id'},
  pipeline: [
         {
           $match: {
             $expr: {
               $eq: ["$instructionList.related_id", "$$rId"]
             }
           }
         },
  ]
}

Но это не работает, я немного потерян с этим сложным конвейерным синтаксисом.

Редактировать

Используя размотку на обоих массивах:

    { 
         {$unwind: "$relatedList"},
         {$lookup:{
             from: "cities",
             let: { "rId": "$relatedList.id" },
             pipeline: [

                {$unwind:"$instructionList"},
                {$match:{$expr:{$eq:["$instructionList.related_id","$$rId"]}}},

             ],
             as:"instructionList",
         }},

         {$group: {
             _id: "$_id",
             instructionList: {$addToSet:"$instructionList"}

          }}
}

Я могу однако, чтобы достичь того, чего я хочу, я не получаю чистого результата:

{
 "name": "TestProduct",
 instructionList: [
    [
      {
        "name": "TestCity",
        "instructionList": {
         "related_id":ObjectId("someId")
        }
      }
    ],
    [
      {
        "name": "TestCity",
        "instructionList": {
         "related_id":ObjectId("anotherId")
        }
      }
    ]
 ]
}

Как я могу сгруппировать все так, чтобы оно было таким же чистым, как указано для моего исходного вопроса? Опять же, я полностью потерялся в структуре агрегации.

Ответы [ 2 ]

0 голосов
/ 13 февраля 2020

операция выбирает каждый связанный объект из списка инструкций в городском документе, чтобы поместить его в связанный список документа продукта)

Приведенный пример документа в коллекции cities:

{"_id": ObjectId("5e4a22a08c54c8e2380b853b"),
  "name": "TestCity",
  "instructionList": [
    {"related_id": "a", "foo": "x"},
    {"related_id": "b", "foo": "y"},
    {"related_id": "c", "foo": "z"}
]}

и пример документа для коллекции products:

{"_id": ObjectId("5e45cdd8e8d44a31a432a981"),
  "name": "TestProduct",
  "relatedList": [
    {"id": "a"},
    {"id": "b"}
]}

Попробуйте выполнить следующий конвейер агрегации:

db.products.aggregate([
    {"$lookup":{
        "from": "cities", 
        "let": { "rId": "$relatedList.id" }, 
        "pipeline": [
            {"$unwind":"$instructionList"},
            {"$match":{
                "$expr":{
                    "$in":["$instructionList.related_id", "$$rId"]
                }
            }
        }], 
        "as":"relatedList",
    }}, 
    {"$project":{
        "name":"$name",
        "relatedList":{
            "$map":{
                "input":"$relatedList",
                "as":"x",
                "in":{
                    "related_id":"$$x.instructionList.related_id",
                    "foo":"$$x.instructionList.foo"
                }                
            }
        }
    }}
]);

Чтобы получить результат выглядит следующим образом:

{  "_id": ObjectId("5e45cdd8e8d44a31a432a981"),
   "name": "TestProduct",
   "relatedList": [
          {"related_id": "a", "foo": "x"},
          {"related_id": "b", "foo": "y"}
]}

Выше тестируется в MongoDB v4.2.x.

Но это не работает, я немного растерялся с этим сложным конвейером синтаксис.

Причина, по которой он немного сложен, заключается в том, что у вас есть массив relatedList, а также массив вложенных документов instructionList. Когда вы ссылаетесь на instructionList.related_id (что может означать несколько значений) с оператором $eq, конвейер не знает, какое из них сопоставить.

В приведенном выше конвейере я добавил $ unwind stage, чтобы превратить instructionList в несколько отдельных документов. Впоследствии, используя $ в до express совпадение одного значения instructionList.related_id в массиве relatedList.

0 голосов
/ 13 февраля 2020

Я полагаю, что вам просто нужно $ развернуть массивы, чтобы найти отношение, а затем $ group, чтобы вспомнить их. Возможно что-то вроде:

.aggregeate([
    {$unwind:"relatedList"},
    {$lookup:{
         from:"cities",
         let:{rId:"$relatedList.id"}
         pipeline:[
             {$match:{$expr:{$eq:["$instructionList.related_id", "$$rId"]}}},
             {$unwind:"$instructionList"},
             {$match:{$expr:{$eq:["$instructionList.related_id", "$$rId"]}}},
             {$project:{_id:0, instruction:"$instructionList"}}
         ],
         as: "lookedup"
     }},
     {$addFields: {"relatedList.foo":"$lookedup.0.instruction.foo"}},
     {$group: {
                _id:"$_id",
                root: {$first:"$$ROOT"},
                relatedList:{$push:"$relatedList"}
     }},
     {$addFields:{"root.relatedList":"$relatedList"}},
     {$replaceRoot:{newRoot:"$root"}}
])

Немного о каждом этапе:

  • $ unwind дублирует весь документ для каждого элемента массива, замените массив одним элементом
  • $ lookup может рассматривать каждый элемент отдельно. Этапы в $ lookup.pipeline:
    a. $ match, поэтому мы раскручиваем документ только с идентификатором
    b. $ раскрутить массив, чтобы мы могли рассмотреть отдельные элементы
    c. Повторите $ match, чтобы у нас остались только совпадающие элементы (возможно, только 1)
  • $ addFields назначает поле foo, полученное из поиска, для объекта из группы relatedList
  • $ собирает вместе все документы с одинаковым _id (то есть, которые были размотаны из одного оригинального документа), сохраняет первый как 'root' и помещает все элементы relatedList обратно в массив
  • $ addFields перемещает relatedList в root
  • $ replace Root возвращает root, который теперь должен быть исходным документом с соответствующим foo, добавленным к каждому relatedList элементу
...