Агрегатные функции принимают множество строк и возвращают одно значение.
COUNT(): считаем строкиSELECT COUNT(*)
FROM employees;→ 8 (всего сотрудников)
⚠️ Важный нюанс:
COUNT(*)считает все строки, включая те, где естьNULL.
Если хочешь посчитать только строки, где в конкретном поле есть значение:
SELECT COUNT(bonus)
FROM employees;→ 4 (только у Анны, Марии, Елены, Дарьи есть премия)
COUNT(bonus) игнорирует NULL. Это частая причина ошибок: если ты хочешь узнать, сколько сотрудников получают премию, используй COUNT(bonus), а не COUNT(*).
Можно посчитать уникальные значения:
SELECT COUNT(DISTINCT city)
FROM employees;→ 3 (Москва, СПб, Казань)
SUM(): сумма значенийSELECT SUM(salary)
FROM employees;→ 760 000 (общий фонд зарплат)
Если в столбце есть NULL, SUM() их игнорирует. Если все строки NULL, результат будет NULL.
AVG(): среднее значениеSELECT AVG(salary)
FROM employees;→ 95 000 (760 000 / 8)
AVG() тоже игнорирует NULL. Если у тебя 10 сотрудников, но у 3-х зарплата NULL, среднее считается по 7.
MIN() и MAX(): минимум и максимумSELECT MIN(salary), MAX(salary)
FROM employees;→ 70 000 (Андрей), 130 000 (Мария)
Работают не только с числами, но и с датами, строками (лексикографический порядок).
GROUP BY: группируем данныеАгрегатные функции сами по себе возвращают одно число. Но часто нам нужно посчитать метрику для каждой категории.
Например: сколько сотрудников в каждом отделе?
SELECT department, COUNT(*)
FROM employees
GROUP BY department;Результат:
| department | count |
|---|---|
| IT | 3 |
| Продажи | 3 |
| Маркетинг | 2 |
employees.GROUP BY department разбивает их на группы по значению department.COUNT(*).💡 Ключевое правило: в
SELECTможно указывать только:
- поля из
GROUP BY(по которым группируем)- агрегатные функции
Всё остальное — ошибка. Почему? Потому что в группе может быть несколько значений, и база не знает, какое из них показать.
-- НЕПРАВИЛЬНО:
SELECT department, name, COUNT(*)
FROM employees
GROUP BY department;В отделе IT три сотрудника (Анна, Мария, Павел). Какое имя показать? База не знает, поэтому выдаст ошибку.
Можно группировать по комбинации полей:
SELECT department, city, COUNT(*)
FROM employees
GROUP BY department, city;Результат:
| department | city | count |
|---|---|---|
| IT | Москва | 2 |
| IT | Казань | 1 |
| Продажи | СПб | 2 |
| Продажи | Москва | 1 |
| Маркетинг | Москва | 1 |
| Маркетинг | Казань | 1 |
Каждая уникальная комбинация (department, city) — отдельная группа.
HAVING: фильтруем группыWHERE фильтрует строки до группировки. HAVING фильтрует группы после группировки.
Пример: найдём отделы, где больше 2 сотрудников.
SELECT department, COUNT(*) AS employee_count
FROM employees
GROUP BY department
HAVING COUNT(*) > 2;Результат:
| department | employee_count |
|---|---|
| IT | 3 |
| Продажи | 3 |
Почему нельзя использовать WHERE? Потому что WHERE работает до GROUP BY, когда групп ещё нет. А HAVING — после, когда группы уже сформированы и можно применять агрегаты.
WHERE vs HAVING: когда что использоватьИспользуй WHERE, если фильтр не зависит от агрегатов:
-- Сотрудники из Москвы, сгруппированные по отделам
SELECT department, COUNT(*)
FROM employees
WHERE city = 'Москва'
GROUP BY department;→ IT: 2, Маркетинг: 1, Продажи: 1
Используй HAVING, если фильтр зависит от агрегата:
-- Отделы, где средняя зарплата больше 100 000
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
HAVING AVG(salary) > 100000;→ IT: 120 000
💡 Оптимизация: если можешь отфильтровать через
WHERE— делай это.WHEREуменьшает количество строк до группировки, что быстрее.HAVINGфильтрует уже после того, как всё посчитано.
-- Медленнее (считает агрегат для всех, потом фильтрует):
SELECT department, AVG(salary)
FROM employees
GROUP BY department
HAVING department = 'IT';
-- Быстрее (сразу берёт только IT):
SELECT department, AVG(salary)
FROM employees
WHERE department = 'IT'
GROUP BY department;Теперь, когда ты знаешь GROUP BY и HAVING, давай соберём полный порядок:
| Этап | Клауза | Описание |
|---|---|---|
| 1 | FROM | → берём таблицу |
| 2 | WHERE | → фильтруем строки (до группировки) |
| 3 | GROUP BY | → группируем строки |
| 4 | HAVING | → фильтруем группы (после группировки) |
| 5 | SELECT | → выбираем/считаем столбцы, применяем DISTINCT |
| 6 | ORDER BY | → сортируем результат |
| 7 | LIMIT | → обрезаем результат |
1. В WHERE нельзя использовать псевдонимы из SELECT:
-- НЕ РАБОТАЕТ:
SELECT salary * 12 AS annual_salary
FROM employees
WHERE annual_salary > 1000000;Почему? Потому что WHERE выполняется до SELECT, и псевдоним annual_salary ещё не существует.
Правильно:
SELECT salary * 12 AS annual_salary
FROM employees
WHERE salary * 12 > 1000000;2. В WHERE нельзя использовать агрегаты:
-- НЕ РАБОТАЕТ:
SELECT department, COUNT(*)
FROM employees
WHERE COUNT(*) > 2
GROUP BY department;Почему? Потому что WHERE выполняется до GROUP BY, и групп ещё нет.
Правильно — использовать HAVING:
SELECT department, COUNT(*)
FROM employees
GROUP BY department
HAVING COUNT(*) > 2;3. В ORDER BY можно использовать псевдонимы:
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
ORDER BY avg_salary DESC;Почему работает? Потому что ORDER BY выполняется после SELECT, и псевдоним уже существует.
4. В HAVING можно использовать псевдонимы (в некоторых СУБД):
В PostgreSQL можно:
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
HAVING AVG(salary) > 100000;Но в некоторых СУБД (например, MySQL старых версий) нельзя. Лучше писать явно: HAVING AVG(salary) > 100000.
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
HAVING COUNT(*) >= 3
ORDER BY avg_salary DESC;Результат:
| department | avg_salary |
|---|---|
| IT | 120 000 |
| Продажи | 85 000 |
Логика:
department.COUNT(*) >= 3 (IT и Продажи).AVG(salary) для оставшихся групп.SELECT city, COUNT(*) AS bonus_employees
FROM employees
WHERE bonus IS NOT NULL
GROUP BY city;Результат:
| city | bonus_employees |
|---|---|
| Москва | 1 |
| Казань | 2 |
| СПб | 1 |
Логика:
WHERE bonus IS NOT NULL — оставляем только тех, у кого есть премия (Анна, Мария, Елена, Дарья).city.SELECT department, MAX(salary) AS max_salary, COUNT(*) AS employee_count
FROM employees
GROUP BY department
HAVING MAX(salary) > 100000;Результат:
| department | max_salary | employee_count |
|---|---|---|
| IT | 130 000 | 3 |
Попробуй решить самостоятельно.
-- 1
SELECT COUNT(*)
FROM employees;
-- 2
SELECT COUNT(bonus)
FROM employees
WHERE bonus IS NOT NULL;
-- 3
SELECT MIN(salary) AS min_sal, MAX(salary) AS max_sal, AVG(salary) AS avg_sal
FROM employees;
-- 4
SELECT department, COUNT(*)
FROM employees
GROUP BY department;
-- 5
SELECT city, AVG(salary)
FROM employees
GROUP BY city;
-- 6
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
HAVING AVG(salary) > 90000;
-- 7
SELECT city, COUNT(*) AS bonus_count
FROM employees
WHERE bonus IS NOT NULL
GROUP BY city;
-- 8
SELECT department, AVG(salary) AS avg_salary, COUNT(*) AS emp_count
FROM employees
GROUP BY department
HAVING COUNT(*) > 2 AND AVG(salary) > 80000
ORDER BY avg_salary DESC;Ты готова к третьему этапу, если можешь без подсказки объяснить:
COUNT(*) отличается от COUNT(column_name);SELECT после GROUP BY можно использовать только поля из GROUP BY и агрегаты;WHERE отличается от HAVING по моменту выполнения;WHERE нельзя использовать псевдонимы из SELECT;