Вложенный запрос агрегации Mongodb

#mongodb #aggregation

#mongodb #агрегация

Вопрос:

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

У меня есть несколько коллекций, включая ticket, fields и fieldOptions, которые выглядят примерно так:

Билет:

 {
    _id: 1,
    subject: "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
    fields: [
        {
            key: "part-number",
            value: "abc123",
        },
        {
            key: "price",
            value: "10",
        },
        {
            key: "officer",
            value: "2",
        },
    ]
}
  

Поля:

 [
    {
        _id: 1,
        created: ISODate("2020-09-10T20:23:46.382Z"),
        options: [],
        updated: ISODate("2020-09-10T20:23:46.382Z"),
        active: true,
        displayOrder: 0,
        key: "part-number",
        label: "Part Number",
        required: false,
        type: "text",
    },
    {
        _id: 2,
        created: ISODate("2020-09-10T20:23:46.382Z"),
        options: [],
        updated: ISODate("2020-09-10T20:23:46.382Z"),
        active: true,
        displayOrder: 1,
        key: "price",
        label: "Price",
        required: false,
        type: "text",
    },
    {
        _id: 3,
        created: ISODate("2020-09-10T20:23:46.382Z"),
        options: [1, 2, 3, 4],
        updated: ISODate("2020-09-10T20:23:46.382Z"),
        active: true,
        displayOrder: 2,
        key: "officer",
        label: "Officer",
        required: false,
        type: "select",
    },
    // THIS WAS ADDED AFTER THE 'TICKET' DOC WAS CREATED, SO IT'S NOT LISTED UNDER 'FIELDS' IN THERE
    {
        _id: 4,
        created: ISODate("2020-09-10T20:23:46.382Z"),
        options: [],
        updated: ISODate("2020-09-10T20:23:46.382Z"),
        active: true,
        displayOrder: 1,
        key: "notes",
        label: "Notes",
        required: false,
        type: "text",
    },
]
  

Параметры поля:

 [
    {
        _id: 1,
        created: ISODate("2020-09-10T20:23:46.382Z"),
        updated: ISODate("2020-09-10T20:23:46.382Z"),
        active: true,
        key: "none",
        label: "None",
        displayOrder: 0,
        legacy: false,
    },
    {
        _id: 2,
        created: ISODate("2020-09-10T20:23:46.382Z"),
        updated: ISODate("2020-09-10T20:23:46.382Z"),
        active: true,
        key: "picard",
        label: "Picard",
        displayOrder: 1,
        legacy: false,
    },
    {
        _id: 3,
        created: ISODate("2020-09-10T20:23:46.382Z"),
        updated: ISODate("2020-09-10T20:23:46.382Z"),
        active: true,
        key: "riker",
        label: "Riker",
        displayOrder: 2,
        legacy: false,
    },
    {
        _id: 4,
        created: ISODate("2020-09-10T20:23:46.382Z"),
        updated: ISODate("2020-09-10T20:23:46.382Z"),
        active: true,
        key: "data",
        label: "Data",
        displayOrder: 3,
        legacy: false,
    },
    {
        _id: 5,
        created: ISODate("2020-09-10T20:23:46.382Z"),
        updated: ISODate("2020-09-10T20:23:46.382Z"),
        active: true,
        key: "red-shirt",
        label: "Red Shirt",
        displayOrder: 4,
        legacy: true,
    },
]
  

In my aggregation pipeline, I have the following:

 [
    { $match: {_id: 2} },
    { $unwind: { path: "$fields", "preserveNullAndEmptyArrays": true } },
            {
                "$lookup": {
                    "from": "fields",
                    "let": { "key": "$fields.key" },
                    "as": "fields.def",
                    "pipeline": [
                        { "$match": { "$expr": { "$eq": ["$key", "$$key"] } } },
                        {
                            "$lookup": {
                                "from": "fieldoptions",
                                "let": { "options": "$options" },
                                "pipeline": [
                                    { "$match": { "$expr": { "$in": ["$_id", "$$options"] } } },
                                ],
                                "as": "options"
                            }
                        }
                    ]
                }
            },
            { $unwind: { path: "$fields.def", "preserveNullAndEmptyArrays": true } },
            {
                $group: {
                    _id: "$_id",
                    fields: { $push: "$$ROOT.fields" },
                    root: { $mergeObjects: "$$ROOT" },
                }
            },
            {
                $replaceRoot: {
                    newRoot: {
                        $mergeObjects: ["$root", "$$ROOT"]
                    }
                }
            },
            {
                $unset: "root"
            },
            { $project: { fields: '$fields'}} // this is to remove other junk for testing
]
  

Which brings back the following (almost correct) result:

 {
    "_id" : 2,
    "fields" : [ 
        {
            "key" : "part-number",
            "value" : "abc123",
            "def" : {
                "_id" : 1,
                "created" : ISODate("2020-09-10T20:23:46.382Z"),
                "options" : [],
                "updated" : ISODate("2020-09-10T20:23:46.382Z"),
                "active" : true,
                "displayOrder" : 0,
                "key" : "part-number",
                "label" : "Part Number",
                "required" : false,
                "type" : "text",
                "__v" : 0
            }
        }, 
        {
            "key" : "price",
            "value" : "10",
            "def" : {
                "_id" : 2,
                "created" : ISODate("2020-09-10T20:23:46.382Z"),
                "options" : [],
                "updated" : ISODate("2020-09-10T20:23:46.382Z"),
                "active" : true,
                "displayOrder" : 1,
                "key" : "price",
                "label" : "Price",
                "required" : false,
                "type" : "text",
                "__v" : 0
            }
        }, 
        {
            "key" : "officer",
            "value" : "2",
            "def" : {
                "_id" : 3,
                "created" : ISODate("2020-09-10T20:23:46.382Z"),
                "options" : [ 
                    {
                        "_id" : 1,
                        "created" : ISODate("2020-09-10T20:23:46.382Z"),
                        "updated" : ISODate("2020-09-10T20:23:46.382Z"),
                        "active" : true,
                        "key" : "none",
                        "label" : "None",
                        "displayOrder" : 0,
                        "legacy" : false,
                        "__v" : 0
                    }, 
                    {
                        "_id" : 2,
                        "created" : ISODate("2020-09-10T20:23:46.382Z"),
                        "updated" : ISODate("2020-09-10T20:23:46.382Z"),
                        "active" : true,
                        "key" : "picard",
                        "label" : "Picard",
                        "displayOrder" : 1,
                        "legacy" : false,
                        "__v" : 0
                    }, 
                    {
                        "_id" : 3,
                        "created" : ISODate("2020-09-10T20:23:46.382Z"),
                        "updated" : ISODate("2020-09-10T20:23:46.382Z"),
                        "active" : true,
                        "key" : "riker",
                        "label" : "Riker",
                        "displayOrder" : 2,
                        "legacy" : false,
                        "__v" : 0
                    }, 
                    {
                        "_id" : 4,
                        "created" : ISODate("2020-09-10T20:23:46.382Z"),
                        "updated" : ISODate("2020-09-10T20:23:46.382Z"),
                        "active" : true,
                        "key" : "data",
                        "label" : "Data",
                        "displayOrder" : 3,
                        "legacy" : false,
                        "__v" : 0
                    }
                ],
                "updated" : ISODate("2020-09-10T20:23:46.382Z"),
                "active" : true,
                "displayOrder" : 2,
                "key" : "officer",
                "label" : "Officer",
                "required" : false,
                "type" : "select",
                "__v" : 0
            }
        }
    ]
}
  

Моя проблема в том, что если я добавлю что-то в коллекцию ‘fields’ (назовем это свойством ‘foo’), существующие объекты ticket не имеют ссылки ‘foo’, поэтому они не отображаются в объекте ticket, я бы хотел как-то (в рамках агрегации)получите список текущих полей и для каждого найдите соответствующее значение, если оно существует, в противном случае возвращайте с пустым значением, например:

новый результат запроса:

 {
    _id: 2,
    fields: [
        ... //existing fields
        {
            key: "foo", // <-- new fields / fields not listed on the ticket
            value: null,
            def: {
                _id: 1,
                created: ISODate("2020-09-10T20:23:46.382Z"),
                options: [],
                updated: ISODate("2020-09-10T20:23:46.382Z"),
                active: true,
                displayOrder: 0,
                key: "foo",
                label: "Foo",
                required: false,
                type: "text",
                __v: 0,
            },
        },
    ],
}
  

Как это можно сделать? Кроме того, есть ли лучший способ сделать это, чем то, что у меня есть? Я очень новичок в агрегации и все еще экспериментирую.

Комментарии:

1. Есть ли что-то в предоставленном ответе, что, по вашему мнению, не отвечает на ваш вопрос? Если да, то, пожалуйста, прокомментируйте ответ, чтобы уточнить, что именно необходимо решить, чего нет.

Ответ №1:

Это очень длительный процесс объединения обоих полей, вы можете выполнить следующие действия,

  • $match ваше условие
  • $addFields чтобы добавить массив полей key в keys массив
  • $facet сгенерировать 2 отдельных массива, один для совпадающих полей, а второй для несогласованного массива
    • создайте массив совпадающих полей fields , передайте keys массив в let и сопоставьте $in запрос и поиск с fieldoptions коллекцией,
      • $project чтобы получить объекты объединения с текущими корневыми ключами и значением, используя $map и $reduce
    • создайте массив из несопоставимых полей other_fields , передайте keys массив в let и сопоставьте $not , а $in запрос означает не включать и поиск с fieldoptions коллекцией
      • $project чтобы получить объекты объединения с текущими корневыми ключами и значением, используя $map и $mergeObjects
  • $project для объединения обоих массивов в массив с помощью fields $concatArrays
  • $unwind деконструировать fields массив
  • $replaceWith fields замена объекта на корневой
  • $unwind деконструировать fields массив
  • $group по идентификатору билета и построению массива fields
 db.ticket.aggregate([
  { $match: { _id: 1 } },
  { $addFields: { keys: { $map: { input: "$fields", in: "$$this.key" } } } },
  {
    $facet: {
      fields: [
        {
          $lookup: {
            from: "fields",
            let: { key: "$keys" },
            as: "_fields",
            pipeline: [
              { $match: { $expr: { $in: ["$key", "$$key"] } } },
              {
                $lookup: {
                  from: "fieldoptions",
                  localField: "options",
                  foreignField: "_id",
                  as: "options"
                }
              }
            ]
          }
        },
        {
          $project: {
            fields: {
              $map: {
                input: "$fields",
                as: "f",
                in: {
                  $mergeObjects: [
                    "$$f",
                    {
                      def: {
                        $reduce: {
                          input: "$_fields",
                          initialValue: {},
                          in: {
                            $cond: [{ $eq: ["$$f.key", "$$this.key"] }, "$$this", "$$value"]
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      ],
      other_fields: [
        {
          $lookup: {
            from: "fields",
            let: { key: "$keys" },
            as: "_fields",
            pipeline: [
              { $match: { $expr: { $not: { $in: ["$key", "$$key"] } } } },
              {
                $lookup: {
                  from: "fieldoptions",
                  localField: "options",
                  foreignField: "_id",
                  as: "options"
                }
              }
            ]
          }
        },
        {
          $project: {
            fields: {
              $map: {
                input: "$_fields",
                as: "f",
                in: {
                  $mergeObjects: [
                    { def: "$$f" },
                    { key: "$$f.key", value: null }
                  ]
                }
              }
            }
          }
        }
      ]
    }
  },
  { $project: { fields: { $concatArrays: ["$fields", "$other_fields"] } } },
  { $unwind: "$fields" },
  { $replaceWith: "$fields" },
  {
    $unwind: {
      path: "$fields",
      preserveNullAndEmptyArrays: true
    }
  },
  {
    $group: {
      _id: "$_id",
      fields: { $push: "$$ROOT.fields" }
    }
  }
])
  

Игровая площадка


Ваш запрос, о котором идет речь, является оптимизированной версией этой площадки запросов