#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
}
}
}
])
Поскольку я чувствую, что суть вопроса была больше о производительности, чем о возможностях, я добавлю эти пару советов:
-
Вы хотели бы убедиться, что у вас есть
index
встроенный как вto_date
поле в коллекции отдела, так и вgender
/emp_no
в поле emps. -
Рассмотрите возможность создания нового
boolean
поля likestillEmployedHere
и обновите его только для текущего сотрудника, с запасным индексом это будет намного быстрее, чемto_date
запрос. Этот совет специально предназначен только для больших масштабов, поскольку в противном случае он будет иметь незначительные последствия.
Комментарии:
1. Спасибо за подробный ответ! Однако я столкнулся с проблемой — агрегация с первыми двумя этапами длится более минуты, и если я добавлю еще
$match
один, он не остановится через 5 минут и, возможно, продлится еще дольше… Является ли это ожидаемым поведением, если вdepartments
коллекции около 331,5 тыс. документов, а вemps
ней около 300 тыс.? Может быть, в запросе чего-то не хватает, что непреднамеренно делает его «вечным»? Индексы еще не добавлены.2. Особенно, если поля, о которых я упоминал, проиндексированы, это не должно занять так много времени, масштаб, который вы описываете, несколько «маленький». возможны ли какие-либо проблемы с оборудованием? маленький процессор / ОЗУ или процессор и память, используемые другими программами?
3. скорее нет, я проверил это только сейчас. У меня 2 ГБ оперативной памяти на моем vps, и ее использование находится на уровне 10%, а процессор еще меньше (2%).