sqlfluff vs sqlfmt: linter и formatter
В Python-мире разделение чёткое: flake8 — линтер (проверяет style), black — форматтер (безоговорочно форматирует). В JS — eslint vs prettier. В SQL-мире для dbt-проектов аналогичные роли играют sqlfluff и sqlfmt.
Эти два инструмента — не конкуренты, они решают разные задачи и часто используются вместе. В этом уроке разберём что делает каждый, когда какой нужен, и как настроить совместную работу.
SQL: как парсится SQL — основа понимания линтераПринципиальное различие: linter vs formatter
Главное:
- sqlfluff обнаруживает анти-паттерны (
SELECT *, отсутствующий alias, неконсистентные кавычки) и стилистические нарушения. Гибко настраивается. - sqlfmt просто форматирует — раскидывает SELECT по строкам, индентит, выравнивает. Нет опций про стиль (не больше десятка).
Они не дублируют друг друга. Используются вместе в типичном dbt-проекте.
sqlfluff: что он проверяет
sqlfluff содержит десятки правил, сгруппированных в категории:
| Категория | Префикс | Примеры |
|---|---|---|
| Aliasing | AL | AL01: explicit alias, AL05: tables aliased but не used |
| Ambiguous | AM | AM04: SELECT * без warning, AM06: GROUP BY by position |
| Capitalisation | CP | CP01: keywords UPPERCASE, CP02: identifiers consistent |
| Convention | CV | CV02: COALESCE over IFNULL, CV06: trailing semicolon |
| Jinja | JJ | JJ01: Jinja whitespace consistency |
| Layout | LT | LT01: trailing whitespace, LT02: indent, LT05: line too long |
| References | RF | RF01: invalid reference, RF02: unqualified references |
| Structure | ST | ST05: 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/
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 пробела. Это не настраивается.
sqlfmt — opinionated. Если команда хочет UPPERCASE keywords или leading commas, sqlfmt не подходит. Используйте sqlfluff (он гибкий) или smile and accept defaults. Холивары про стиль — пустая трата времени, проще принять чужой выбор.
Используются ли они вместе
Да, часто. Логика:
- sqlfmt делает форматирование (отступы, переносы, выравнивание).
- 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
Без 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-проекте:
- Установите оба инструмента:
pip install sqlfluff sqlfluff-templater-dbt shandy-sqlfmt[jinjafmt]
- Создайте плохо отформатированный 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'
- Запустите sqlfluff без templater:
sqlfluff lint models/staging/stg_test.sql --dialect duckdb
Увидите ошибку на {{ ref(...) }}.
- Создайте
.sqlfluff:
[sqlfluff]
dialect = duckdb
templater = dbt
-
Снова запустите. Теперь должны быть только style ошибки (без unparsable).
-
Прогоните sqlfmt:
sqlfmt models/staging/stg_test.sql
Откройте файл — отформатирован.
- Снова
sqlfluff lint— большинство layout-нарушений исчезло.
Бонус: добавьте sqlfluff-fix, прогоните, посмотрите что он автоправил, что не смог.
Ключевые выводы
- sqlfluff — linter. Десятки правил (style + анти-паттерны), гибко настраивается. Команды:
lint(проверить),fix(автоправить). - sqlfmt — formatter. Opinionated, минимум конфига. Команды:
sqlfmt(форматировать),sqlfmt --check(CI mode). - Используются вместе. sqlfmt делает форматирование, sqlfluff проверяет логические нарушения. Конфликты решаются через
exclude_rulesв sqlfluff. - dbt templater для sqlfluff — обязателен. Без него ref()/source()/var() -> unparsable. Установка:
sqlfluff-templater-dbt. - Конфиг sqlfluff —
.sqlfluffв корне репо. Минимум:dialect,templater,rules(илиexclude_rules). - Альтернатива «оба» — использовать только sqlfluff. Он покрывает и форматирование. sqlfmt — для тех, кто принимает opinionated стиль.