代码之家  ›  专栏  ›  技术社区  ›  user1578872

使用$graphLookup的Mongo分层查询

  •  9
  • user1578872  · 技术社区  · 6 年前

    我收集了50万份员工档案。每个记录都有以下详细信息。

    mongo文件如下。

    {
      "_id": "234463456453643563456",
      "name": "Mike",
      "empId": "10",
      "managerId": "8",
      "projects" : [ "123", "456", "789"]
    }
    

    答。过滤器打开
    b。筛选项目

    结果应该是,

          10     ->>> Manager
          /\
         /  \
        8    6  ---->> 8 & 6 reporting to manager 10
        /\    /\
       /  \  /  \
      4    5 2   1  ---->> 4 & 5 reporting to manager 8 ...
    

    任何帮助将得到与水平的分级结果表示感谢?

    db.getCollection("employees").insert({"_id":"10","empId": "10","name":"Employee10","managerId":"15" });
    
    db.getCollection("employees").insert({"_id":"8","empId": "8","name":"Employee8","managerId":"10" });
    
    db.getCollection("employees").insert({"_id":"6","empId": "6","name":"Employee6","managerId":"10" });
    
    db.getCollection("employees").insert({"_id":"4","empId": "4","name":"Employee4","managerId":"8" });
    
    db.getCollection("employees").insert({"_id":"5","empId": "5","name":"Employee5","managerId":"8" });
    
    db.getCollection("employees").insert({"_id":"2","empId": "2","name":"Employee2","managerId":"6" });
    
    db.getCollection("employees").insert({"_id":"1","empId": "1","name":"Employee1","managerId":"6" });
    

    查询:-

    db.getCollection('employees').aggregate([
    {
        $match: {
            empId : "10"
        }
    },
    {
       $graphLookup: {
          from: "employees",
          startWith: "$empId",
          connectFromField: "empId",
          connectToField: "managerId",
          as: "reportees",
          maxDepth: 4,
          depthField: "level"
       }
    },
    {
       $project: {
         "empId":1,
         "managerId":1,
         "reportees.empId":1,
         "reportees.name":1,
         "reportees.managerId":1,
         "reportees.level":1
       }
    }
    ]);
    

    实际结果:-

    { 
        "_id" : "10", 
        "empId" : "10", 
        "managerId" : "15", 
        "reportees" : [
            {
                "empId" : "1", 
                "name" : "Employee1", 
                "managerId" : "6", 
                "level" : NumberLong(1)
            }, 
            {
                "empId" : "4", 
                "name" : "Employee4", 
                "managerId" : "8", 
                "level" : NumberLong(1)
            }, 
            {
                "empId" : "2", 
                "name" : "Employee2", 
                "managerId" : "6", 
                "level" : NumberLong(1)
            }, 
            {
                "empId" : "5", 
                "name" : "Employee5", 
                "managerId" : "8", 
                "level" : NumberLong(1)
            }, 
            {
                "empId" : "6", 
                "name" : "Employee6", 
                "managerId" : "10", 
                "level" : NumberLong(0)
            }, 
            {
                "empId" : "8", 
                "name" : "Employee8", 
                "managerId" : "10", 
                "level" : NumberLong(0)
            }
        ]
    }
    

    { 
        "_id" : "10", 
        "empId" : "10", 
        "managerId" : "15", 
        "reportees" : [
            {
                "empId" : "6", 
                "name" : "Employee6", 
                "managerId" : "10", 
                "level" : NumberLong(0),
                "reportees" : [
                  {
                   "empId" : "1", 
                   "name" : "Employee1", 
                   "managerId" : "6", 
                   "level" : NumberLong(1)
                  }, 
                  {
                   "empId" : "2", 
                   "name" : "Employee2", 
                   "managerId" : "6", 
                   "level" : NumberLong(1)
                  }
                ]
            }, 
            {
                "empId" : "8", 
                "name" : "Employee8", 
                "managerId" : "10", 
                "level" : NumberLong(0),
                "reportees" : [
                  {
                    "empId" : "5", 
                    "name" : "Employee5", 
                    "managerId" : "8", 
                    "level" : NumberLong(1)
                  },
                  {
                    "empId" : "4", 
                    "name" : "Employee4", 
                    "managerId" : "8", 
                    "level" : NumberLong(1)
                  }
                 ]
            }
        ]
    }
    

    问题:-

    1. 另外,是否有可能得到最高级别的计数,以及每个子级别的计数?
    2. 如何在所有级别应用投影?
    3 回复  |  直到 6 年前
        1
  •  5
  •   dnickless    6 年前

    那正是你想要的 $graphLookup $filter $match 取决于你想过滤的方式。

    db.employees.aggregate({
        $graphLookup: {
          from: "employees",
          startWith: "$managerId",
          connectFromField: "managerId",
          connectToField: "empId",
          as: "managers",
        }
    })
    

    根据您的澄清更新1:

    为了得到您想要的层次结构,您可以执行以下操作。然而,我不认为这是一个很好的解决方案,因为它需要您静态地定义要降低的级别数,并且还需要重复部分,但是对于您的示例来说,它确实起到了作用。也不确定是否/有多容易扩展到更多级别。我个人认为客户端循环解决方案更适合这种工作:

    db.employees.aggregate([
    {
        $match: {
            empId : "10"
        }
    },
    // level 0
    {
       $graphLookup: {
          from: "employees",
          startWith: "$empId",
          connectFromField: "empId",
          connectToField: "managerId",
          as: "reportees",
          maxDepth: 0
       }
    },
    {
        $unwind: "$reportees" // flatten
    },
    {
        $addFields: {
            "reportees.level": 0 // add level field
        }
    },
    // level 1
    {
       $graphLookup: {
          from: "employees",
          startWith: "$reportees.empId",
          connectFromField: "reportees.empId",
          connectToField: "managerId",
          as: "reportees.reportees",
          maxDepth: 0
       }
    },
    {
        $group: { // group previously flattened documents back together
            _id: "$_id",
            empId: { $first: "$empId" },
            name: { $first: "$name" },
            managerId: { $first: "$managerId" },
            reportees: { $push: "$reportees" },
        }
    },
    {
        $addFields: {
            "reportees.reportees.level": 1 // add level field
        }
    }
    ])
    

    更新2:

    下面的查询从输出结构的角度(我省略了 level 字段,但应该很容易添加)。然而,它并不是特别漂亮,再次要求你预先定义一个最大的组织深度。

    db.employees.aggregate([
    {
        $match: {
            empId : "10"
        }
    },
    {
       $graphLookup: { // get the relevant documents out of our universe of employees
          from: "employees",
          startWith: "$empId",
          connectFromField: "empId",
          connectToField: "managerId",
          as: "reportees"
       }
    },
    {
        $project: { // add the employee we are interested in into the array of employees we're looking at
            _id: 0,
            reportees: { $concatArrays: [ "$reportees", [ { _id: "$_id", empId: "$empId", name: "$name", managerId: "$managerId" } ] ] }
        }
    },
    {
        $project: {
            reportees: {
                $let: {
                    vars: {
                        managers: {
                            $filter: { // remove employees with no reportess so keep managers only
                                input: {
                                    $map: {
                                        input: "$reportees",
                                        as: "this",
                                        in: {
                                            $mergeObjects: [
                                                "$$this",
                                                {
                                                    reportees: {
                                                        $filter: { // extract reportees from list of employees
                                                            input: "$reportees",
                                                            as: "that",
                                                            cond: {
                                                                $eq: [ "$$this._id", "$$that.managerId" ]
                                                            }
                                                        }
                                                    }
                                                }
                                            ]
                                        }
                                    }
                                },
                                as: "this",
                                cond: { $ne: [ "$$this.reportees", [] ] }
                            }
                        }
                    },
                    in: {
                        $cond: [ // this is to break the processing once we have reached a top level manager
                            { $eq: [ "$$managers", [] ] },
                            "$reportees",
                            "$$managers"
                        ]
                    }
                }
            }
        }
    },
    // second level: exactly identical to the previous stage
    // third level: exactly identical to the previous stage
    // basically, from here onwards you would need to repeat an exact copy of the previous stage to go one level deeper
    ]);
    
        2
  •  11
  •   mickl    6 年前

    水平 我们可以使用 $reduce . 为了实现这一点,我们需要 reportees $graphLookup . 不幸的是,目前唯一的方法是使用 $unwind $sort + $group 这使得聚合相当长。

    然后我们可以使用 $reduce 被报道者 从上一级开始。此外,我们还需要检测 level 在我们的处理过程中的变化,并在这种情况下重新排列助手数组。

    $addFields 简单地替换现有的 本例中的字段。 $concatArrays 允许我们附加当前员工( $$this $filter 被报道者

    db.getCollection('employees').aggregate([
        {
            $match: {
                empId : "10"
            }
        },
        {
            $graphLookup: {
                from: "employees",
                startWith: "$empId",
                connectFromField: "empId",
                connectToField: "managerId",
                as: "reportees",
                maxDepth: 4,
                depthField: "level"
            }
        },
        {
            $project: {
                "empId":1,
                "managerId":1,
                "reportees.empId":1,
                "reportees.name":1,
                "reportees.managerId":1,
                "reportees.level":1
            }
        },
        {
            $unwind: "$reportees"
        },
        {
            $sort: { "reportees.level": -1 }
        },
        {
            $group: {
                _id: "$_id",
                empId: { $first: "$empId" },
                managerId: { $first: "$managerId" },
                reportees: { $push: "$reportees" }
            }
        },
        {
            $addFields: {
                reportees: {
                    $reduce: {
                        input: "$reportees",
                        initialValue: {
                            currentLevel: -1,
                            currentLevelEmployees: [],
                            previousLevelEmployees: []
                        },
                        in: {
                            $let: {
                                vars: {
                                    prev: { 
                                        $cond: [ 
                                            { $eq: [ "$$value.currentLevel", "$$this.level" ] }, 
                                            "$$value.previousLevelEmployees", 
                                            "$$value.currentLevelEmployees" 
                                        ] 
                                    },
                                    current: { 
                                        $cond: [ 
                                            { $eq: [ "$$value.currentLevel", "$$this.level" ] }, 
                                            "$$value.currentLevelEmployees", 
                                            [] 
                                        ] 
                                    }
                                },
                                in: {
                                    currentLevel: "$$this.level",
                                    previousLevelEmployees: "$$prev",
                                    currentLevelEmployees: {
                                        $concatArrays: [
                                            "$$current", 
                                            [
                                                { $mergeObjects: [ 
                                                    "$$this", 
                                                    { reportees: { $filter: { input: "$$prev", as: "e", cond: { $eq: [ "$$e.managerId", "$$this.empId"  ] } } } } 
                                                ] }
                                            ]
                                        ]
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        {
            $addFields: { reportees: "$reportees.currentLevelEmployees" }
        }
    ]).pretty()
    

    上述解决方案应适用于多个级别。输出:

    {
        "_id" : "10",
        "empId" : "10",
        "managerId" : "15",
        "reportees" : [
            {
                "empId" : "6",
                "name" : "Employee6",
                "managerId" : "10",
                "level" : NumberLong(0),
                "reportees" : [
                    {
                            "empId" : "1",
                            "name" : "Employee1",
                            "managerId" : "6",
                            "level" : NumberLong(1),
                            "reportees" : [ ]
                    },
                    {
                            "empId" : "2",
                            "name" : "Employee2",
                            "managerId" : "6",
                            "level" : NumberLong(1),
                            "reportees" : [ ]
                    }
                ]
            },
            {
                "empId" : "8",
                "name" : "Employee8",
                "managerId" : "10",
                "level" : NumberLong(0),
                "reportees" : [
                    {
                        "empId" : "5",
                        "name" : "Employee5",
                        "managerId" : "8",
                        "level" : NumberLong(1),
                        "reportees" : [ ]
                    },
                    {
                        "empId" : "4",
                        "name" : "Employee4",
                        "managerId" : "8",
                        "level" : NumberLong(1),
                        "reportees" : [ ]
                    }
                ]
            }
        ]
    }