Як працює агрегація в MongoDB: практичний гайд
- Катерина Шевченко
- 25 лип.
- Читати 6 хв
Оновлено: 31 лип.

MongoDB — це документо-орієнтована NoSQL база, оптимізована для роботи з даними, які мають складну структуру і можуть змінюватися з часом. Її використовують у Strum — найбільшій навчальній платформі в Genesis. Там MongoDB обслуговує понад 30 000 користувачів і дає змогу масштабовано працювати з навчальними курсами, прогресом, ролями та правами доступу.
Один із ключових інструментів для аналітичних запитів у MongoDB — агрегація. Вона дозволяє не лише шукати документи за умовами, а й трансформувати, групувати та обчислювати метрики безпосередньо на рівні бази. У цьому тексті Сергій Бодров, Software Engineer в Genesis, пояснює High Bar Journal, як влаштований агрегаційний пайплайн MongoDB, як працюють основні етапи та оператори, та наводить приклади, як вони комбінуються.

Для наочності уявімо, що ми працюємо з колекцією фільмів. Кожен документ містить інформацію про назву, жанр, режисера, акторський склад, рейтинг, бюджет і касові збори. На цих прикладах ми розберемо, як працюють фільтрація, трансформації та групування в MongoDB.

Агрегаційний пайплайн: як дані проходять через MongoDB
Агрегація в MongoDB — це механізм багатоступеневої обробки даних, який дозволяє послідовно трансформувати документи з колекції для досягнення конкретного результату: формування аналітичного звіту, зміни структури документів або об'єднання інформації з різних джерел.
На відміну від простих запитів find(), які лише повертають документи за умовою, агрегація дозволяє:
будувати умовну логіку;
змінювати структуру документа;
працювати з вкладеними масивами;
об’єднувати дані з різних колекцій;
знаходити суми, середні, мінімальні та максимальні значення.
У MongoDB агрегація будується у вигляді пайплайну — послідовності етапів, які обробляють документи з колекції. Кожен етап — це окремий об'єкт у масиві, що описує трансформацію, фільтрацію, обчислення або зміну структури. Усе це виконується всередині методу aggregate().
Уявіть пайплайн як конвеєр: документи колекції потрапляють на вхід, і на кожному етапі їх можна фільтрувати, змінювати або групувати. Наприклад:
$match — фільтрація документів;
$project — відбір або перейменування полів;
$unwind — розгортання масивів у кілька документів;
$group, $set, $replaceWith — інші трансформації, які будемо розглядати далі.

Пайплайн завжди повертає масив документів.
Для побудови та налагодження агрегаційних запитів зручно використовувати MongoDB Compass — графічний інтерфейс, який підтримує:
покрокову візуалізацію етапів пайплайну;
попередній перегляд результатів на кожному кроці;
зручне редагування та дебаг запитів.
Ті самі команди працюють і в Mongo Shell, і у всіх офіційних драйверах MongoDB — наприклад, у Node.js, Python, Java тощо.
У Strum агрегації активно застосовуються для розрахунку прогресу користувачів, формування списку доступних курсів і занять, побудови аналітичних звітів. Вони значно гнучкіші за базові методи find, updateOne чи deleteOne.
Також у пайплайні можна звертатися до системних змінних, зокрема:
$$NOW — повертає нинішню дату, яка лишається незмінною протягом усієї агрегації;
$$ROOT — повертає чинний документ, часто використовується на етапі $group;
$$REMOVE — видаляє поле з документа, часто використовується усередині оператора $cond для умовного видалення полів.
Поширені етапи агрегації: $set, $match, $project, $lookup, $group, $unwind, $sort, $replaceWith, $merge, $unset, $facet, $limit.
Також на STRUM використовуються: $skip, $bucket, $sample, $densify, $documents, $setWindowFields.
Робота з полями: $project, $set
Етапи $project і $set використовуються тоді, коли потрібно змінити структуру кожного документа окремо — прибрати, залишити, додати або перезаписати поля.
$project дозволяє залишити лише вибрані поля. Якщо якесь поле не вказане — воно буде вилучене. $set додає нові поля до документа або оновлює ті, що існують. Якщо ім’я поля збігається з наявним — значення буде перезаписано.
До прикладу, в документі залишимо тільки назву фільму, ім’я режисера та касові збори:

Або сформуємо нові поля: округлимо рейтинг до найближчого цілого числа й порахуємо прибуток:

Логічні, математичні, умовні оператори
Етапи $project і $set підтримують обчислення значень «на льоту» за допомогою агрегаційних операторів. Вони складаються з операторів, які записуються у форматі польської нотації: спочатку назва оператора, потім двокрапка і масив аргументів. Нижче — найуживаніші групи таких операторів.
1. Логічні оператори
$eq, $ne — перевірка на рівність або нерівність;
$gt, $lt, $gte, $lte — більше / менше / більше або дорівнює / менше або дорівнює;
$in, $nin — перевірка, чи міститься / не міститься значення в масиві;
$not — логічне НІ;
$and, $or — логічне І / АБО для кількох умов.
Такі вирази завжди повертають true або false і використовуються для умовної логіки прямо всередині агрегаційного пайплайну.
2. Математичні оператори
$add, $subtract, $multiply, $divide — базові арифметичні операції;
$round — округлення числа до вказаного розряду.
Наприклад, додамо до цього документу два поля: чи містить список жанрів драму, чи окупився фільм (gross був більшим, ніж budget).

3. Умовні оператори
Умовні оператори в MongoDB мають специфічний синтаксис. Один із найвживаніших — це $cond, який працює як тернарний оператор: приймає три аргументи — умову, результат при true, результат при false: { $cond: { if: умова, then: результат, else: результат } }.
Ще один приклад — $ifNull, який повертає перший визначений вираз з переданого масиву. Якщо перше null або його взагалі немає, Mongo візьме наступне. І тут ще один підводний камінь: у MongoDB null і відсутнє поле (undefined) трактуються однаково, що може призвести до несподіваної поведінки в складних пайплайнах.
Наприклад, спробуємо категоризувати фільми як «Blockbuster» або «Regular» залежно від прибутку:

Цей підхід дозволяє додавати умовну бізнес-логіку просто в агрегаційний пайплайн.
Оператори для роботи з масивами
MongoDB дозволяє безпосередньо працювати з масивами всередині документів — діставати окремі елементи, шукати значення, фільтрувати або трансформувати в нову структуру. Для цього є спеціальні оператори:
$arrayElemAt — повертає елемент масиву за заданим індексом;
$indexOfArray — повертає індекс вказаного значення в масиві;
$size — повертає кількість елементів у масиві.

$filter — залишає лише ті елементи масиву, які відповідають умові. Наприклад, прибрати з масиву жанр «drama».
$map — трансформує кожен елемент масиву. Наприклад, залишити лише імена акторів із поля cast, де кожен актор — обʼєкт.

У виразах $map і $filter для звернення до поточного елемента масиву слід використовувати змінну $$this.
Наприклад, витягнемо ролі Метью Макконагі:

Інші оператори:
$reduce — перетворення масиву на скаляр;
$arrayToObject — перетворення масиву на обʼєкт;
$concat — конкатенація рядків;
$mergeObjects — злиття обʼєктів;
$concatArrays — конкатенація масивів;
$toUpper / $toLower — приведення до регістра;
$setUnion — обʼєднання множин;
$slice — отримання підмножини масиву;
$setIntersection — перетин множин;
$switch — багатоваріантне гілкування;
$sortArray — сортування масиву;
$zip — транспонування двовимірного масиву.
Серед них уваги заслуговує $replaceWith, який повністю замінює документ. Наприклад, щоб замість всього документа лишити лише піддокумент director. Цей етап буквально викидає «обгортку» документа й залишає лише вказане поле або піддокумент.
Етапи, що змінюють структуру потоку документів
MongoDB має три ключові етапи агрегації, які змінюють структуру потоку документів: $match, $group і $unwind.
$match
Етап $match виконує фільтрацію документів за заданою умовою. Синтаксис $match ідентичний синтаксису, який вживається у методах findOne і find. Оператор вказується після імені поля, наприклад: { year: { $gte: 2000 } }.
Умовні оператори на етапі $match:
{ $eq: expr } / { $ne: expr } перевіряє, чи поле рівне / не рівне виразу;
{ $gt: expr } / { $lt: expr } перевіряє, чи поле більше / менше за вираз;
{ $in: array } / { $nin: array } перевіряє, чи поле міститься / не міститься у масиві;
{ $gte: expr } / { $lte: expr } перевіряє, чи поле ≥ / ≤ за вираз;
{ $exists: boolean } перевіряє, чи присутнє поле в документі;
{ $size: int } перевіряє, чи рівний розмір поля-масиву числу;
{ $regex: regex } перевіряє, чи задовольняє поле regex-виразу;
{ $type: string } перевіряє, чи має поле певний тип.
Наприклад, залишимо в результаті лише ті документи, які задовольняють двом умовам: жанри фільму містять 'Drama', а ім’я режисера закінчується на nolan (незалежно від регістру).

$group
Етап $group використовується для групування документів за певним ключем. Він зводить усі документи групи до одного документа, обчислюючи зведені значення: суму, середнє, мінімум, максимум тощо. Наприклад, щоб згрупувати фільми за іменем режисера і порахувати кількість фільмів та середній рейтинг.

Оператори, які можна використовувати:
$sum, $avg, $min, $max;
$first, $last;
$push — додає всі значення в масив;
$addToSet — додає лише унікальні значення в масив.
$unwind
Цей етап агрегації розгортає масив: з одного документа з масивом створює кілька документів — по одному на кожен елемент масиву. Його використовують, коли потрібно працювати з кожним елементом масиву окремо — наприклад, фільтрувати, групувати або рахувати їх.
Ці етапи часто комбінуються. Наприклад, щоб порахувати кількість фільмів у кожному жанрі або, щоб знайти кількість унікальних акторів у фільмах жанру «drama».
Так, етап $unwind розгортає масив жанрів у фільмах, увідповіднюючи кожному жанру окремий документ, а потім $group рахує кількість фільмів у кожному жанрі. «The Godfather» із двома жанрами дає два документи — для Crime і Drama, а «The Matrix», у якого жанри не вказані, буде включений до групи з _id: null завдяки параметру preserveNullAndEmptyArrays: true. У підсумку маємо підрахунок жанрів із урахуванням навіть відсутніх значень.

Отже, все вищезазначене — не просто набір команд MongoDB. Це приклад того, як підходити до обробки даних послідовно й структуровано, будувати пайплайн крок за кроком: від фільтрації через $match, до трансформації полів через $set, роботи з масивами через $map і $filter, і до групування та розгортання документів.
Найважливіше — зрозуміти логіку: кожен етап перетворює дані, які надходять з попереднього. І в кінці ми завжди отримуємо масив об’єктів, сформований точно під завдання. Це дозволяє будувати аналітику, змінювати структуру даних без постобробки на стороні коду.