SQL
SQL — декларативный язык работы с реляционными данными. В запросе мы описываем желаемый результат, а СУБД выбирает физический план: порядок соединений, способы доступа к таблицам, индексы и алгоритмы сортировки.
Учебная схема
Примеры в этом разделе используют три таблицы:
CREATE TABLE users (
id BIGINT PRIMARY KEY,
email VARCHAR(320) NOT NULL UNIQUE,
status VARCHAR(20) NOT NULL,
created_at TIMESTAMP NOT NULL
);
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
state VARCHAR(20) NOT NULL,
total NUMERIC(12, 2) NOT NULL CHECK (total >= 0),
created_at TIMESTAMP NOT NULL
);
CREATE TABLE items (
id BIGINT PRIMARY KEY,
order_id BIGINT NOT NULL REFERENCES orders(id),
sku VARCHAR(100) NOT NULL,
quantity INTEGER NOT NULL CHECK (quantity > 0),
price NUMERIC(12, 2) NOT NULL CHECK (price >= 0)
);erDiagram users ||--o{ orders : places orders ||--|{ items : contains
Это маленькая модель, но на ней видны основные задачи: фильтрация, соединение, агрегация, ограничения, транзакции и индексы.
Логический порядок SELECT
Запрос записывают начиная с SELECT, но логически его части обрабатываются иначе:
FROMиJOINформируют набор строк.WHEREотбрасывает строки.GROUP BYсобирает оставшиеся строки в группы.- Агрегатные функции вычисляют значения для групп.
HAVINGотбрасывает группы.SELECTформирует столбцы результата.DISTINCTудаляет дубликаты результата.ORDER BYсортирует.LIMITилиFETCHограничивает выдачу.
Физический порядок может отличаться: оптимизатор вправе переставлять эквивалентные операции, если это не меняет смысл.
SELECT u.id, COUNT(*) AS paid_orders
FROM users AS u
JOIN orders AS o ON o.user_id = u.id
WHERE o.created_at >= TIMESTAMP '2026-01-01 00:00:00'
AND o.state = 'PAID'
GROUP BY u.id
HAVING COUNT(*) >= 2
ORDER BY paid_orders DESC, u.id
FETCH FIRST 20 ROWS ONLY;Псевдоним paid_orders доступен в ORDER BY, но обычно недоступен в WHERE: на логическом шаге WHERE выражение из SELECT еще не вычислено.
NULL и трехзначная логика
NULL означает отсутствие известного значения. Сравнение с ним дает не TRUE или FALSE, а UNKNOWN.
-- Неверно: условие никогда не становится TRUE.
WHERE email = NULL
-- Верно:
WHERE email IS NULLWHERE оставляет только строки, для которых предикат равен TRUE; FALSE и UNKNOWN отбрасываются. Поэтому NOT IN особенно опасен, если подзапрос может вернуть NULL: часто надежнее NOT EXISTS.
Карта раздела
- устройство запросов: Операции SQL по группам, Условный оператор WHERE, Оператор GROUP BY, Оператор HAVING;
- наборы строк: Запрос из 2х таблиц, Теория множеств, Объединение запросов, оператор UNION;
- модель данных: Ключи БД, Ограничения, constraints, Нормализация денормализация;
- надежность: Транзакции принцип ACID, Изоляция транзакций;
- производительность: Индексы SQL, SQL оценка производительности, Селективность SQL;
- распределение данных: Шардирование базы данных.
Что помнить на собеседовании
SQL — это алгоритм? Нет. Это декларация результата; алгоритм выполнения строит оптимизатор.
Почему результат без ORDER BY не имеет гарантированного порядка? Реляционный результат — неупорядоченный набор; план и физическое размещение строк могут измениться.
Чем WHERE отличается от HAVING? WHERE фильтрует строки до группировки, HAVING — группы после нее.
Почему запрос, быстрый на тестовых данных, может замедлиться в production? Меняются объем, распределение значений, кэш, параллельная нагрузка и статистика, а вместе с ними — план.
Что важнее синтаксической «красоты» запроса? Корректная семантика, подтвержденный план и измерения на репрезентативных данных.