Как объединить два совпадающих объекта из разных массивов в один объект?

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

{
    "_id" : ObjectId("5a42432d69cbfed9a410e8ad"),
    "bacId" : "BAC0023444",
    "cardId" : "2",
    "defaultCardOrder" : "2",
    "alias" : "Finance",
    "label" : "Finance",
    "for" : "",
    "cardTooltip" : {
        "enable" : true,
        "text" : ""
    },
    "dataBlocks" : [
        {
            "defaultBlockOrder" : "1",
            "blockId" : "1",
            "data" : "0"
        },
        {
            "defaultBlockOrder" : "2",
            "blockId" : "2",
            "data" : "0"
        },
        {
            "defaultBlockOrder" : "3",
            "blockId" : "3",
            "data" : "0"
        }
    ],
    "templateBlocks" : [
        {
            "blockId" : "1",
            "label" : "Gross Profit",
            "quarter" : "",
            "data" : "",
            "dataType" : {
                "typeId" : "2"
            },
            "tooltip" : {
                "enable" : true,
                "text" : ""
            }
        },
        {
            "blockId" : "2",
            "label" : "Profit Forecast",
            "quarter" : "",
            "data" : "",
            "dataType" : {
                "typeId" : "2"
            },
            "tooltip" : {
                "enable" : true,
                "text" : ""
            }
        },
        {
            "blockId" : "3",
            "label" : "Resource Billing",
            "quarter" : "",
            "data" : "",
            "dataType" : {
                "typeId" : "2"
            },
            "tooltip" : {
                "enable" : true,
                "text" : ""
            }
        }
    ]
},
{
    "_id" : ObjectId("5a42432d69cbfed9a410e8ad"),
    "bacId" : "BAC0023444",
    "cardId" : "3",
    "defaultCardOrder" : "3",
    "alias" : "Staffing",
    "label" : "Staffing",
    "for" : "",
    "cardTooltip" : {
        "enable" : true,
        "text" : ""
    },
    "dataBlocks" : [
        {
            "defaultBlockOrder" : "1",
            "blockId" : "1",
            "data" : "1212"
        },
        {
            "defaultBlockOrder" : "2",
            "blockId" : "2",
            "data" : "1120"
        },
        {
            "defaultBlockOrder" : "3",
            "blockId" : "3",
            "data" : "1200"
        }
    ],
    "templateBlocks" : [
        {
            "blockId" : "1",
            "label" : "Staffing Planner",
            "quarter" : "",
            "data" : "",
            "dataType" : {
                "typeId" : "1"
            },
            "tooltip" : {
                "enable" : true,
                "text" : ""
            }
        },
        {
            "blockId" : "2",
            "label" : "Baseline",
            "quarter" : "",
            "data" : "",
            "dataType" : {
                "typeId" : "1"
            },
            "tooltip" : {
                "enable" : true,
                "text" : ""
            }
        },
        {
            "blockId" : "3",
            "label" : "Projected",
            "quarter" : "",
            "data" : "",
            "dataType" : {
                "typeId" : "1"
            },
            "tooltip" : {
                "enable" : true,
                "text" : ""
            }
        }
    ]
}

Теперь я хочу сравнить два массива объектов для каждой строки, в данном случае это «dataBlocks» и «templateBlocks» на основе «blockId», и я хочу получить результат в следующем формате.

{
    "_id" : ObjectId("5a42432d69cbfed9a410e8ad"),
    "bacId" : "BAC0023444",
    "cardId" : "2",
    "defaultCardOrder" : "2",
    "alias" : "Finance",
    "label" : "Finance",
    "for" : "",
    "cardTooltip" : {
        "enable" : true,
        "text" : ""
    },
    "blocks" : [
        {
            "defaultBlockOrder" : "1",
            "blockId" : "1",
            "data" : "0",
            "label" : "Gross Profit",
            "quarter" : "",
            "dataType" : {
                "typeId" : "2"
            },
            "tooltip" : {
                "enable" : true,
                "text" : ""
            }
        },
        {
            "defaultBlockOrder" : "2",
            "blockId" : "2",
            "data" : "0",
            "label" : "Profit Forecast",
            "quarter" : "",
            "dataType" : {
                "typeId" : "2"
            },
            "tooltip" : {
                "enable" : true,
                "text" : ""
            }
        },
        {
            "defaultBlockOrder" : "3",
            "blockId" : "3",
            "data" : "0",
            "label" : "Resource Billing",
            "quarter" : "",
            "dataType" : {
                "typeId" : "2"
            },
            "tooltip" : {
                "enable" : true,
                "text" : ""
            }
        }
    ]
},
{
    "_id" : ObjectId("5a42432d69cbfed9a410e8ad"),
    "bacId" : "BAC0023444",
    "cardId" : "3",
    "defaultCardOrder" : "3",
    "alias" : "Staffing",
    "label" : "Staffing",
    "for" : "",
    "cardTooltip" : {
        "enable" : true,
        "text" : ""
    },
    "dataBlocks" : [
        {
            "defaultBlockOrder" : "1",
            "blockId" : "1",
            "data" : "1212",
            "label" : "Staffing Planner",
            "quarter" : "",
            "dataType" : {
                "typeId" : "1"
            },
            "tooltip" : {
                "enable" : true,
                "text" : ""
            }
        },
        {
            "defaultBlockOrder" : "2",
            "blockId" : "2",
            "data" : "1120",
            "label" : "Baseline",
            "quarter" : "",
            "dataType" : {
                "typeId" : "1"
            },
            "tooltip" : {
                "enable" : true,
                "text" : ""
            }
        },
        {
            "defaultBlockOrder" : "3",
            "blockId" : "3",
            "data" : "1200",
            "label" : "Projected",
            "quarter" : "",
            "dataType" : {
                "typeId" : "1"
            },
            "tooltip" : {
                "enable" : true,
                "text" : ""
            }
        }
    ]
}

Можно ли это сделать с помощью mongodb? Я использую 3.4 и пытаюсь добиться этого с помощью агрегации.

Заранее спасибо.


person John Maclein    schedule 27.12.2017    source источник


Ответы (2)


Следующий запрос выполняет эту работу:

db.merge.aggregate([
  // unwind twice
  {$unwind: "$templateBlocks"},
  {$unwind: "$dataBlocks"},
  // get rid of documents where dataBlocks.blockId and 
  // templateBlocks.blockId are not equal
  {$redact: {$cond: [{
                        $eq: [
                               "$dataBlocks.blockId",
                               "$templateBlocks.blockId"
                             ]
                      },
                      "$$KEEP",
                      "$$PRUNE"
                    ]
            }
  },
  // merge dataBlocks and templateBlocks into a single document
  {$project: {
                bacId: 1,
                cardId: 1,
                defaultCardOrder: 1,
                alias: 1,
                label: 1,
                for: 1,
                cardTooltip: 1,
                dataBlocks: {
                              defaultBlockOrder: "$dataBlocks.defaultBlockOrder",
                              blockId: "$dataBlocks.blockId",
                              data: "$dataBlocks.data",
                              label: "$templateBlocks.label",
                              quarter: "$templateBlocks.quarter",
                              data: "$templateBlocks.data",
                              dataType: "$templateBlocks.dataType",
                              tooltip: "$templateBlocks.tooltip"
                            }
             }
      },
      // group to put correspondent dataBlocks to an array
      {$group: {
              _id: {
                     _id: "$_id",
                     bacId: "$bacId",
                     cardId: "$cardId",
                     defaultCardOrder: "$defaultCardOrder",
                     alias: "$alias",
                     label: "$label",
                     for: "$for",
                     cardTooltip: "$cardTooltip"
                   },
              dataBlocks: {$push: "$dataBlocks" }
           }
  },
  // remove the unnecessary _id object
  {$project: {
               _id: "$_id._id",
               bacId: "$_id.bacId",
               cardId: "$_id.cardId",
               defaultCardOrder: "$_id.defaultCardOrder",
               alias: "$_id.alias",
               label: "$_id.label",
               for: "$_id.for",
               cardTooltip: "$_id.cardTooltip",
               dataBlocks: "$dataBlocks"
             }
  }
])

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

person Andriy Simonov    schedule 27.12.2017
comment
Спасибо. Это спасло мой день. - person John Maclein; 27.12.2017

Вы можете попробовать агрегацию ниже в 3.6.

Приведенный ниже запрос повторяет массив dataBlocks и объединяет элемент блока данных с элементом блока шаблона. Блок шаблона просматривается с помощью $indexofArray, который находит индекс массива с соответствующим идентификатором блока, и $arrayElemAt для доступа к элементу по найденному индексу.

db.collection_name.aggregate([{"$addFields":{
  "blocks":{
    "$map":{
      "input":"$dataBlocks",
      "in":{
        "$mergeObjects":[
          "$$this",
          {"$arrayElemAt":[
            "$templateBlocks",
            {"$indexOfArray":["$templateBlocks.blockId","$$this.blockId"]}
            ]
          }
        ]
      }
    }
  }
}}])

Для версии 3.4 замените $mergeObjects комбинацией $arrayToObject, $objectToArray и $concatArrays, чтобы объединить каждый элемент массива из обоих массивов.

db.collection_name.aggregate([{"$addFields":{
  "blocks":{
    "$map":{
      "input":"$dataBlocks",
      "in":{
        "$arrayToObject":{
          "$concatArrays":[
            {"$objectToArray":"$$this"},
            {"$objectToArray":{
              "$arrayElemAt":[
                "$templateBlocks",
                {"$indexOfArray":["$templateBlocks.blockId","$$this.blockId"]
                }
              ]
            }}
          ]
        }
      }
    }
  }
}}])

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

{"$project":{"templateBlocks":0,"dataBlocks":0}}
person s7vr    schedule 27.12.2017
comment
Часть { "$indexOfArray":["$templateBlocks","$$this.blockId"] } не работает, так как templateBlocks содержит объекты, а не элементы, поэтому они не совпадают с данным blockId напрямую, их нужно каким-то образом сопоставить, чтобы вы могли ссылаться на свойство blockId каждого templateBlock. - person Amr Saber; 24.11.2019
comment
Я предложил редактирование с исправлением проблемы, упомянутой @Argento. В противном случае это определенно должен быть принятый ответ. - person Burawi; 17.03.2020
comment
@Argento, @Burawi, я только что добавил { "$indexOfArray":["$templateBlocks .blockId ","$$this.blockId"] }, и, похоже, это работает. - person Kenna; 11.06.2020