# Агрегация в MongoDB 

## Как запустить агрегацию?
В MongoDB Compass - это вкладка `aggregation`;
В mongo shell или mongosh — используется метод `aggregation()`.

## Отличия от SQL

Агрегация в MongoDB несколько отличается от агрегации в SQL.

Главная причина - наличие pipeline (конвейера), который позволяет последовательно указать несколько операций,  
где результаты (выходные документы) предыдущей операции будут входными документами для последующей.

Таким образом, все БЕЗ ИСКЛЮЧЕНИЯ запросы MongoDB можно выразить через также через агрегацию.

**Задание для примера**:  
Выбрать документы, где возраст сотрудника больше 30 лет. 
Оставить только имя и зарплату, отсортировать по з/п по убыванию.
Пропустить первые 10 документов, и вывести следующие 10.

- Обычный запрос
```
db.getCollection('employees').find(
    { age: { $gt: 30 } }, 
    { name: 1, salary: 1, _id: 0 }
  )
  .sort({ salary: -1 })
  .skip(10)
  .limit(10);
```

Запрос через агрегацию:
```
db.employees.aggregate(
    [
        { "$match": { "age": { "$gt": 30 } } },
        { "$project": { "_id": 0, "name": 1, "salary": 1 } },
        { "$sort": { "salary": -1 } },
        { "$skip": 10 },
        { "$limit": 10 }
    ]
);
```

## Непосредственно группировка

Бывает двух видов:
- `$group` - похожа на `GROUP BY`
- `$sortByCount` - одновременно: 
  - группирует, 
  - сортирует по убыванию 
  - и подсчитывает кол-во документов в каждой группе
  - (те есть фактически это две стадии: `$group` и `$sort`)

### Стадия `$group` 
                    
#### Синтаксис:
```
{
  _id: expression,
  fieldN: {
    accumulatorN: expressionN
  }
}
```
где 
- `expression` - поле, по которому происходит группировка. Если оно `null`, значит группировка идёт по всей коллекции
- `fieldN` - произвольное имя агрегированного значения
- `expressionN` - поле, по которому идёт агрегация
- `accumulatorN` - функция-агрегатор

| Оператор     | Назначение                                          | Пример использования     |
|--------------|-----------------------------------------------------|--------------------------|
| `$sum`       | Суммирует значения в группе                         | `"$sum": "$price"`       |
| `$avg`       | Вычисляет среднее значение                          | `"$avg": "$age"`         |
| `$min`       | Находит минимальное значение                        | `"$min": "$score"`       |
| `$max`       | Находит максимальное значение                       | `"$max": "$score"`       |
| `$first`     | Первое значение в группе (в порядке поступления)    | `"$first": "$name"`      |
| `$last`      | Последнее значение в группе                         | `"$last": "$name"`       |
| `$push`      | Добавляет значение в массив                         | `"$push": "$tag"`        |
| `$addToSet`  | Добавляет уникальные значения в массив (без дубликатов) | `"$addToSet": "$tag"` |
| `$count`     | Подсчёт количества документов (через `$sum: 1`)     | `"$sum": 1`              |


#### Пример:
Найти средний возраст всех сотрудников по отделам:
```
{
    $group: {
        _id: "$department_id",
        avg_age: { $avg: "$age" }
    }
}
```
Найти средний возраст всех сотрудников по всех фирме (в смысле, по всей коллекции):
```
{
    $group: {
        _id: null,
        avg_age: { $avg: "$age" }
    }
}
```

### Стадия `$sortByCount` 
                    
#### Синтаксис:
```
{
    $sortByCount:  <expression> 
}
```
где `expression` - поле, по которому идёт группировка, подсчёт значений и их сортировка
(то есть в поле агрегации просто пишется поле с долларом, в кавычках, но БЕЗ скобок!)

ВАЖНО: `$sortByCount` не используется для вычисления max/min значений. 
Для этого используется `$group` с `$max` или `$min`.