1

我有一个相当复杂的情况要解决。我有一个非常动态的模型,它允许引用其他对象(在同一个集合中)。引用可以是单个值或引用数组。此外,这些名称事先并不知道(在本例中,我将它们命名为 ref1 和 ref2,但它们可能是任何名称)。标识引用对象的是存在具有 key referenceId 的字段。我实际上确实解决了这部分问题(见下文)。示例数据看起来像这样(为了简单起见,我选择了字符串而不是 ObjectIds):

[
  {
    "id": "1",
    "name": "First"
  },
  {
    "id": "2",
    "name": "Second",
    "ref1": {
      "referenceId": "3"
    },
    "ref2": [
      {
        "referenceId": "1",
      },
      {
        "referenceId": "3"
      }
    ]
  },
  {
    "id": "3"
    "name": "Third",
  }
]

所需的结果将是(在执行查找并将结果对象替换为内联之后) - 这里对于 id = 2 的对象:

[
  {
    "id": "2",
    "name": "Second",
    "ref1":   {
      "id": "1",
      "name": "First"
    },
    "ref2": [
      {
        "id": "1",
        "name": "First"
      },
      {
        "id": "3"
        "name": "Third",
      }
    ]
  }
]

经过大量试验后,我想出了一个聚合管道的想法,它产生以下输出:

[
  {
    "id": "2",
    "name": "Second",
    "ref1": {
      "id": "3",
      "name": "Third"
    },
    "ref2": [
      {
        "referenceId": "1"
      },
      {
        "referenceId": "3"
      }
    ]
  },
  {
    "id": "2",
    "name": "Second",
    "ref1": {
      "referenceId": "3"
    },
    "ref2": {
      "id": "1",
      "name": "First"
    }
  },
  {
    "id": "2",
    "name": "Second",
    "ref1": {
      "referenceId": "3"
    },
    "ref2": {
      "id": "3",
      "name": "Third"
    }
  }
]

这非常接近我想要的,但是,我在这个阶段遇到了以下问题:

  • 如何删除不需要的字段?由于 $lookup 的 $unwinding,我有重复的字段,这些字段在我的最终结果中不想要(查找 ref2 的 ref1 和 ref1 的 ref2)
  • 如何通过动态(又名不可预测)字段名称(在本例中为 ref1 和 ref2?)将结果分组到一个对象中?

这在 MongoDB 中可行吗?我是在正确的道路上还是我应该考虑在客户端(如后端)代码中做的事情?我更愿意在聚合中解决这个问题,因为客户端代码会对性能产生影响。

以下是我目前正在采取的步骤:

蒙古游乐场

  1. 通过 $objectToArray 将结果转换为数组,并通过检查 referenceId 键的存在来仅获取我感兴趣的键。如果存在,则返回 [key, value] 对,否则返回 null:(匹配上面数据中的第二个对象)
  {
    $match: {
      id: "2"
    }
  }
  {
    $addFields: {
      "references": {
        $map: {
          input: {
            "$objectToArray": "$$ROOT"
          },
          as: "item",
          in: {
            $cond: {
              if: {
                $ne: [
                  "$$item.v.referenceId",
                  undefined
                ]
              },
              then: [
                "$$item.k",
                "$$item.v.referenceId"
              ],
              else: null
            }
          }
        }
      }
    }
  }
  1. 过滤掉后续映射的空值:
  {
    "$addFields": {
      "references": {
        $filter: {
          input: "$references",
          cond: {
            $ne: [
              "$$this",
              null
            ]
          }
        }
      }
    }
  }
  1. 对于查找,我需要固定的字段名称,因此将结果转换为 {"k": , "v": "} 的数组
{
    "$addFields": {
      "references": {
        $map: {
          input: "$references",
          as: "item",
          in: {
            k: {
              "$arrayElemAt": [
                "$$item",
                0
              ]
            },
            v: {
              "$arrayElemAt": [
                "$$item",
                1
              ]
            }
          }
        }
      }
    }
  }
  1. 展开结果以使用 av 字段执行查找:
  {
    $unwind: "$references"
  },
  {
    $unwind: "$references.v"
  }
  1. 对“已解析”字段执行查找,展开它,并将其转换为名称为(在我上面的示例中为~ ref1,ref2)的对象,并将其添加到结果文档中。
{
    $lookup: {
      "from": "collection",
      "localField": "references.v",
      "foreignField": "id",
      "as": "resolved"
    }
  },
  {
    $unwind: "$resolved"
  },
  {
    "$addFields": {
      "k": "$references.k",
      "v": "$resolved"
    }
  },

最后删除临时字段

  {
    $project: {
      resolved: 0,
      references: 0,
    }
  },

有人愿意挑战还是我太努力了?;-)

4

0 回答 0