Шпаргалка 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; -- сколько строк
SELECT *— все столбцы; удобно посмотреть, неудобно отдавать в отчёт.AS— алиас:COUNT(*) AS cases_total,FROM cases AS c.DISTINCT— уникальные значения:SELECT DISTINCT district_code FROM cases.ROUND(x, 1)— округление до одного знака.
Отобрать строки: 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 = NULL | 0 строк без сообщения об ошибке | 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.