Learning Platform
Глоссарий Troubleshooting
Урок 13.02 · 25 мин
Средний
sqlfluffsqlfmtLintingFormattingdbt templater

sqlfluff vs sqlfmt: linter и formatter

В Python-мире разделение чёткое: flake8 — линтер (проверяет style), black — форматтер (безоговорочно форматирует). В JS — eslint vs prettier. В SQL-мире для dbt-проектов аналогичные роли играют sqlfluff и sqlfmt.

Эти два инструмента — не конкуренты, они решают разные задачи и часто используются вместе. В этом уроке разберём что делает каждый, когда какой нужен, и как настроить совместную работу.

SQL: как парсится SQL — основа понимания линтера

Принципиальное различие: linter vs formatter

Linter vs Formatter — разница в философии
sqlfluff (linter)Linter — проверяющий. Знает много правил (десятки или сотни), большинство опциональны. Конфигурируется детально: какие правила включить, какие игнорировать. Сообщает о нарушениях. Часть умеет автоправить, но не вся стилистика.
sqlfmt (formatter)Formatter — безапелляционно форматирует. Минимум конфига (нет десятков опций как у linter). Принцип: 'у каждой команды одинаковый формат, дебаты прекращены'. Аналог prettier для JS, black для Python.

Главное:

  • sqlfluff обнаруживает анти-паттерны (SELECT *, отсутствующий alias, неконсистентные кавычки) и стилистические нарушения. Гибко настраивается.
  • sqlfmt просто форматирует — раскидывает SELECT по строкам, индентит, выравнивает. Нет опций про стиль (не больше десятка).

Они не дублируют друг друга. Используются вместе в типичном dbt-проекте.


sqlfluff: что он проверяет

sqlfluff содержит десятки правил, сгруппированных в категории:

КатегорияПрефиксПримеры
AliasingALAL01: explicit alias, AL05: tables aliased but не used
AmbiguousAMAM04: SELECT * без warning, AM06: GROUP BY by position
CapitalisationCPCP01: keywords UPPERCASE, CP02: identifiers consistent
ConventionCVCV02: COALESCE over IFNULL, CV06: trailing semicolon
JinjaJJJJ01: Jinja whitespace consistency
LayoutLTLT01: trailing whitespace, LT02: indent, LT05: line too long
ReferencesRFRF01: invalid reference, RF02: unqualified references
StructureSTST05: subquery, ST06: column order

Полный список — sqlfluff rules. На старте лучше включить базовый набор и постепенно расширять.

Минимальный .sqlfluff (в корне репо):

[sqlfluff]
dialect = duckdb
templater = dbt
rules = LT01,LT02,LT05,CP01,CP02,CV02,AM04

[sqlfluff:rules:capitalisation.keywords]
capitalisation_policy = upper

[sqlfluff:rules:capitalisation.identifiers]
capitalisation_policy = lower

[sqlfluff:rules:layout.long_lines]
max_line_length = 120

Команда:

# Только проверить
sqlfluff lint models/

# Автоправка (только тех нарушений, которые умеет править)
sqlfluff fix models/
NOTE

sqlfluff не умеет всё автоправить. CV02 (replace IFNULL -> COALESCE) — да, умеет. AL01 (отсутствующий alias) — не умеет, alias надо придумать. Запускайте sqlfluff fix, потом sqlfluff lint снова, чтобы увидеть что осталось.


sqlfmt: безапелляционное форматирование

sqlfmt — это пакет от tconbeer, ориентированный на dbt. Философия:

  • Один правильный формат. Все запятые ведущие (, column), все SELECT с одной колонкой на строку, индент 4 пробела.
  • Без конфигурации стиля. Только --line-length (по умолчанию 88).
  • Идемпотентность. Прогнал второй раз — изменений нет.

Установка:

pip install shandy-sqlfmt[jinjafmt]

Использование:

# Отформатировать (изменить файлы in-place)
sqlfmt models/

# Проверить (CI режим: exit 1 если есть изменения, файлы не трогает)
sqlfmt --check models/

# Diff: показать что бы изменилось
sqlfmt --diff models/

Пример. Вход:

select id,name,case when status='active' then 1 else 0 end as is_active from {{ ref('stg_customers') }}where created_at>'2026-01-01'

После sqlfmt:

select
    id,
    name,
    case
        when status = 'active' then 1
        else 0
    end as is_active
from {{ ref('stg_customers') }}
where created_at > '2026-01-01'

Заметьте: всё нижний регистр (sqlfmt opinionated на этом), запятые в конце (trailing), индент 4 пробела. Это не настраивается.

WARNING

sqlfmt — opinionated. Если команда хочет UPPERCASE keywords или leading commas, sqlfmt не подходит. Используйте sqlfluff (он гибкий) или smile and accept defaults. Холивары про стиль — пустая трата времени, проще принять чужой выбор.


Используются ли они вместе

Да, часто. Логика:

  1. sqlfmt делает форматирование (отступы, переносы, выравнивание).
  2. sqlfluff проверяет всё остальное: анти-паттерны, capitalization (если sqlfmt не покрывает), references, конвенции.

Проблема: они могут конфликтовать на стилистике. Например, sqlfmt делает lowercase, sqlfluff с capitalisation_policy = upper будет ругаться.

Решение: настраивать sqlfluff так, чтобы он не конфликтовал с sqlfmt. У sqlfluff даже есть преднастроенный конфиг для этого:

[sqlfluff]
dialect = duckdb
templater = dbt
exclude_rules = LT01, LT02, LT05, LT07, LT09, LT10, LT12, LT13, CP01

Эти правила (layout + capitalization) отключены — sqlfmt их делает. sqlfluff остаётся для логики (CV02, AM04, RF02 и т.д.).

Альтернативный путь: выбрать только один инструмент. Многие команды используют только sqlfluff (потому что он покрывает и форматирование и логику), без sqlfmt. Это валидный путь, особенно если хотите кастомный стиль.


dbt templater для sqlfluff

Стандартная проблема. У вас в SQL:

SELECT * FROM {{ ref('stg_customers') }}
WHERE created_at > '{{ var("start_date") }}'

Без templater sqlfluff видит {{ ref('stg_customers') }} как буквальный текст, не SQL -> парсер падает с Unparsable section.

dbt templater решает: sqlfluff запускает dbt compile, получает скомпилированный SQL, парсит и линтит скомпилированный код:

SELECT * FROM dev_jaffle_shop.stg_customers
WHERE created_at > '2026-01-01'

Это рабочий SQL, который sqlfluff может проверить.

Настройка:

[sqlfluff]
dialect = duckdb
templater = dbt

[sqlfluff:templater:dbt]
profiles_dir = ~/.dbt
profile = jaffle_shop
target = dev

И установка адаптера:

pip install sqlfluff-templater-dbt
DANGER

Без templater = dbt sqlfluff будет давать false positives на каждый ref()/source()/var(). Многие джуны видят Unparsable section и думают что sqlfluff просто не работает с dbt. Решение — установить templater. Это не опция, это must-have для dbt проектов.

Производительность templater=dbt

Минус: каждый запуск sqlfluff запускает dbt compile (или его эквивалент через manifest.json). Это медленно — 10-30 секунд для среднего проекта.

Ускорения:

  • Кеширование manifest.json. sqlfluff умеет использовать готовый target/manifest.json от предыдущего dbt compile. Прогон линта без compile = секунды.
  • Параллелизация. sqlfluff lint --processes 4 использует несколько ядер.
  • fast-jinja templater (если не нужны refs). Альтернатива templater = jinja — парсит Jinja без dbt context. Быстрее, но не понимает ref()/source(). Подходит если у вас простой SQL без refs (редко в dbt проекте).

Типичный workflow в команде

1. Разработчик пишет SQL -> сохраняет файл
2. (опционально) IDE автоформат через sqlfmt при save
3. git add
4. git commit
   -> pre-commit запускает sqlfmt --check (если ничего не меняется — Passed)
   -> pre-commit запускает sqlfluff-lint
   -> если есть нарушения, которые автофиксятся -> sqlfluff-fix их правит -> файл изменился -> надо повторно git add
5. git push
6. CI запускает sqlfmt --check + sqlfluff lint (safety net на случай --no-verify)
7. PR review (стилистика уже унифицирована, обсуждаем только логику)

sqlfluff конфиг — общие настройки

В типичном production проекте:

# .sqlfluff

[sqlfluff]
dialect = duckdb
templater = dbt
# Если используете sqlfmt — exclude его правила
exclude_rules = LT01, LT02, LT05, LT07, LT09, LT10, LT12, LT13, CP01

# Производительность
processes = 4
max_line_length = 120

# Какие файлы проверять
include = models/, snapshots/, analyses/

[sqlfluff:indentation]
indent_unit = space
tab_space_size = 4

[sqlfluff:templater:dbt]
profiles_dir = ~/.dbt
profile = jaffle_shop
target = dev

[sqlfluff:rules:capitalisation.keywords]
capitalisation_policy = lower    # если используете sqlfmt

[sqlfluff:rules:references.consistent]
force_enable = true

[sqlfluff:rules:references.special_chars]
allow_space_in_identifier = false

Попробуй сам

В вашем dbt-проекте:

  1. Установите оба инструмента:
pip install sqlfluff sqlfluff-templater-dbt shandy-sqlfmt[jinjafmt]
  1. Создайте плохо отформатированный SQL в models/staging/stg_test.sql:
select id,name,case when status='active' then 1 else 0 end as flag from {{ ref('stg_customers') }} where created_at>'2026-01-01'
  1. Запустите sqlfluff без templater:
sqlfluff lint models/staging/stg_test.sql --dialect duckdb

Увидите ошибку на {{ ref(...) }}.

  1. Создайте .sqlfluff:
[sqlfluff]
dialect = duckdb
templater = dbt
  1. Снова запустите. Теперь должны быть только style ошибки (без unparsable).

  2. Прогоните sqlfmt:

sqlfmt models/staging/stg_test.sql

Откройте файл — отформатирован.

  1. Снова sqlfluff lint — большинство layout-нарушений исчезло.

Бонус: добавьте sqlfluff-fix, прогоните, посмотрите что он автоправил, что не смог.


Ключевые выводы

  1. sqlfluff — linter. Десятки правил (style + анти-паттерны), гибко настраивается. Команды: lint (проверить), fix (автоправить).
  2. sqlfmt — formatter. Opinionated, минимум конфига. Команды: sqlfmt (форматировать), sqlfmt --check (CI mode).
  3. Используются вместе. sqlfmt делает форматирование, sqlfluff проверяет логические нарушения. Конфликты решаются через exclude_rules в sqlfluff.
  4. dbt templater для sqlfluff — обязателен. Без него ref()/source()/var() -> unparsable. Установка: sqlfluff-templater-dbt.
  5. Конфиг sqlfluff.sqlfluff в корне репо. Минимум: dialect, templater, rules (или exclude_rules).
  6. Альтернатива «оба» — использовать только sqlfluff. Он покрывает и форматирование. sqlfmt — для тех, кто принимает opinionated стиль.
Проверка знанийKnowledge check
В CI запускаете \`sqlfluff lint\` без templater. На моделях с ref()/source() получаете 'Unparsable section'. Что произошло и как починить?
ОтветAnswer
Проблема: sqlfluff видит сырой SQL с `{{ ref('...') }}` — для него это не SQL, парсер падает. По умолчанию templater = jinja (минимальный, не знает ref/source).\n\n**Починка:**\n\n1. **Установить dbt templater:**\n\n```bash\npip install sqlfluff-templater-dbt\n```\n\n2. **Включить в `.sqlfluff`:**\n\n```ini\n[sqlfluff]\ntemplater = dbt\n\n[sqlfluff:templater:dbt]\nprofiles_dir = ~/.dbt\nprofile = jaffle_shop\ntarget = dev\n```\n\nТеперь sqlfluff запускает dbt compile, получает скомпилированный SQL (`SELECT * FROM dev_jaffle_shop.stg_customers`) и линтит его.\n\n3. **CI gotcha**: в CI нужен валидный `profiles.yml` (с warehouse credentials или DuckDB локально). Иначе dbt compile упадёт. Часто в CI используется DuckDB target специально для линтинга — он не требует креденшелов, просто читает manifest.\n\n4. **Performance gotcha**: каждый запуск sqlfluff делает dbt compile (10-30 сек). Кэширование через готовый `target/manifest.json` ускоряет в разы. В CI порядок: `dbt deps && dbt parse` (создаёт manifest) -> `sqlfluff lint` (читает manifest).\n\n**Анти-паттерн** — пытаться обойти проблему через regex замену `ref()` на хардкод имён таблиц. Нарушает dbt принципы, ломается на каждом коммите.
Проверка знанийKnowledge check
Команда настроила sqlfmt + sqlfluff в pre-commit. После запуска \`pre-commit run --all-files\` файл сначала исправляется sqlfmt (нижний регистр keywords), потом sqlfluff с правилом CP01 (capitalisation_policy=upper) ругается. Цикл. Как решить?
ОтветAnswer
Это **конфликт правил между инструментами**. sqlfmt принципиально opinionated на lowercase keywords, sqlfluff CP01 принципиально настраиваем — но команда поставила его в `upper`. Они борются за один файл.\n\n**Варианты решения:**\n\n1. **Выбрать стиль sqlfmt (lowercase).** В `.sqlfluff` либо отключить CP01:\n\n```ini\n[sqlfluff]\nexclude_rules = CP01\n```\n\nЛибо поставить policy = lower:\n\n```ini\n[sqlfluff:rules:capitalisation.keywords]\ncapitalisation_policy = lower\n```\n\nТеперь sqlfmt форматирует, sqlfluff не ругается. **Рекомендуемый путь** — sqlfmt всё равно нельзя переубедить.\n\n2. **Убрать sqlfmt.** Использовать только sqlfluff с настраиваемым стилем. У sqlfluff есть rule LT01-LT13 для layout. `sqlfluff fix` отформатирует с вашим стилем. Минус — sqlfluff менее «умный» в форматировании, чем sqlfmt.\n\n3. **Использовать sqlfluff preset для sqlfmt совместимости.** В sqlfluff docs есть список rules, которые конфликтуют с sqlfmt — рекомендуется их exclude. Это: LT01, LT02, LT05, LT07, LT09, LT10, LT12, LT13, CP01.\n\n```ini\n[sqlfluff]\nexclude_rules = LT01, LT02, LT05, LT07, LT09, LT10, LT12, LT13, CP01\n```\n\nЭто **правильная настройка** для тандема sqlfmt + sqlfluff.\n\n**Не делать**: использовать оба без exclude. Цикл вечный, pre-commit зависает.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 6. Какое утверждение наиболее точно описывает разницу между sqlfluff и sqlfmt?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 4