Объединить результат поиска $ с существующим массивом - PullRequest
0 голосов
/ 01 ноября 2018

Я новичок в Монго, и мне нужна ваша помощь.

У меня есть коллекция studijneProgramy . Это образец документа:

{
    "_id" : "dGFY",
    "garranti" : [
        {
            "typ" : {
                "sk" : "garant",
                "en" : "Chairman of study board"
            },
            "id" : "1025769"
        },
        {
            "typ" : {
                "sk" : "predseda odborovej komisie",
                "en" : "Chairman of study board"
            },
            "id" : "1025769"
        }
    ]
}

Далее у меня есть коллекция osoby .
Пример документа:

{
    "_id" : "1025769",
    "plneMeno" : "prof. RNDr. Peter Moczo, DrSc.",
    "priezvisko" : "Moczo",
    "meno" : "Peter",
    "jeGarantProgramu" : "dGFY/x"
}

Мне нужно добавить документы из osoby в соответствующий документ в массиве garranti (где studijneProgramy.garanti.id == osoby._id). Так что это мой желаемый результат:

{
    "_id" : "dGFY",
    "garranti" : [
        {
            "typ" : {
                "sk" : "garant",
                "en" : "Chairman of study board"
            },
            "id" : "1025769"
            "garant":{
                "_id" : "1025769",
                "plneMeno" : "prof. RNDr. Peter Moczo, DrSc.",
                "priezvisko" : "Moczo",
                "meno" : "Peter",
                "jeGarantProgramu" : "dGFY/x"
            }
        },
        {
            "typ" : {
                "sk" : "predseda odborovej komisie",
                "en" : "Chairman of study board"
            },
            "id" : "1025769"
            "garant":{
                "_id" : "1025769",
                "plneMeno" : "prof. RNDr. Peter Moczo, DrSc.",
                "priezvisko" : "Moczo",
                "meno" : "Peter",
                "jeGarantProgramu" : "dGFY/x"
            }
        }
    ]
}

Я попробовал эту агрегацию, но она заменила содержимое garranti .

db.studijneProgramy.aggregate([
{
    $lookup:
    {
        from:"osoby", 
        localField:"garranti.id",
        foreignField:"_id", 
        as:"garranti.garant"
    }
 }
]
).pretty()

Любая помощь будет принята с благодарностью!

Ответы [ 2 ]

0 голосов
/ 02 ноября 2018

MongoDB $lookup не будет "обновлять" элементы в существующем массиве совпадениями из коллекции "lookup". Он будет только выводить «массив» совпадений с заданными критериями, будь то сопоставленный с «существующим массивом» значений, как у вас, или с единичным значением.

Чтобы «объединить» записи с помощью «серверной» операции $lookup, вместо этого вам нужно будет выполнить одну из следующих опций, чтобы вернуться в нужную форму.

$ раскрутить массив первым

Самая простая форма - просто изменить структуру документов так, чтобы каждый элемент массива из источника был собственным документом в первую очередь , прежде чем вы действительно попытаетесь "объединить" связанную информацию:

db.studijneProgramy.aggregate([
  { "$unwind": "$garranti" },
  { "$lookup": {
    "from": "osoby",
    "as": "garranti.garrant",
    "localField": "garranti.id",
    "foreignField": "_id"
  }},
  { "$unwind": "$garranti.garrant" },
  { "$group": {
    "_id": "$_id",
    "garranti": { "$push": "$garranti" }
  }}
])

Поскольку исходный материал массива теперь представляет собой единичные документы, каждый из них получает только «массив» совпадений из объединенной коллекции. Это снова $unwind и, наконец, использование $group для $push до окончательной формы массива с «объединенными» записями.

Соотнесите "массивы"

Немного интереснее в версиях, которые его поддерживают, - использовать функции $indexOfArray и $arrayElemAt, чтобы "совпасть" с выходным массивом $lookup для существующих записей массива в документе:

db.studijneProgramy.aggregate([
  { "$lookup": {
    "from": "osoby",
    "as": "related",
    "localField": "garranti.id",
    "foreignField": "_id"
  }},
  { "$project": {
    "garranti": {
      "$map": {
        "input": "$garranti",
        "in": {
          "typ": "$$this.typ",
          "id": "$$this.id",
          "garrant": {
            "$arrayElemAt": [
              "$related",
              { "$indexOfArray": [ "$related._id", "$$this.id" ] }
            ]
          }
        }
      }
    }
  }}
])

Таким образом, поиск возвращает «массив совпадений» (related), и вы «просматриваете» соответствующие записи этих записей и переносите их в исходный массив документов с помощью $map. Конечно, для изменения формы документа требуется дополнительная ступень $project или аналогичная, поскольку вы не можете "нацелить" каждый элемент существующего массива в выводе $lookup, как упоминалось ранее.

Это на самом деле прямая корреляция на "сервере" того, что некоторые библиотеки, такие как "mongoose", делают для "эмуляции соединения на клиенте". По сути, «чужие» записи «сопоставляются» с существующим массивом.

Обработка под-трубопровода

Немного причудливее и длиннее - это еще одна альтернатива, использующая обработку "под-конвейера" Некоррелированного подзапроса , доступного от MongoDB 3.6 и выше. Здесь мы в основном выполняем манипуляции в «под-конвейере» $lookup вместо обработки на последующих этапах агрегирования:

db.studijneProgramy.aggregate([
  { "$lookup": {
    "from": "osoby",
    "as": "garranti",
    "let": { "garranti": "$garranti" },
    "pipeline": [
      { "$match": {
        "$expr": { "$in": [ "$_id", "$$garranti.id" ] } 
      }},
      { "$addFields": {
        "docs": {
          "$filter": {
            "input": "$$garranti",
            "cond": {
              "$eq": [ "$$this.id", "$_id" ]
            }
          }
        }
      }},
      { "$unwind": "$docs" },
      { "$replaceRoot": {
        "newRoot": {
          "$mergeObjects": [
            "$docs",
            { "garrant": {
              "$arrayToObject": {
                "$filter": { 
                  "input": { "$objectToArray": "$$ROOT" },
                  "cond": { "$ne": [ "$$this.k", "docs"] }
                }
              }
            }}
          ]
        }
      }}
    ]
  }}
])

Этот тип переворачивает операцию "над головой" и эффективно помещает "совпадающие элементы массива" из "исходного документа" в каждый соответствующий внешний элемент в виде массива.

Затем обработка эффективно использует $unwind в отфильтрованном исходном списке, а затем объединяет содержимое из внешней коллекции, так что теперь создается впечатление, что $lookup «выходной массив» на самом деле данные из «локального массива» теперь «объединены» с «чужим контентом».

На самом деле это просто более интересный вызов того же процесса $map, описанного выше, но выполняющий "корреляцию" записей до , результаты объединяются с исходным родительским документом, перезаписывающим свойство исходного массива.


Я думаю, что где-то есть JIRA, но у меня вроде есть ощущение, что "работает, как задумано" отмечен во всех таких отчетах, так что вряд ли он изменится с тем, что в настоящее время делает.

Таким образом, у вас было неправильное представление о том, что «соединение» «сливается» с записями массива «автоматически». Это не так.

Если вы действительно хотите «объединить вывод массива», то подходы, описанные выше, - это «серверный» подход.

0 голосов
/ 01 ноября 2018

Кроме потенциального дубликата, указанного в dege, вы можете использовать JS следующим образом, если данных не много. Использование агрегата и $ lookup было бы быстрее, но я считаю, что это чище.

db.studijneProgramy.find().forEach(stu=>{
    stu.garranti=stu.garranti.map(gar=>{
        gar.garant=db.osoby.find({_id:gar.id})[0];
        return gar;
    });
    db.studijneProgramy.save(stu);
});
...