【问题标题】:MongoDB $lookup aggregation resulting in nested arrayMongoDB $lookup 聚合导致嵌套数组
【发布时间】:2021-05-24 19:55:56
【问题描述】:

我有两个多对一关系的集合(多个主机的 http 服务通常提供“相同”服务,例如 DNS 级负载平衡)。 我正在尝试构建一个查询,返回合并为一个的相关文档(来自两个集合)。

主机集合:

{
    "_id" : ObjectId("60aa2485332483cb4f5e7122"),
    "ip" : "1.2.3.4",
    "services" : [
        {
            "proto" : "tcp",
            "port" : "22",
            "status" : "open",
            "reason" : "syn-ack",
            "ttl" : 53,
        },
        {
            "proto" : "tcp",
            "port" : "80",
            "status" : "open",
            "reason" : "syn-ack",
            "ttl" : 51,
            "http" : [
                ObjectId("60aa64c67d0bf23ce47c530c")
            ]
        }
    ],
    "version" : 4,
    "last_scanned" : 1621573240.730579,

https 合集:

{
    "_id" : ObjectId("60aa64c67d0bf23ce47c530c"),
    "vhost" : "test.com",
    "paths" : [
        {
            "path" : "/admin",
            "code" : 200
        },
        {
            "path" : "/stuff",
            "code" : 200
        }
    ]
}

我想编写一个查找,其中输出是这两个集合的组合。到目前为止,我能够将 https 文档放入主机中的顶级数组中:

db.hosts.aggregate([                                                                                                                                       
  {                                                                             
    $lookup:                                                                    
        {                                                                       
            from: "https",                                                      
            localField: "services.http",                                        
            foreignField: "_id",                                                
            as: 'http'                                                 
        }                                                                       
  }                                                                             
]).pretty()

最终结果为:

{
    "_id" : ObjectId("60aa2485332483cb4f5e7122"),
    "ip" : "1.2.3.4",
    "services" : [
        {
            "proto" : "tcp",
            "port" : "22",
            "status" : "open",
            "reason" : "syn-ack",
            "ttl" : 53,
        },
        {
            "proto" : "tcp",
            "port" : "80",
            "status" : "open",
            "reason" : "syn-ack",
            "ttl" : 51,
            "http" : [
                ObjectId("60aa64c67d0bf23ce47c530c")
            ]
        }
    ],
    "http" : [
        {
            "_id" : ObjectId("60aa64c67d0bf23ce47c530c"),
            "vhost" : "test.com",
            "paths" : [
                {
                    "path" : "/admin",
                    "code" : 200
                },
                {
                    "path" : "/stuff",
                    "code" : 200
                }
            ]
        }
    ]
    "version" : 4,
    "last_scanned" : 1621573240.730579
    ]
}

问题是我无法将“http”字段移动到通过查找 (services.$.http) 找到它的 ObjectId 的位置。我试图以各种方式修改 $lookup 的“as”字段,但没有成功。

甚至可以用“as”指向嵌套文档的较低级别吗? 有什么解决方法可以实现这一点?

【问题讨论】:

    标签: mongodb aggregation-framework


    【解决方案1】:
    • $unwind解构服务数组
    • $lookuphttps 并将 as 设置为 services.http
    • $group by _id 并重构 services 数组并设置其他必填字段
    db.hosts.aggregate([
      { $unwind: "$services" },
      {
        $lookup: {
          from: "https",
          localField: "services.http",
          foreignField: "_id",
          as: "services.http"
        }
      },
      {
        $group: {
          _id: "$_id",
          ip: { $first: "$ip" },
          services: { $push: "$services" },
          version: { $first: "$version" },
          last_scanned: { $first: "$last_scanned" }
        }
      }
    ]).pretty()
    

    Playground


    没有$unwind的第二个选项,

    • $lookuphttps 合集
    • $map 迭代 services 数组的循环
    • $filter 迭代来自查找的 http 结果循环
    • $ifNull 如果字段为空/未找到,将返回空 []
    • $mergeObjects 合并services 的当前对象和过滤的http 数组
    • http 现在不需要数组结果,所以使用 $$REMOVE 删除它
    db.hosts.aggregate([
      {
        $lookup: {
          from: "https",
          localField: "services.http",
          foreignField: "_id",
          as: "http"
        }
      },
      {
        $addFields: {
          services: {
            $map: {
              input: "$services",
              as: "s",
              in: {
                $mergeObjects: [
                  "$$s",
                  {
                    http: {
                      $filter: {
                        input: "$http",
                        cond: {
                          $in: ["$$this._id", { $ifNull: ["$$s.http", []] }]
                        }
                      }
                    }
                  }
                ]
              }
            }
          },
          http: "$$REMOVE"
        }
      }
    ])
    

    Playground

    【讨论】:

    • 谢谢,这似乎是我想要的。我走在正确的轨道上,只是没有正确使用组。回去进一步实验......
    • 我看到的唯一缺点是 'hosts' 中的每个字段都需要添加到 $group 中,因此文档的灵活性取决于聚合管道。这不是一个大问题atm,但有什么办法可以避免呢?
    • 在我们使用$unwind & $gruoup的时候没有其他选项,但是有一个选项没有$unwind,当数据很多时会导致性能问题。
    • 看到我添加了第二个选项,没有 $unwind。
    猜你喜欢
    • 2021-08-04
    • 1970-01-01
    • 2021-08-09
    • 2017-07-26
    • 1970-01-01
    • 2020-04-03
    • 2020-12-31
    • 2017-07-16
    • 1970-01-01
    相关资源
    最近更新 更多