【问题标题】:Join and Group By in MongoDB在 MongoDB 中加入和分组
【发布时间】:2020-09-26 08:44:35
【问题描述】:

数据库概览

我有两个集合 - 第一个是员工详细信息,第二个是他们在某些部门的成员身份。

Emps 集合样本(大小约为 331.5k 个文档):

{ 
    "_id" : ObjectId("5dc027718da295b969e529ae"), 
    "emp_no" : 10001, 
    ...,
    "gender" : "M", 
    "titles" : [
        {
            "title" : "Senior Engineer", 
            "dept_name" : "Development", 
            "from_date" : "1986-06-26", 
            "to_date" : "9999-01-01"
        },
        {
            "title" : "Staff", 
            "dept_name" : "Human Resources", 
            "from_date" : "1986-06-26", 
            "to_date" : "9999-01-01"
        }
    ]
}

departments 集合样本(大小约为 300k 文档):

{ 
    "_id" : ObjectId("5dc026438da295b969e01893"), 
    "dept_no" : "d005", 
    "dept_name" : "Development", 
    "emp_no" : 10001, 
    "from_date" : "1986-06-26", 
    "to_date" : "9999-01-01"
},
{ 
    "_id" : ObjectId("5dc026438da295b969e01894"), 
    "dept_no" : "d003", 
    "dept_name" : "Human Resources", 
    "emp_no" : 10001, 
    "from_date" : "1986-06-26", 
    "to_date" : "9999-01-01"
}

问题

现在我如何计算每个部门有多少gender 等于M 的员工?到目前为止,我想出了这个查询:

db.getCollection("emps").aggregate([
    {$match: {'gender': 'F'}},
    {$project: {_id: 0, emp_no: 1, gender: 1}},
    {$lookup: {
        from: 'departments',
        localField: 'emp_no',
        foreignField: 'emp_no',
        as: 'departments',
    }},
])

但它缺少关键阶段,例如

  • to_date"9999-01-01" 的条件表示该员工仍在部门工作,
  • 按部门名称分组

我希望查询尽可能高效,因此我尽量避免使用$unwind,因为它会生成更多文档。有没有其他方法可以在没有$unwind 阶段的情况下到达内部数组元素?

最后一件事 - 我看到可以在 $lookup 中使用管道,因此我可以通过在查找的文档上进行投影来摆脱我不感兴趣的字段,但我没有设法自己正确地做到这一点。如果你知道怎么做,请告诉我。

提前感谢您的帮助!

【问题讨论】:

    标签: mongodb mongodb-query aggregation-framework


    【解决方案1】:

    从员工收集开始,无法避免$unwind,我仍然不会说这在管道效率方面会破坏交易。

    但是,通过从 departments 集合启动管道,您可以避免它,而且启发式(假设公司很大并且已经存在一段时间)您可以通过首先匹配仍在公司工作的人来消除更多人(通过匹配to_date 在部门集合上),而不是先匹配性别。 (性别消除了 50%,而在职员工可能要低得多,例如 10%)。您实际上可以自己计算分布,并决定管道的哪种“方式”对您更有效。

    我只是提出这一点,因为管道中最昂贵的阶段是$lookup,这是对性能影响最大的操作。因此,$lookup 越小,整体性能就越好。

    我会这样做:

    db.departments.aggregate([
      {
        $match: {
          "to_date": "9999-01-01"
        }
      },
      {
        $lookup: {
          from: "emps",
          let: {
            empNum: "$emp_no"
          },
          pipeline: [
            {
              $match: {
                $expr: {
                  $and: [
                    {
                      $eq: [
                        "$gender",
                        "M"
                      ]
                    },
                    {
                      $eq: [
                        "$$empNum",
                        "$emp_no"
                      ]
                    }
                  ]
                }
              }
            }
          ],
          as: "employees"
        }
      },
      {
        $match: {
          "employees.0": {
            $exists: true
          }
        }
      },
      {
        $group: {
          _id: "$dept_no",
          count: {
            $sum: 1
          }
        }
      }
    ])
    

    Mongo Playground

    因为我觉得问题的核心更多是关于性能而不是功能,所以我将添加以下几个提示:

    1. 您需要确保在部门集合中的to_date 字段和emps 字段中的gender/emp_no 上都有一个index

    2. 考虑创建一个新的boolean 字段,如stillEmployedHere 并仅在当前员工的字段上更新它,使用备用索引将比to_date 查询快得多。此技巧仅适用于大规模,否则会产生轻微影响。

    【讨论】:

    • 感谢您的详细解答!但是,我遇到了问题 - 前两个阶段的聚合持续了超过一分钟,如果我添加另一个 $match 它并没有在 5 分钟内停止并且可能会持续更长时间......这是预期的吗departments 集合有大约 331.5k 个文档,emps 有大约 300k?也许查询中缺少某些东西会无意中使其“永远”持续下去?尚未添加任何索引。
    • 特别是如果我提到的字段被索引它不应该花这么长时间,你描述的规模有点“小”。是否有任何相关的硬件问题?小 cpu /RAM 或 cpu 和内存被其他程序使用?
    • 相当不,我刚刚检查过了。我的 vps 上有 2gb 的内存,它的使用率在 10% 的水平上,cpu 甚至更少(2%)。
    猜你喜欢
    • 1970-01-01
    • 2017-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-22
    • 1970-01-01
    • 1970-01-01
    • 2020-08-04
    相关资源
    最近更新 更多