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

Фильтрация и группировка в 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 <условие>
  • Комбинируются для построения сложных фильтров.
  • Приоритет: NOT → AND → OR. Использование скобок уточняет порядок.

Сортировка​

★ Сортировка – ключевая операция, которая сортирует результаты запроса по указанным столбцам (по возрастанию или убыванию), путём выполнения 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, с условиями на агрегаты. Логический порядок: WHERE → GROUP BY → HAVING → SELECT → ORDER 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;

По умолчанию: при ASC — NULLS LAST, при DESC — NULLS 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 строк — что увидит внешний запрос?

См. также​