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, но логически его части обрабатываются иначе:

  1. FROM и JOIN формируют набор строк.
  2. WHERE отбрасывает строки.
  3. GROUP BY собирает оставшиеся строки в группы.
  4. Агрегатные функции вычисляют значения для групп.
  5. HAVING отбрасывает группы.
  6. SELECT формирует столбцы результата.
  7. DISTINCT удаляет дубликаты результата.
  8. ORDER BY сортирует.
  9. 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 NULL

WHERE оставляет только строки, для которых предикат равен TRUE; FALSE и UNKNOWN отбрасываются. Поэтому NOT IN особенно опасен, если подзапрос может вернуть NULL: часто надежнее NOT EXISTS.

Карта раздела

Что помнить на собеседовании

SQL — это алгоритм? Нет. Это декларация результата; алгоритм выполнения строит оптимизатор.

Почему результат без ORDER BY не имеет гарантированного порядка? Реляционный результат — неупорядоченный набор; план и физическое размещение строк могут измениться.

Чем WHERE отличается от HAVING? WHERE фильтрует строки до группировки, HAVING — группы после нее.

Почему запрос, быстрый на тестовых данных, может замедлиться в production? Меняются объем, распределение значений, кэш, параллельная нагрузка и статистика, а вместе с ними — план.

Что важнее синтаксической «красоты» запроса? Корректная семантика, подтвержденный план и измерения на репрезентативных данных.