Объединение и группирование по в MongoDB

#mongodb #mongodb-query #aggregation-framework

#mongodb #mongodb-запрос #агрегация-фреймворк

Вопрос:

Обзор БД

У меня есть две коллекции — первая с данными о сотрудниках, а вторая с их членством в определенных отделах.

Emps образец коллекции (размером около 331,5 тыс. документов):

 { 
    "_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 образец коллекции (размером около 300 тыс. документов):

 { 
    "_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 as "9999-01-01" означает, что сотрудник все еще работает в отделе,
  • группировка по названию отдела

Я бы хотел, чтобы запрос был настолько эффективным, насколько это возможно, поэтому с учетом сказанного я стараюсь избегать использования $unwind , поскольку он создает больше документов. Есть ли какой-либо другой способ получить доступ к элементам внутреннего массива без $unwind stage?

И последнее — я увидел, что внутри можно использовать конвейер $lookup , чтобы я мог избавиться от полей, которые меня не интересуют, путем создания проекции на просматриваемые документы, но мне не удалось сделать это правильно самостоятельно. Если вы знаете, как это сделать, пожалуйста, дайте мне знать.

Заранее благодарим вас за любую помощь!

Ответ №1:

Невозможно избежать $unwind , начав с коллекции employee, я бы все же не сказал, что это нарушает условия сделки с точки зрения эффективности конвейера.

Однако, запустив конвейер из 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

Поскольку я чувствую, что суть вопроса была больше о производительности, чем о возможностях, я добавлю эти пару советов:

  1. Вы хотели бы убедиться, что у вас есть index встроенный как в to_date поле в коллекции отдела, так и в gender / emp_no в поле emps.

  2. Рассмотрите возможность создания нового boolean поля like stillEmployedHere и обновите его только для текущего сотрудника, с запасным индексом это будет намного быстрее, чем to_date запрос. Этот совет специально предназначен только для больших масштабов, поскольку в противном случае он будет иметь незначительные последствия.

Комментарии:

1. Спасибо за подробный ответ! Однако я столкнулся с проблемой — агрегация с первыми двумя этапами длится более минуты, и если я добавлю еще $match один, он не остановится через 5 минут и, возможно, продлится еще дольше… Является ли это ожидаемым поведением, если в departments коллекции около 331,5 тыс. документов, а в emps ней около 300 тыс.? Может быть, в запросе чего-то не хватает, что непреднамеренно делает его «вечным»? Индексы еще не добавлены.

2. Особенно, если поля, о которых я упоминал, проиндексированы, это не должно занять так много времени, масштаб, который вы описываете, несколько «маленький». возможны ли какие-либо проблемы с оборудованием? маленький процессор / ОЗУ или процессор и память, используемые другими программами?

3. скорее нет, я проверил это только сейчас. У меня 2 ГБ оперативной памяти на моем vps, и ее использование находится на уровне 10%, а процессор еще меньше (2%).