Перейти к основному содержимому

Фильтрация и группировка в SQL

Разработчику Аналитику Тестировщику
Архитектору Инженеру

Перед чтением: Операторы — операнды, приоритеты, типы; здесь то же в WHERE и условиях.


Фильтрация и группировка в SQL

Операции, которые уточняют какие строки попасть в ответ и как их сгруппировать. Сначала — SELECT и NULL / трёхзначная логика; здесь — расширение и связь с подзапросами.

ТемаУглубление
Синтаксис SELECTОператор SELECT — синтаксис и стиль
NULL, NOT INФильтрация и трёхзначная логика
EXISTS, подзапросыПодзапросы, EXISTS и IN
JOINАлиасы, JOIN и объединение таблиц

LIMIT

LIMIT — это ключевое слово в SQL, которое используется для ограничения количества строк, возвращаемых запросом. Оно полезно, когда нужно получить только определенное число строк из результата запроса, например, топ-N записей или выборку для пагинации.

SELECT * FROM users LIMIT 10;

Порой требуется получить только часть результатов — например, для постраничного вывода или отладки. Синтаксис может варьироваться в зависимости от диалекта SQL: в PostgreSQL и MySQL LIMIT n указывает максимальное число строк; в стандарте SQL и в некоторых других СУБД, таких как SQL Server, используется конструкция TOP или OFFSET ... FETCH. В SQLite и MySQL LIMIT также поддерживает смещение: LIMIT offset, count или LIMIT count OFFSET offset.

SELECT <выборка>
FROM <таблица>
[WHERE <условие>]
[ORDER BY <столбец> [ASC | DESC]]
LIMIT <количество>;
  • <количество> — максимальное число возвращаемых строк.
  • В SQL Server вместо LIMIT часто используют TOP сразу после SELECT:
-- SQL Server
SELECT TOP 10 name, price FROM products ORDER BY price DESC;

-- PostgreSQL / MySQL
SELECT name, price FROM products ORDER BY price DESC LIMIT 10;

Альтернативный синтаксис смещения:

SELECT <выборка>
FROM <таблица>
LIMIT <смещение>, <количество>;
-- или
LIMIT <количество> OFFSET <смещение>;

Фильтрация

Фильтрация данных – одна из самых важных операций в SQL, позволяет выбирать только нужные данные из таблиц. Оператор WHERE используется для указания условий отбора записей.

Примеры простых условий:

-- Выбрать всех пользователей старше 25 лет
SELECT * FROM users WHERE age > 25;

-- Выбрать товары с ценой от 100 до 500 рублей
SELECT * FROM products WHERE price BETWEEN 100 AND 500;

-- Выбрать заказы, сделанные сегодня
SELECT * FROM orders WHERE order_date = CURRENT_DATE;
SELECT <выборка>
FROM <таблица>
WHERE <условие>;
  • <условие> — логическое выражение, содержащее сравнения, проверки на NULL, диапазоны (BETWEEN), списки (IN) и шаблоны (LIKE).

Примеры условий:

  • column = value
  • column BETWEEN low AND high
  • column IN (value1, value2, ...)
  • column LIKE 'шаблон'
  • column IS NULL

Операторы SQL

Основные операторы в SQL сравнительные и логические. Рассмотрим их в таблице:

ОператорОписаниеПример
=РавноWHERE age = 30
<> или !=Не равноWHERE status <> 'active'
>БольшеWHERE price > 1000
<МеньшеWHERE rating < 3.5
>=Больше или равноWHERE quantity >= 10
<=Меньше или равноWHERE age <= 18
BETWEENВ диапазоне (включительно)WHERE price BETWEEN 50 AND 100
INВ списке значенийWHERE id IN (1, 5, 10)
NOT INНе в списке значенийWHERE country NOT IN ('US', 'UK')
IS NULLПроверка на NULLWHERE email IS NULL
IS NOT NULLПроверка на не-NULLWHERE phone IS NOT NULL
ANDИWHERE country = 'Russia' AND age > 25;
ORИЛИWHERE price < 100 OR price > 1000;
NOTНЕWHERE NOT status = 'cancelled';
LIKEШаблон с % (любая длина) и _ (один символ); это не полный RegEx, в PostgreSQL для regex — оператор ~WHERE name LIKE 'Ан%'; WHERE email LIKE '%@gmail.com'; WHERE name LIKE '____';
... WHERE <условие_1> AND <условие_2>
... WHERE <условие_1> OR <условие_2>
... WHERE NOT <условие>
  • Комбинируются для построения сложных фильтров.
  • Приоритет: NOTANDOR. Использование скобок уточняет порядок.

Сортировка

Сортировка – ключевая операция, которая сортирует результаты запроса по указанным столбцам (по возрастанию или убыванию), путём выполнения ORDER BY:

SELECT column1, column2
FROM table_name
ORDER BY column1 [ASC|DESC], column2 [ASC|DESC];

ASC (по умолчанию) – сортировка по возрастанию (A-Z, 0-9);

SELECT name, salary FROM employees ORDER BY salary ASC;

DESC – по убыванию (Z-A, 9-0).

SELECT name, salary FROM employees ORDER BY salary DESC;
SELECT <выборка>
FROM <таблица>
[WHERE <условие>]
ORDER BY <столбец_1> [ASC | DESC][, <столбец_2> [ASC | DESC], ...];
  • ASC — сортировка по возрастанию (по умолчанию).
  • DESC — сортировка по убыванию.
  • Допускается указание нескольких столбцов.

Шпаргалка — агрегаты и группировка

КонструкцияНазначение
GROUP BYРазбить строки на группы с одинаковыми значениями в указанных столбцах
HAVINGОтфильтровать группы (условие на COUNT, SUM, AVG и т.д.)
COUNTЧисло строк или ненулевых значений в группе
SUMСумма значений столбца
AVGСреднее арифметическое
MINМинимальное значение
MAXМаксимальное значение

WHERE отсекает отдельные строки до GROUP BY и не может содержать агрегатные функции (COUNT, SUM, AVG); HAVING — уже сформированные группы после GROUP BY, с условиями на агрегаты. Логический порядок: WHEREGROUP BYHAVINGSELECTORDER BYПринципы работы SQL-движка.


Группировка

Группировка объединяет строки с одинаковыми значениями в группы – GROUP BY. Для каждой группы может быть вычислено агрегированное значение с использованием функций, таких как COUNT, SUM, AVG, MIN, MAX. Это агрегатные функции - о них мы поговорим отдельно.

Структура запроса с группировкой:

SELECT column, AGG_FUNC(expression)
FROM table
WHERE condition
GROUP BY column
HAVING group_condition
ORDER BY column;

Предложение GROUP BY определяет, по каким признакам производится объединение строк. Все уникальные комбинации значений в указанных столбцах формируют отдельную группу.

SELECT department_id, COUNT(*) AS employee_count
FROM employees
GROUP BY department_id;

Запрос возвращает количество сотрудников в каждом отделе.

Если в списке выборки присутствуют неагрегированные столбцы, они должны входить в GROUP BY. В противном случае запрос будет некорректным (за исключением некоторых диалектов, допускающих функциональную зависимость).

Можно группировать по нескольким столбцам:

SELECT department_id, job_title, AVG(salary)
FROM employees
GROUP BY department_id, job_title;

Это позволяет получить среднюю зарплату по каждой должности внутри отдела.

Кроме этого, фильтрация может быть через HAVING.

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

WHERE применяется до GROUP BY — фильтрует строки таблицы.

HAVING применяется после GROUP BY — фильтрует результаты по агрегированным значениям.

-- Отделы, где средняя зарплата > 100000
SELECT department, AVG(salary) as avg_salary
FROM employees
GROUP BY department
HAVING AVG(salary) > 100000;
-- Только активные сотрудники (фильтр до группировки)
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
WHERE status = 'active'
GROUP BY department_id
HAVING AVG(salary) > 80000; -- Фильтр по средней зарплате (после группировки)

Здесь:

WHERE status = 'active' исключает уволенных до формирования групп.

HAVING AVG(salary) > 80000 оставляет только те отделы, где средняя зарплата превышает 80 000.

В HAVING можно использовать агрегатные функции (AVG, SUM, COUNT и т.д.), так как они рассчитываются на уровне групп. Строки с NULL в столбце группировки формируют отдельную группу. Все NULL значения считаются равными друг другу в контексте GROUP BY. Некоторые СУБД поддерживают расширенные формы группировки:

  • GROUP BY ROLLUP(...) — формирует итоговые строки по иерархии.
  • GROUP BY CUBE(...) — все возможные комбинации группировок.
SELECT <группирующий_столбец>[, <другой_группирующий_столбец>, ...],
<агрегатная_функция>(<выражение>)
FROM <таблица>
[WHERE <условие_до_группировки>]
GROUP BY <группирующий_столбец>[, <другой_группирующий_столбец>, ...]
[HAVING <условие_после_группировки>]
[ORDER BY <столбец> [ASC | DESC]];
  • Все неагрегированные столбцы в SELECT обязаны входить в GROUP BY.
  • HAVING фильтрует уже сформированные группы.
  • Агрегатные функции — COUNT(), SUM(), AVG(), MIN(), MAX().
... HAVING <агрегатное_условие>;
  • Пример: HAVING AVG(salary) > 100000
  • Может содержать агрегатные функции и ссылаться на столбцы из GROUP BY.

Вложенные запросы (подзапросы)

ПодзапросSELECT, вложенный в другой запрос. Логически задаёт промежуточный результат; физически порядок выбирает оптимизатор (отдельное выполнение, JOIN, materialize).

Полный разбор: Подзапросы, EXISTS и IN.


Классификация подзапросов

Скалярный подзапрос

Ожидается одно значение (одна строка, один столбец):

СитуацияРезультат
Одна строкаЗначение подставляется
Ноль строкNULL
Больше одной строкиОшибка

Пример в проекции:

SELECT name, salary,
(SELECT AVG(salary) FROM employees) AS avg_salary
FROM employees;

Здесь подзапрос возвращает среднюю зарплату по всем сотрудникам и дублирует её в каждой строке результата.

SELECT <столбец>,
(<подзапрос>) AS <алиас>
FROM <таблица>;
  • <подзапрос> возвращает одно значение.
  • Используется в SELECT, WHERE, HAVING.

Строчный подзапрос

Возвращает одну строку с несколькими столбцами. Используется, например, при сравнении составных значений.

Пример:

SELECT * FROM employees
WHERE (name, salary) = (
SELECT name, salary FROM employees
WHERE id = 101
);
SELECT *
FROM <таблица>
WHERE (<столбец_1>, <столбец_2>, ...) = (
SELECT <столбец_1>, <столбец_2>, ...
FROM <таблица>
WHERE <условие>
);
  • Сравнивает составные значения.
  • Обязательно равенство количества и типов столбцов.

Табличный подзапрос

Возвращает набор строк и столбцов. Может использоваться в предложении FROM или WHERE.

Пример:

SELECT e.name, e.salary
FROM (SELECT * FROM employees WHERE salary > 50000) AS e;

Подзапрос формирует временную таблицу "e" с сотрудниками, получающими более 50 000.

SELECT <выборка>
FROM (<подзапрос>) AS <алиас>
[WHERE <условие>];
  • Подзапрос возвращает таблицу.
  • Обязательно наличие алиаса.

Подзапросы с оператором ALL

Оператор ALL используется для сравнения значения с каждым элементом результата подзапроса. Условие считается истинным, только если оно выполняется для всех значений.

Пример:

SELECT name, salary
FROM employees
WHERE salary > ALL (
SELECT salary FROM employees WHERE department_id = 5
);

Запрос возвращает сотрудников, чья зарплата выше любой зарплаты в отделе 5 (то есть выше максимальной в этом отделе).

Аналогичный результат можно получить с помощью MAX, но ALL полезен в контексте обобщённых условий.

SELECT <выборка>
FROM <таблица>
WHERE <значение> > ALL (<подзапрос>);
  • Условие истинно, если выполняется для всех значений из подзапроса.
  • Аналогично — <, >=, <=, <> ALL.

Многостолбцовые подзапросы

Многостолбцовые подзапросы позволяют сравнивать несколько столбцов одновременно. Часто используются с операторами IN, EXISTS, =, >, < и ALL.

Пример:

SELECT * FROM employees
WHERE (department_id, salary) IN (
SELECT department_id, MAX(salary)
FROM employees
GROUP BY department_id
);

Запрос находит сотрудников с максимальной зарплатой в каждом отделе.

SELECT *
FROM <таблица_1>
WHERE (<столбец_A>, <столбец_B>) IN (
SELECT <столбец_X>, <столбец_Y>
FROM <таблица_2>
GROUP BY <столбец_X>
HAVING <агрегатное_условие>
);
  • Позволяет сравнивать кортежи значений.
  • Часто используется для поиска экстремумов внутри групп.

Коррелированные и некоррелированные подзапросы

Некоррелированный подзапрос — выполняется независимо от внешнего запроса. Его можно вычислить один раз.

Пример:

SELECT name FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);

Коррелированный подзапрос — зависит от внешнего запроса и выполняется для каждой строки внешнего результата. Содержит ссылки на столбцы из внешнего запроса.

Пример:

SELECT e1.name, e1.salary, e1.department_id
FROM employees e1
WHERE e1.salary = (
SELECT MAX(e2.salary)
FROM employees e2
WHERE e2.department_id = e1.department_id
);

Для каждого сотрудника (e1) подзапрос находит максимальную зарплату в его отделе. Затем проверяется, равна ли зарплата сотрудника этому максимуму.

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

SELECT <столбцы_внешнего>
FROM <таблица> AS t1
WHERE <условие> = (
SELECT <агрегатная_функция>(<столбец>)
FROM <таблица> AS t2
WHERE t2.<связующий_столбец> = t1.<связующий_столбец>
);
  • Подзапрос ссылается на столбцы внешнего запроса (t1).
  • Выполняется для каждой строки внешнего результата.

Сортировка NULL и пагинация

В PostgreSQL порядок NULL при сортировке можно задать явно:

SELECT name, stock_qty
FROM products
ORDER BY stock_qty NULLS LAST, name ASC;

По умолчанию: при ASCNULLS LAST, при DESCNULLS FIRST.

Пагинация — всегда с ORDER BY, иначе порядок строк нестабилен:

-- Страница 2 по 20 записей (смещение 20)
SELECT product_id, name, price
FROM products
ORDER BY product_id
LIMIT 20 OFFSET 20;

OFFSET на больших смещениях дорог: СУБД всё равно "пропускает" строки. Для глубокой пагинации используют ключевую выборку:

SELECT product_id, name, price
FROM products
WHERE product_id > :last_seen_id
ORDER BY product_id
LIMIT 20;

Стандарт SQL:2008 — FETCH FIRST n ROWS ONLY / OFFSET m ROWS (PostgreSQL 13+). Диалект PostgreSQL: LIMIT n OFFSET m.

В REST API тот же keyset-подход встречается как ?after_id=102&limit=20 или opaque cursorПагинация в API — шесть схем.

Подробнее о NULL в условиях: Фильтрация и трёхзначная логика. Операции UNION / INTERSECT: Алиасы, JOIN.


Контрольные вопросы

  1. Чем WHERE отличается от HAVING?
  2. Почему без GROUP BY dept, name может быть ошибкой?
SELECT dept, name, AVG(sal)
  1. Зачем пагинации нужен стабильный ORDER BY?
  2. Скалярный подзапрос вернул 0 строк — что увидит внешний запрос?

См. также