June 24

⚫️ Хороший SQL не паникует, если параметр не пришёл

📦 Часть 1: Пишем гибкие SQL‑запросы с параметрами и fallback‑логикой

📍 Сценарий 1: фильтрация по параметру, если он передан

-- фильтрация по стране, если указана
select *
from users
where (:country is null or country = :country);

-- Поведение: -- если передать:country = 'US' → фильтруется по стране -- если:country = null → условие превращается в TRUE, фильтр игнорируется

📍 Сценарий 2: дата по умолчанию, если параметр пустой

select *
from orders
where order_date >= coalesce(:from_date, current_date - interval '30 days');

-- Поведение: -- если передан:from_date → используется он -- если нет → берём 30 дней от текущей даты

📍 Сценарий 3: fallback на подзапрос при пустом параметре

select *
from sessions
where user_id = coalesce(:user_id, (
  select user_id
  from users
  where email = current_setting('app.default_email')
  limit 1
));

— Поведение: — если есть:user_id → он приоритетен — если нет → берём дефолтного пользователя из системной переменной


Он не падает. Он спокойно говорит: "Окей, параметра нет — покажу всё."

Вместо жёстких WHERE param = ... — используем COALESCE(), IS NULL, OR param IS NULL.
Так один и тот же запрос работает и с фильтрами, и без них — без дублирования, без паники.

📚 В этом посте:
— Как писать SQL с параметрами, которые могут быть пустыми
— Что делать, если фильтр не задан
— Как подставлять дефолты, подзапросы и даже сессионные переменные

🎯 Пример:
WHERE (region = :region OR :region IS NULL)
И всё: фильтр работает, но не мешает, если его нет.

📌 Универсальный SQL — это не магия. Это просто когда он не орёт на пользователя, а работает как надо.


Представь: ты пишешь запрос для BI-дашборда или API. Всё работает на тестах, параметры
передаются. Но потом — кто-то запускает отчёт без фильтров. И твой SQL падает с ошибкой или
возвращает мусор. Почему? Потому что он не готов к реальности.

Вот где включается “устойчивая” архитектура: SQL, который работает в любых условиях.

💡 Паттерн 1: :param IS NULL OR column = :param
Это классика жанра. Работает так:
– если параметр пришёл — фильтруем по нему
– если нет — условие превращается в TRUE, и фильтрация пропускается
Пример:
WHERE :country IS NULL OR country = :country
Параметра country нет? — покажем все страны. Есть? — отфильтруем. Без IF, без двух запросов.

💡 Паттерн 2: COALESCE(:param, default)
Если параметр отсутствует — подставь дефолтное значение.
Пример:
WHERE event_time >= COALESCE(:start_date, CURRENT_DATE - INTERVAL '30 days')
Идеально для “отчёта за последние 30 дней, если не указано иное”.

Можно передавать как дефолты:
• фиксированные значения ('2024-01-01')
• функции (CURRENT_DATE)
• параметры окружения (now(), timezone)

💡 Паттерн 3: COALESCE(:param, (SELECT ...))
Здесь можно вставить динамическое значение из базы — например, текущего пользователя, компанию
или сегмент.
Пример:
WHERE user_id = COALESCE(:user_id, (SELECT id FROM users WHERE email = current_user))
— и всё работает даже без внешнего параметра. Магия? Нет — просто хороший SQL.

💡 Фишка PostgreSQL: current_setting('app.context_key')
Иногда надо “пробросить” значение в сессию — один раз установить, и использовать везде без
дополнительных параметров.

Пример:
SET app.user_id = 123;

-- где-нибудь дальше
WHERE user_id = current_setting('app.user_id')::int

Это как глобальная переменная, но в рамках одного подключения. Удобно для BI и авторизации.

📌 Зачем всё это нужно нам?
Такие паттерны позволяют:
• не писать две версии запроса (одна — с параметрами, вторая — без)
• не держать логику “на фронте” — всё решается внутри SQL
• писать отчёты, которые не разваливаются от мелочей
• меньше нервничать: BI не передал фильтр? — всё равно работает

💥 А теперь признавайтесь:
🔥 — узнал(а) новое и уже хочу всё переписать
🤝 — давно так пишу, но приятно освежить
👨‍💻 — а я думал, current_setting — это про настройки Postgres 😅

Реакция — как COALESCE: даже если не пришла, всё равно приятно 😄

👉 Senior Data Analyst | #sql