我有一个相当复杂的情况要解决。我有一个非常动态的模型,它允许引用其他对象(在同一个集合中)。引用可以是单个值或引用数组。此外,这些名称事先并不知道(在本例中,我将它们命名为 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 中可行吗?我是在正确的道路上还是我应该考虑在客户端(如后端)代码中做的事情?我更愿意在聚合中解决这个问题,因为客户端代码会对性能产生影响。
以下是我目前正在采取的步骤:
- 通过 $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
}
}
}
}
}
}
- 过滤掉后续映射的空值:
{
"$addFields": {
"references": {
$filter: {
input: "$references",
cond: {
$ne: [
"$$this",
null
]
}
}
}
}
}
- 对于查找,我需要固定的字段名称,因此将结果转换为 {"k": , "v": "} 的数组
{
"$addFields": {
"references": {
$map: {
input: "$references",
as: "item",
in: {
k: {
"$arrayElemAt": [
"$$item",
0
]
},
v: {
"$arrayElemAt": [
"$$item",
1
]
}
}
}
}
}
}
- 展开结果以使用 av 字段执行查找:
{
$unwind: "$references"
},
{
$unwind: "$references.v"
}
- 对“已解析”字段执行查找,展开它,并将其转换为名称为(在我上面的示例中为~ 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,
}
},
有人愿意挑战还是我太努力了?;-)