首页 > 解决方案 > 在 MongoDB 中加入和分组

问题描述

数据库概述

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

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"
}

问题

现在我如何计算每个部门有多少员工在工作genderM到目前为止,我想出了这个查询:

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',
    }},
])

但它缺乏关键阶段,比如

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

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

预先感谢您的任何帮助!

标签: mongodbmongodb-queryaggregation-framework

解决方案


从员工收集开始是无法避免$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
      }
    }
  }
])

蒙戈游乐场

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

  1. 您需要确保在部门集合中的字段和emps 字段中的/上都有index构建。to_dategenderemp_no

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


推荐阅读