SQL Style Guide для аналитика
Зачем style guide
- Читабельность — код читают в 10 раз чаще, чем пишут.
- Ревью — меньше bike-shedding на обсуждении кавычек.
- Debugging — легче найти баг в чистом коде.
- Onboarding — новички быстрее вникают.
Ниже — проверенный style guide для команды аналитиков.
Общие правила
Keyword в UPPERCASE
-- ✅
SELECT user_id, COUNT(*) AS orders
FROM orders
WHERE status = 'paid'
GROUP BY user_id;
-- ❌
SELECT user_id, count(*) AS orders FROM orders WHERE status='paid' GROUP BY user_id;Имена в lowercase
-- ✅
SELECT user_id, total_amount
-- ❌
SELECT UserId, TotalAmountSnake_case, не camelCase
-- ✅
order_date, first_purchase_at, is_active
-- ❌
orderDate, firstPurchaseAt, isActiveФорматирование
Одна колонка на строку в SELECT
-- ✅
SELECT
user_id,
name,
email,
registered_at
FROM users;
-- ❌ (всё в одну строку на большом SELECT)
SELECT user_id, name, email, registered_at FROM users;Запятые в начале
Стандарт в стиле Mode / Looker:
-- ✅ (запятые перед)
SELECT
user_id
, name
, email
, COUNT(*) AS orders
FROM orders
GROUP BY
user_id
, name
, email;Или классический — запятые в конце. Выберите один и держитесь.
JOIN на новой строке
-- ✅
SELECT u.name, o.amount
FROM users u
INNER JOIN orders o
ON o.user_id = u.user_id
LEFT JOIN shipments s
ON s.order_id = o.order_id;
-- ❌
SELECT u.name, o.amount FROM users u INNER JOIN orders o ON o.user_id = u.user_id LEFT JOIN shipments s ON s.order_id = o.order_id;Отступ 4 пробела
-- ✅
SELECT
user_id,
SUM(amount) AS total
FROM orders
WHERE status = 'paid'
GROUP BY user_id;Не табы. Всегда пробелы. Один стандарт.
CTE > подзапросы
-- ✅ Читабельно
WITH recent_orders AS (
SELECT * FROM orders WHERE created_at >= '2026-01-01'
),
user_totals AS (
SELECT user_id, SUM(amount) AS total
FROM recent_orders
GROUP BY user_id
)
SELECT * FROM user_totals WHERE total > 10000;
-- ❌ Вложенные подзапросы
SELECT * FROM (
SELECT user_id, SUM(amount) AS total FROM (
SELECT * FROM orders WHERE created_at >= '2026-01-01'
) a GROUP BY user_id
) b WHERE total > 10000;Попробовать силы на подобных вопросах проще всего в тренажёре Карьерник — прямо в Telegram, без регистрации через сайт.
Алиасы
Осмысленные имена
-- ✅
FROM users u JOIN orders o
-- ❌
FROM users a JOIN orders bAS для колонок
-- ✅
SELECT SUM(amount) AS total_revenue
-- ❌
SELECT SUM(amount) total_revenueИменование
Таблицы — множественное число
-- ✅
users, orders, products, events
-- ❌
user, ORDER, product, eventСтолбцы — descriptive
-- ✅
user_id, created_at, total_amount
-- ❌
uid, dt, amtBoolean — префикс is_ / has_
is_active, has_subscription, is_premiumВременные колонки — суффикс _at / _date
created_at, updated_at, deleted_at
signup_date, first_purchase_dateCount / Sum — суффикс _count / _total
orders_count, session_count
total_revenue, total_amountCTE
Descriptive names
-- ✅
WITH active_users_last_30d AS (...)
-- ❌
WITH t1 AS (...), cte AS (...), tmp AS (...)Один CTE — одна логическая операция
-- ✅ Чёткое разделение
WITH cohort AS (
-- когорта апреля
SELECT user_id FROM users WHERE ...
),
activity AS (
-- активность когорты
SELECT ... FROM events JOIN cohort ...
),
metrics AS (
-- агрегированные метрики
SELECT ... FROM activity GROUP BY ...
)
SELECT * FROM metrics;
-- ❌ Один CTE делает 5 операций
WITH everything AS (
SELECT ... -- 100 строк мешанины
)WHERE
Условия на отдельных строках
-- ✅
WHERE
status = 'paid'
AND created_at >= '2026-01-01'
AND amount > 0
AND user_id IS NOT NULLФорматирование IN
-- ✅ (для длинных списков)
WHERE status IN (
'paid',
'pending',
'refunded'
)Чётко по датам
-- ❌ неоднозначно
WHERE created_at BETWEEN '2026-01-01' AND '2026-01-31'
-- ✅ явно половина-открытый интервал
WHERE created_at >= '2026-01-01'
AND created_at < '2026-02-01'BETWEEN с timestamps неоднозначен (включает ли конечную секунду).
Комментарии
Зачем комментарии
- Business context — почему именно такое условие.
- Workaround — причина неочевидного решения.
- TODO — что надо улучшить.
Не зачем
- Пересказ кода.
-- select user_idнадSELECT user_id— шум.
Примеры
-- В апреле 2026 был запущен новый сплитер,
-- исключаем тестовые группы до 2026-04-15
WHERE created_at < '2026-04-15' OR ab_group IS NULL
-- TODO: заменить на JSON-фильтр после миграции events
WHERE properties::text LIKE '%premium%'Пройти 30–50 задач по теме за вечер можно в Telegram-тренажёре. Это то, что отличает «знаю» от «уверенно отвечу на собесе».
Производительность
Явные типы приведения
-- ✅
WHERE created_at::DATE = '2026-04-15'::DATE
-- ❌
WHERE created_at = '2026-04-15' -- implicit CAST может сломать индексБез SELECT *
-- ✅
SELECT user_id, amount FROM orders
-- ❌ (в prod-дашбордах)
SELECT * FROM ordersИнструменты для форматирования
- SQLFluff — linter и formatter.
- dbt fmt — если используете dbt.
- Pretty SQL — online formatter.
Настройте в CI — автопроверка стиля при PR.
Чеклист ревью
Перед мёрджем SQL-кода:
- Keywords в UPPERCASE.
- 4 пробела отступ.
- Колонки на отдельных строках.
- CTE вместо вложенных подзапросов.
- Осмысленные алиасы.
- Нет SELECT *.
- Комментарии для неочевидной логики.
- Форматирование дат — явное.
- Запрос прошёл EXPLAIN.
Читайте также
FAQ
Запятые в начале или в конце?
Не важно. Важно — одинаково в команде. Выберите и придерживайтесь.
UPPERCASE для keywords обязателен?
В больших компаниях — стандарт. В маленьких — неформально. Но UPPERCASE читабельнее.
Нужны ли линтеры для SQL?
Да, особенно в команде 3+ человек. SQLFluff автоматически forcing стиль.
Кто пишет style guide?
Senior / Lead аналитик + team. Обновляется по мере необходимости.