0

我有一个这样的 mongo 数据库:

db.templates.insertMany( [
   { _id: 1, uuid: "1", name: "t1", related_templates: [ "2", "2" ] },
   { _id: 2, uuid: "2", name: "t2", related_templates: [ "3", "3" ] },
   { _id: 3, uuid: "3", name: "t3", related_templates: [ "4", "4" ] },
   { _id: 4, uuid: "4", name: "t4"},
] )

如您所见,数据表示树结构,但支持对同一子节点的重复引用。我正在尝试从 t1 开始递归地获取整个树,包括重复的引用。

结果将如下所示:

{
    "_id" : 1,
    "uuid" : "1",
    "name": "t1",
    "related_templates" : [
        {
            "_id" : 2,
            "uuid" : "2",
            "name" : "t2",
            "related_templates" : [
                {
                    "_id" : 3,
                    "uuid" : "3",
                    "name" : "t3",
                    "related_templates" : [
                        {
                            "_id" : 4,
                            "uuid" : "4",
                            "name" : "t4"
                        },
                        {
                            "_id" : 4,
                            "uuid" : "4",
                            "name" : "t4"
                        }
                    ]
                },
                {
                    "_id" : 3,
                    "uuid" : "3",
                    "name" : "t3",
                    "related_templates" : [
                        {
                            "_id" : 4,
                            "uuid" : "4",
                            "name" : "t4"
                        },
                        {
                            "_id" : 4,
                            "uuid" : "4",
                            "name" : "t4"
                        }
                    ]
                }
            ]
        },
        ...(t2 repeats here)
    ]
}

Mongo 网站上建议的解决方案在这里:https ://docs.mongodb.com/manual/reference/operator/aggregation/lookup/#std-label-unwind-example 。如果没有重复引用,则此解决方案效果很好,只需稍作修改,甚至还允许递归查找。但是,在我的情况下,我需要保留重复的查找

我也考虑过使用 unwind + group 的传统解决方案。该解决方案确实保留了重复项,但我还没有弄清楚如何递归地使用它。

我还考虑过使用 mongo 网站上的解决方案来获取不重复的内容,然后使用地图将获取的数据附加到原始的 related_templates 数组中。我认为这会起作用,但它似乎不是很优雅。

是否有一个优雅/更简单的解决方案来做到这一点,我错过了?

4

1 回答 1

0

如果将来有人最终遇到此问题,我将继续发布我提出的解决方案:

db.templates.aggregate([
  {
    "$match": {'uuid': "1"}
  },
  {
    '$lookup': {
      'from': "templates",
      'let': { 'uuids': "$related_templates"},
      'pipeline': [
        { 
          '$match': { 
            '$expr': { 
              '$and': [
                { '$in': [ "$uuid",  "$$uuids" ] },
              ]
            }
          }
        }
      ],
      'as': "related_templates_objects"
    }
  },
  {
    $addFields: {
      "related_templates_objects_uuids": { 
        $map: {
          input: "$related_templates_objects",
          in: "$$this.uuid"
        }
      }
    }
  },
  {
    $addFields: {
      "related_templates": { 
        $map: {
          input: "$related_templates",
          in: {"$arrayElemAt":[
            "$related_templates_objects",
            {"$indexOfArray":["$related_templates_objects_uuids","$$this"]}
          ]}
        }
      }
    }
  },
  {"$project":{"related_templates_objects":0,"related_templates_objects_uuids":0}}
])

总之:

  1. 进行不重复的查找,称为related_templates_objects。
  2. 创建一个与related_templates_objects 相同的数组,除了只提取uuid,称为related_templates_objects_uuids。
  3. 通过将related_templates 中的每个原始引用映射到related_templates_objects 中的正确对象(通过related_templates_objects_uuids 找到其索引),创建所需的对象数组。
  4. 投影出用于创建新的related_templates 的原始两个中间字段。

当然,这个解决方案不是递归的。可以通过将外部管道的最后 4 个元素复制到内部管道中来递归一次。然后按照我在项目中编码的相同的复制和粘贴公式递归 x 次。

希望解决方案可以帮助某人。

于 2021-12-13T17:47:46.003 回答