⚫️ Хороший 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')
Иногда надо “пробросить” значение в сессию — один раз установить, и использовать везде без
дополнительных параметров.
-- где-нибудь дальше
WHERE user_id = current_setting('app.user_id')::int
Это как глобальная переменная, но в рамках одного подключения. Удобно для BI и авторизации.
📌 Зачем всё это нужно нам?
Такие паттерны позволяют:
• не писать две версии запроса (одна — с параметрами, вторая — без)
• не держать логику “на фронте” — всё решается внутри SQL
• писать отчёты, которые не разваливаются от мелочей
• меньше нервничать: BI не передал фильтр? — всё равно работает
💥 А теперь признавайтесь:
🔥 — узнал(а) новое и уже хочу всё переписать
🤝 — давно так пишу, но приятно освежить
👨💻 — а я думал, current_setting — это про настройки Postgres 😅
Реакция — как COALESCE: даже если не пришла, всё равно приятно 😄
👉 Senior Data Analyst | #sql