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, TotalAmount

Snake_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 b

AS для колонок

-- ✅
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, amt

Boolean — префикс is_ / has_

is_active, has_subscription, is_premium

Временные колонки — суффикс _at / _date

created_at, updated_at, deleted_at
signup_date, first_purchase_date

Count / Sum — суффикс _count / _total

orders_count, session_count
total_revenue, total_amount

CTE

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. Обновляется по мере необходимости.