Аккумуляторы, $project и $lookup
Вычисления внутри групп, формирование вида результата и аналог JOIN между коллекциями.
Аккумуляторы — функции, которые сводят группу документов к одному значению: сумма, среднее, минимум, максимум, количество.
Аккумуляторы в $group
Внутри $group кроме $sum доступны $avg (среднее), $min, $max и $push (собрать значения в массив). Статистика заказов по городам сразу по нескольким метрикам:
db.orders.aggregate([
{ $group: {
_id: "$city",
revenue: { $sum: "$total" },
avgCheck: { $avg: "$total" },
maxOrder: { $max: "$total" },
count: { $sum: 1 }
} }
])За один проход получаем выручку, средний чек, самый крупный заказ и количество. В SQL это были бы SUM, AVG, MAX, COUNT.
$project — выбрать и вычислить поля
Этап $project формирует вид документов: какие поля оставить, как их переименовать, какие вычислить. Включаем 1, исключаем 0, а новые поля задаём выражением:
db.orders.aggregate([
{ $project: {
_id: 0,
city: 1,
total: 1,
withVat: { $multiply: ["$total", 1.2] }
} }
])Поле withVat вычисляется на лету — цена с НДС 20%. В $project доступна арифметика ($multiply, $add, $subtract), работа со строками и условия.
$lookup — соединение коллекций
Когда данные разнесены по коллекциям (ссылки!), их объединяют этапом $lookup — это и есть аналог LEFT JOIN. Подтянем к заказам данные пользователя:
db.orders.aggregate([
{ $lookup: {
from: "users",
localField: "userId",
foreignField: "_id",
as: "user"
} }
])Смысл полей: from — к какой коллекции присоединяем, localField — поле-ссылка в текущем документе, foreignField — поле в той коллекции, as — имя нового поля для результата. Подошедшие документы складываются в массив:
Результат:
{
"_id": "o1",
"userId": "u1",
"total": 1800,
"user": [ { "_id": "u1", "name": "Анна" } ]
}Обратите внимание: user — это всегда массив (совпасть может несколько документов). Часто после $lookup добавляют $unwind, чтобы развернуть массив в отдельные документы.
$lookup стоит дорого
$lookup мощный, но небесплатный: соединение коллекций на больших объёмах медленнее, чем чтение одного встроенного документа. Поэтому, если данные почти всегда нужны вместе, в документной модели их предпочитают встраивать заранее, а $lookup приберечь для случаев, когда встраивание неуместно.
Итог
- Аккумуляторы
$sum,$avg,$min,$maxсводят группу к числу внутри$group. $projectформирует вид результата и вычисляет новые поля выражениями.$lookup— аналогJOINмежду коллекциями; результат — массив, операция дорогая, поэтому встраивание часто предпочтительнее.