Шпаргалка SQL

Основные команды с примерами на учебном реестре; в конце — раздел для тех, кто пойдёт дальше: ключи, нормальные формы, транзакции, индексы, хранилище, озеро.

Открыть редактор запросов ↗   Задания   Таблицы и схема   Шпаргалка SQL   Ответы

Примеры — на учебном реестре обращений (таблицы cases, persons, districts, topics, districts_history). Работает в PostgreSQL, SQLite и MySQL 8, если не сказано иное. Ответы к заданиям — на отдельной странице стенда; задание 12 использует таблицу districts_history (у района R-3 две версии).

Из чего состоит запрос

СловоЧто делаетЧитаем словами
SELECTкакие столбцы показать«покажи номер и дату»
FROMиз какой таблицы«из таблицы обращений»
WHEREкакие строки оставить«только район R-2»
GROUP BYпо каким группам считать«по каждому району»
HAVINGкакие группы оставить«где обращений не меньше 15»
ORDER BYв каком порядке«сначала свежие»
LIMITсколько строк показать«первые пять»

Порядок записи всегда такой; порядок выполнения другой: FROM и JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT. Поэтому алиас из SELECT нельзя использовать в WHERE, а в ORDER BY — можно.

Выбрать и упорядочить

SELECT case_id, district_code, opened_at   -- столбцы
FROM cases                                  -- таблица
ORDER BY opened_at DESC, case_id DESC       -- сортировка: DESC по убыванию, ASC по возрастанию
LIMIT 5;                                    -- сколько строк

Отобрать строки: WHERE

SELECT case_id, opened_at
FROM cases
WHERE district_code = 'R-2'                 -- равно; текст в одинарных кавычках
  AND opened_at >= '2026-04-01'             -- «и»
  AND opened_at <  '2026-07-01'             -- правая граница исключена
  AND channel IN ('портал', 'телефон')      -- список
  AND topic_code LIKE 'T-%'                 -- шаблон: % любое число символов, _ один символ
  AND closed_at IS NULL;                    -- пусто; «= NULL» не работает никогда

Операторы: =, <> (не равно), <, >, <=, >=, BETWEEN a AND b (включительно), IN (...), LIKE, IS NULL, IS NOT NULL, AND, OR, NOT. Даты в учебной базе — текст ГГГГ-ММ-ДД, сравнение работает как с датами.

Посчитать: агрегаты

ФункцияЧто считаетПустые значения
COUNT(*)число строксчитает все
COUNT(столбец)число заполненныхне считает
SUM, AVGсумма, среднеепропускает; среднее по заполненным
MIN, MAXминимум, максимумработают и с датами, и с текстом
SELECT COUNT(*) AS cases_total,
       COUNT(closed_at) AS cases_closed,
       ROUND(AVG(days_to_close), 1) AS avg_days,
       MAX(days_to_close) AS max_days
FROM cases;

Сгруппировать: GROUP BY и HAVING

SELECT district_code,
       COUNT(*) AS cases_total,
       ROUND(AVG(days_to_close), 1) AS avg_days
FROM cases
WHERE opened_at >= '2026-01-01'   -- отбор строк до группировки
GROUP BY district_code             -- одна строка результата на район
HAVING COUNT(*) >= 15              -- отбор групп по итогам
ORDER BY avg_days DESC;

В SELECT — только столбцы группировки и агрегаты. Месяц из даты: SUBSTR(opened_at, 1, 7) (в PostgreSQL с типом date — date_trunc('month', opened_at)).

Условие внутри расчёта: CASE

SELECT ROUND(100.0 * SUM(CASE WHEN days_to_close <= 15 THEN 1 ELSE 0 END)
             / COUNT(closed_at), 1) AS on_time_pct   -- доля закрытых в срок
FROM cases;

100.0 делает деление дробным; COALESCE(x, 0) заменяет пустое нулём, если пусто действительно значит ноль.

Связать таблицы: JOIN

SELECT d.title AS district, COUNT(c.case_id) AS cases_total
FROM cases AS c
LEFT JOIN districts AS d ON c.district_code = d.district_code
GROUP BY d.title
ORDER BY cases_total DESC, district;
ВидЧто остаётся
JOIN (INNER)только строки с парой в обеих таблицах
LEFT JOINвсе строки левой таблицы; без пары — пусто справа
FULL JOINвсе строки обеих таблиц
RIGHT JOINзеркало LEFT; почти не пишут

Правила: главная таблица слева, справочник справа, LEFT — чтобы не терять строки; ключ справа должен быть уникальным, иначе строки удваиваются. Проверка до объединения: SELECT key, COUNT(*) FROM t GROUP BY key HAVING COUNT(*) > 1; после — число строк не изменилось. При LEFT JOIN дополнительное условие по правой таблице пишут в ON, а не в WHERE.

UNION складывает строки двух запросов одной формы и убирает полные дубли; UNION ALL сохраняет все.

Запрос внутри запроса: CTE и подзапрос

WITH by_district AS (                       -- CTE: именованный подзапрос
  SELECT district_code, AVG(days_to_close) AS avg_days
  FROM cases GROUP BY district_code
)
SELECT district_code, ROUND(avg_days, 1)
FROM by_district
WHERE avg_days > (SELECT AVG(days_to_close) FROM cases);   -- подзапрос: одно число

Три проверки качества одним запросом

-- повтор ключа
SELECT iin, COUNT(*) FROM persons GROUP BY iin HAVING COUNT(*) > 1;
-- код вне справочника
SELECT c.case_id, c.district_code FROM cases c
LEFT JOIN districts d ON c.district_code = d.district_code WHERE d.district_code IS NULL;
-- двойная регистрация
SELECT person_id, topic_code, opened_at, COUNT(*) FROM cases
GROUP BY person_id, topic_code, opened_at HAVING COUNT(*) > 1;

Чек-лист перед показом результата

1. Число строк сходится: сумма по группам равна общему числу.

2. Пустые учтены: COUNT(*) против COUNT(столбец); IS NULL вместо «= NULL».

3. Объединение не потеряло и не задвоило строки: LEFT там, где нельзя терять; ключ справа уникален.

4. Условия соответствуют паспорту показателя, а не «так проще».

5. Границы дат: «не раньше начала и раньше следующего дня после конца».

6. Расхождение с чужим отчётом — искать причину, а не выбирать удобную цифру.

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

ОшибкаЧто происходитКак правильно
closed_at = NULL0 строк без сообщения об ошибкеclosed_at IS NULL
Агрегат в WHEREотказ базыагрегаты фильтруются в HAVING
LIMIT без ORDER BY«первые» строки случайнысначала ORDER BY
Деление целых0 вместо долиумножить на 100.0
JOIN вместо LEFT JOINстроки без пары исчезают молчаLEFT JOIN и проверка числа строк
Повтор ключа справастроки удваиваютсяпроверить справочник до объединения
DESC только у последнего столбцаостальные сортируются по возрастаниюнаправление у каждого столбца
UNION вместо UNION ALLдубли строк исчезают молчапо умолчанию UNION ALL

Для тех, кто пойдёт дальше: как устроены базы

Ключи и ограничения. PK (primary key) — первичный ключ, уникален и обязателен, один на таблицу. FK (foreign key) — внешний ключ, ссылка на строку другой таблицы; база не даст записать код района, которого нет в справочнике. UNIQUE — без повторов; NOT NULL — обязательно; CHECK — условие на строку.

Нормальные формы — правила, чтобы каждое значение хранилось один раз. 1NF: в ячейке одно значение, не список. 2NF: факт зависит от всего ключа, а не от его части. 3NF: факт хранится у своего объекта (название района — в справочнике, а не в каждом обращении). Нормализация защищает рабочую базу от расхождений при правках; в витринах хранилища повторы допускают намеренно ради простоты чтения (денормализация, «звезда»).

Транзакция — группа изменений, которая применяется целиком или отменяется целиком. ACID: атомарность, согласованность, изоляция, долговечность. Для аналитика важна изоляция: отчёт, запущенный во время ночной загрузки, может увидеть половину данных; узнайте время готовности данных.

Индекс — вспомогательная структура для быстрого поиска строк по значению столбца, как алфавитный указатель. Ускоряет WHERE, JOIN, ORDER BY; замедляет запись. Функция над столбцом в условии не даёт использовать обычный индекс (нужен индекс по выражению): вместо «год от даты = 2026» пишите диапазон дат. EXPLAIN показывает план: Seq Scan — полный перебор, Index Scan — поиск по индексу.

OLTP и OLAP. Рабочая база (OLTP) — много мелких записей, текущее состояние, строковое хранение. Аналитическая (OLAP) — редкие тяжёлые чтения по истории, колоночное хранение (ClickHouse, Greenplum, Vertica): столбцы лежат отдельно и сжимаются, агрегат по одному столбцу не читает остальные. Тяжёлый отчёт в рабочую базу — главный анти-паттерн.

Хранилище (DWH). Слои: сырой (как пришло) → ядро (согласовано, точка истины) → витрины (под задачу). Три школы ядра: Инмон (нормализованные таблицы, сначала единое хранилище), Кимбалл (факты и измерения, «звезда», сначала витрины), Data Vault (хабы, связи, сателлиты; префиксы h_, l_, s_). SCD (медленно меняющееся измерение): тип 1 — перезапись, тип 2 — новая строка с датами действия; объединение по коду без фильтра версии удваивает строки.

Озеро данных и lakehouse. Озеро — файлы любого вида как есть, схема при чтении, дёшево; без каталога и владельцев — «болото». Lakehouse — файлы Parquet плюс табличный формат (Iceberg, Delta Lake) с транзакциями и версиями. Форматы: CSV (текст, без типов), JSON и XML (обмен между системами), Parquet (колоночный, сжатый, с типами).

Где тренироваться

sql-academy.org, sql-ex.ru, w3schools.com/sql, stratascratch.com; стиль записи — sqlstyle.guide/ru. Учебная база практикума остаётся у вас: setup.sql создаёт таблицы в SQLite (sqliteonline.com, DBeaver), PostgreSQL и MySQL.