$在父数组中查找对象数组并将结果附加到所述数组的每个项目

$lookup Array of Objects in parent Array and append the results to each item of said Array

考虑以下文档"Backpack",每个slots是所述背包的一部分,每个插槽都有一个contents描述各种物品和它们的数量。

    {
        _id: "backpack",
        slots: [
            {
                slot: "left-pocket",
                contents: [
                    {
                        item: "pen",
                        count: 3
                    },
                    {
                        item: "pencil",
                        count: 2
                    },
                ]
            },
            {
                slot: "right-pocket",
                contents: [
                    {
                        item: "bottle",
                        count: 1
                    },
                    {
                        item: "eraser",
                        count: 1
                    },
                ]
            }
        ]
    }

item字段是另一个集合中某项的_id,例如:

    {
        _id: "pen",
        color: "red"
        (...)
    },

同样适用于钢笔、铅笔、瓶子、橡皮等

我想创建一个 $lookup 以便我可以填写项目的数据,但我没有找到使查找的 as 与项目位于同一位置的方法。即:

    db.collection.aggregate({
        {
            $lookup: {
                from: 'items',
                localField: 'slots.contents.item',
                foreignField: '_id',
                as: 'convertedItems', // <=== ISSUE
            },
        },
    })

问题是 as 被命名为 convertedItems 意味着文档在名为 'convertedItems' 的文档的根目录中获取项目数组,如下所示:

{
    _id: "backpack",
    slots: [ (...) ],
    convertedItems: [ (...) ]
}

如何告诉 $lookup 实际使用 localField 作为附加数据的位置?

即使文档变为:

{
    _id: "backpack",
    slots: [
        {
            slot: "left-pocket",
            contents: [
                {
                    item: "pen", // <== NOTE
                    count: 3, // <== NOTE
                    _id: "pen",
                    color: "red"
                    (...)
                },
                {
                    item: "pencil", // <== NOTE
                    count: 2, // <== NOTE
                    _id: "pencil",
                    color: "blue"
                    (...)
                },
            ]
        },
    (...)

注:此时,如果有item的全部数据,item属性保留无所谓,但count 必须保留。

我无法用 arrayElemAt 手动执行 $addFields,因为 slots 中的项目数不固定。


额外信息:我使用的是 MongoAtlas Free,所以假设 MongoDB 4.2+(无需为 $lookup 展开数组)。


PS:我现在想到的只是作为根项目离开(例如 "convertedItems")并在接收 API 的代码上循环项目时,我做 Array.find 在 "convertedItems" 上根据使用该项目的 _id。 我会保留这个问题,因为我很好奇如何在 MongoDB 方面

当您使用 $lookup 时,源管道中每个文档的相关集合中只有一个查询,而不是源文档中每个值的查询。

如果您希望单独查找每个项目,则需要展开数组,以便管道中的每个文档都包含一个项目,进行查找,然后分组以重建数组。

db.collection.aggregate([
  {$unwind: "$slots"},
  {$unwind: "$slots.contents"},
  {$lookup: {
      from: "items",
      localField: "slots.contents.item",
      foreignField: "_id",
      as: "convertedItems"
  }},
  {$group: {
      _id: "$slots.slot",
      root: {$first: "$$ROOT"},
      items: {
        $push: {
          $mergeObjects: [
            "$slots.contents",
            {$arrayElemAt: ["$convertedItems", 0]}
          ]
      }},      
  }},
  {$addFields: {"root.slots.contents": "$items"}},
  {$replaceRoot: {newRoot: "$root"}},
  {$group: {
      _id: "$_id",
      root: {$first: "$$ROOT"},
      slots: {$push: "$slots"}
  }},
  {$addFields: {"root.slots": "$slots"}},
  {$replaceRoot: {newRoot: "$root"}},
  {$project: { convertedItems: 0}}
])

Playground

unwind makes your collection explode, Also you can't specify in place of 'as', So you need to add additional stages like addFields, filters to get required o/p

正如我所评论的那样,为了将主文档的元素与 $lookup 结果相匹配,您的要求需要做一些工作,也许这可以通过代码轻松完成,但如果必须通过查询,使用这个查询,你将处理与你在集合中的相同的 no.of 文档,而不是放松,因为当你拥有像你现在这样的嵌套数组时,它会爆炸你的文档,一般来说有点复杂如果需要更好的性能,请尝试使用 $match 作为第一阶段来过滤文档。此外,您可以使用 $explain 来了解您的查询性能。

查询:

db.Backpack.aggregate([
    /** lookup on items collection & get matched docs to items array */
    {
      $lookup: {
        from: "items",
        localField: "slots.contents.item",
        foreignField: "_id",
        as: "items"
      }
    },
    /** Iterate on slots & contents & internally filter on items array to get matched doc for a content object &
     *  merge the objects back to respective objects to form the same structure */
    {
      $project: {
        slots: {
          $map: {
            input: "$slots",
            in: {
              $mergeObjects: [
                "$$this",
                {
                  contents: {
                    $map: {
                      input: "$$this.contents",
                      as: "c",
                      in: {
                        $mergeObjects: [
                          "$$c",
                          {
                            $let: {
                              vars: {
                                matchedItem: {
                                  $arrayElemAt: [
                                    {
                                      $filter: {
                                        input: "$items",
                                        as: "i",
                                        cond: {
                                          $eq: [
                                            "$$c.item",
                                            "$$i._id"
                                          ]
                                        }
                                      }
                                    },
                                    0
                                  ]
                                }
                              },
                              in: {
                                color: "$$matchedItem.color"
                              }
                            }
                          }
                        ]
                      }
                    }
                  }
                }
              ]
            }
          }
        }
      }
    }
  ])

测试: MongoDB-Playground