SQL SELECT: WHERE, GROUP BY, HAVING, ORDER BY

Структура запроса SELECT, порядок выполнения предложений, фильтрация WHERE, агрегатные функции и группировка, отличие HAVING от WHERE, подзапросы.

SELECT — основной запрос, вокруг которого строится практическая часть курсовой по базам данных. Разобравшись с порядком выполнения его предложений, вы перестанете гадать, почему СУБД ругается на псевдоним столбца.

SELECT   d.name AS отдел, COUNT(*) AS сотрудников, AVG(e.salary) AS средняя
FROM     employees e
JOIN     departments d ON d.id = e.dept_id
WHERE    e.hired_at >= '2024-01-01'
GROUP BY d.name
HAVING   COUNT(*) > 3
ORDER BY средняя DESC
LIMIT    10;

Порядок выполнения

ПредложениеЧто делает
1FROM, JOINСобирает исходный набор строк
2WHEREОтсеивает строки до группировки
3GROUP BYСкладывает строки в группы
4HAVINGОтсеивает уже готовые группы
5SELECTВычисляет выражения и псевдонимы
6ORDER BYСортирует результат
7LIMIT / OFFSETОтрезает нужную порцию
SELECT выполняется пятым — вот почему псевдоним из SELECT нельзя использовать в WHERE: на момент фильтрации его ещё не существует. В ORDER BY можно, он идёт после.

Фильтрация в WHERE

WHERE salary BETWEEN 50000 AND 90000        -- включая границы
WHERE dept_id IN (1, 2, 5)
WHERE name LIKE 'Ив%'                        -- % любое число символов, _ ровно один
WHERE manager_id IS NULL                     -- только IS NULL, не = NULL
WHERE EXTRACT(YEAR FROM hired_at) = 2025
WHERE NOT (status = 'closed' OR archived)

NULL не равен ничему, включая другой NULL. Сравнение = NULL всегда даёт неопределённость, поэтому существует отдельный оператор IS NULL. Из-за этого же строки с NULL молча выпадают из результата условия вроде salary <> 100000.

Агрегатные функции

ФункцияСмыслУчитывает NULL
COUNT(*)Число строкДа, считает все строки
COUNT(col)Число непустых значенийНет, пропускает NULL
SUM(col)СуммаНет
AVG(col)СреднееНет — делит на количество непустых
MIN / MAXМинимум и максимумНет
AVG по столбцу с пропусками считает среднее только по заполненным значениям. Если пропуск по смыслу равен нулю, пишите AVG(COALESCE(col, 0)) — иначе средняя окажется завышенной.

GROUP BY и правило совместимости

Каждый столбец в SELECT должен быть либо перечислен в GROUP BY, либо обёрнут в агрегатную функцию. Иначе СУБД не знает, какое из значений группы показать. PostgreSQL и стандартный SQL выдадут ошибку, MySQL в нестрогом режиме молча вернёт произвольное значение — и это хуже ошибки.

-- Неверно: city не в GROUP BY и не агрегирован
SELECT dept_id, city, COUNT(*) FROM employees GROUP BY dept_id;

-- Верно
SELECT dept_id, city, COUNT(*) FROM employees GROUP BY dept_id, city;

-- Или агрегируем
SELECT dept_id, MAX(city), COUNT(*) FROM employees GROUP BY dept_id;

WHERE или HAVING

WHEREHAVING
ФильтруетОтдельные строкиГотовые группы
КогдаДо группировкиПосле группировки
АгрегатыНельзяМожно и нужно
ПримерWHERE salary > 50000HAVING AVG(salary) > 50000

Правило выбора: условие относится к отдельной записи — WHERE, к характеристике группы — HAVING. Если можно отфильтровать в WHERE, делайте это там: строк до группировки останется меньше и запрос отработает быстрее.

Подзапросы

-- Скалярный: возвращает одно значение
SELECT name, salary FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);

-- В списке значений
SELECT * FROM departments
WHERE id IN (SELECT dept_id FROM employees WHERE salary > 100000);

-- Коррелированный: выполняется для каждой строки внешнего запроса
SELECT d.name FROM departments d
WHERE EXISTS (SELECT 1 FROM employees e
              WHERE e.dept_id = d.id AND e.salary > 150000);

-- CTE — читается сверху вниз, удобно для курсовой
WITH avg_by_dept AS (
    SELECT dept_id, AVG(salary) AS avg_sal
    FROM employees GROUP BY dept_id
)
SELECT d.name, a.avg_sal
FROM avg_by_dept a JOIN departments d ON d.id = a.dept_id
WHERE a.avg_sal > 80000;

Типичные ошибки

Частые вопросы

Чем отличается COUNT(*) от COUNT(поле)?

COUNT(*) считает все строки группы, COUNT(поле) — только те, где поле не NULL. Разница этих двух чисел показывает количество пропусков.

Можно ли использовать GROUP BY без агрегатных функций?

Можно: получится список уникальных комбинаций, как при DISTINCT. Но если задача только в уникальности, DISTINCT читается понятнее.

Что быстрее: подзапрос или JOIN?

В современных СУБД оптимизатор часто приводит их к одному плану. Коррелированные подзапросы опаснее — они могут выполняться для каждой строки. Смотрите план через EXPLAIN, а в курсовой приложите его как доказательство обоснованности решения.

Читайте также

Сделаем работу по этой теме

Опишите задачу — ответим в течение 15 минут в личных сообщениях ВКонтакте, назовём срок и цену. Предоплаты за оценку нет.

  • Оценка заявки бесплатно
  • Правки по замечаниям преподавателя
  • Работы по всем техническим и IT-дисциплинам

Нажимая кнопку, вы соглашаетесь на обработку указанных данных для ответа на заявку.