将合并查找结果聚合到主文档

时间:2017-11-09 20:45:29

标签: node.js mongodb aggregation-framework

我创建了一个函数来拼接来自不同集合的2条记录:

选择1记录:

{
    _id: objectId(1231242331233),
    acc: '12390',
    val2: 'asdasdas'
}

收集2记录:

{
    _id: objectId(989232382302308),
    isValid: '1',
    tf: '098789928',
    acc: '12390'
}

为此,我提出了以下聚合函数和$ lookup。

Collection2.aggregate([
    {
        $lookup:
        {
            from: "Collection1",
            localField: "acc",
            foreignField: "acc",
            as: "acc_record"
        }
    }   
    {
        $out: 'Collection3'
    }
]);

这将生成一个Collection3,其记录具有以下结构:

{
    _id: objectId(989232382302308),
    isValid: '1',
    tf: '098789928',
    acc: '12390',
    acc_record:[
        {
            _id: objectId(1231242331233),
            acc: '12390',
            val2: 'asdasdas'
        }
    ]
}

合并这两条记录的聚合函数是什么,但不是将Collection1记录放在json对象的更深层,而只放置非相等元素并合并具有相同名称的元素?

所以最终的记录结果是:

{
        _id: objectId(989232382302308),
        isValid: '1',
        tf: '098789928',
        acc: '12390',
        val2: 'asdasdas'
}

1 个答案:

答案 0 :(得分:2)

添加$project

Collection2.aggregate([
    { "$lookup":{
      "from": "Collection1",
      "localField": "acc",
      "foreignField": "acc",
      "as": "acc_record"
    }},
    { "$project": {
      "isValid": 1,
      "tf": 1,
      "acc": { "$arrayElemAt": ["$acc_record.acc",0] },
      "val2": { "$arrayElemAt": ["$acc_record.val2", 0] }
    }},
    { "$out": "Collection3" }
]);

使用$arrayElemAt引用数组中的值,并将它们作为顶级对象中的值进行提升。

如果你总是知道结果是“一对一的”并且可以简单地从返回的第一个数组元素中获取值,那就没关系。如果他们是“一对多”,那么您也应用$unwind

Collection2.aggregate([
    { "$lookup":{
      "from": "Collection1",
      "localField": "acc",
      "foreignField": "acc",
      "as": "acc_record"
    }},
    { "$unwind": "$acc_record" },
    { "$project": {
      "_id": 0,
      "isValid": 1,
      "tf": 1,
      "acc": "$acc_record.acc",
      "val2": "$acc_record.val2"
    }},
    { "$out": "Collection3" }
]);

对于“一对一”也是完全有效的,但您应该注意_id在这里被“故意”删除。原因是,对于“很多”结果,$unwind$lookup结果中返回的每个数组成员生成父文档的“多个”副本。由于_id是“主键”,因此您无法将该值保持为“多个文档”中的相同值。

因此,丢弃主键的意义在于$out可以在写入时创建新值,而不会因“重复键错误”而失败。或者,如果您想将此作为“参考”,则只需将"$_id"值重命名为$project中的其他字段。

对于更大的输出,我们可以使用一些技巧来“合并”MongoDB支持这些功能的地方。在当前版本中,这些是来自MongoDB 3.4.4及更高版本的$arrayToObject$objectToArray

Collection2.aggregate([
    { "$lookup":{
      "from": "Collection1",
      "localField": "acc",
      "foreignField": "acc",
      "as": "acc_record"
    }},
    { "$unwind": "$acc_record" },
    { "$replaceRoot": {
      "newRoot": {
        "$arrayToObject": {
          "$concatArrays": [
            { "$filter": {
              "input": { "$objectToArray": "$$ROOT" },
              "cond": { "$not": { "$in":  ["$$this.k", ["_id", "acc_record"] } }
            }},
            { "$filter": {
              "input": { "$objectToArray": "$acc_record" },
              "cond": { "$ne": ["$$this.k", "acc"] }
            }}
          ]
        }
      }
    }},
    { "$out": "Collection3" }
])

诀窍是将“ROOT”文档和子数组内容转换为单独的数组,过滤掉重叠的键并应用$concatArrays将它们组合成一个数组。然后,您可以对“已加入”结果应用$arrayToObject,并通过$replaceRoot将其转换为根文档。

MongoDB 3.6

MongoDB 3.6使它更容易并引入$mergeObjects,所以你可以做一些简单的事情:

Collection2.aggregate([
    { "$lookup":{
      "from": "Collection1",
      "localField": "acc",
      "foreignField": "acc",
      "as": "acc_record"
    }},
    { "$unwind": "$acc_record" },
    { "$replaceRoot": {
      "newRoot": {
        "$mergeObjects": [
          { "$arrayToObject": {
            "$filter": {
              "input": { "$objectToArray": "$$ROOT" },
              "cond": { "$not": { "$in":  ["$$this.k", ["_id", "acc_record","acc"] } },
            }
          },
          "$acc_record"
        ]
      }
    }},
    { "$out": "Collection3" }
]);

一般情况下,您仍然需要$filter您不想要的密钥,例如$lookup的目标字段名称,最有可能是"localField"或{{1} }值也是。因此,除非您准备添加另一个聚合阶段以完全删除该子密钥,否则您不能真正使用"foreignField"与子密钥的内容合并。

所以一般来说$mergeObjects并没有真正增加太多,除了操作符的命名使得代码的意图清晰明了。