Как заменить множество CASE WHEN в SQL: аналоги и альтернативы
При написании сложных SQL-запросов часто возникает необходимость выбирать значения из одной таблицы или другой в зависимости от условия. Классический подход - CASE WHEN для каждого поля, что приводит к громоздкому коду. В этой статье мы разберём, как упростить такие конструкции с помощью COALESCE, UNPIVOT и других методов.
Проблема множественного CASE WHEN
Исходный запрос с перебором полей:
SELECT
CASE WHEN <условие> THEN t2.p_1 ELSE t1._p_1 END AS r1,
CASE WHEN <условие> THEN t2.p_2 ELSE t1._p_2 END AS r2,
...
CASE WHEN <условие> THEN t2.p_n ELSE t1._p_n END AS rn,
t3.*
FROM t1
LEFT JOIN t2 ON t2.t1_id = t1.id
LEFT JOIN t3 ON t3.t1_id = t1.idТакая конструкция избыточна и трудно поддерживается, особенно при большом количестве полей (n > 10).
Аналог с использованием COALESCE и UNION
В некоторых СУБД (PostgreSQL, MySQL, SQL Server) можно применить COALESCE для выбора первого ненулевого значения. Если условие влияет на то, из какой таблицы брать данные, можно объединить два запроса через UNION ALL с фильтрацией в WHERE:
SELECT t2.p_1 AS r1, t2.p_2 AS r2, ..., t3.*
FROM t1
LEFT JOIN t2 ON t2.t1_id = t1.id
LEFT JOIN t3 ON t3.t1_id = t1.id
WHERE <условие>
UNION ALL
SELECT t1._p_1 AS r1, t1._p_2 AS r2, ..., t3.*
FROM t1
LEFT JOIN t2 ON t2.t1_id = t1.id
LEFT JOIN t3 ON t3.t1_id = t1.id
WHERE NOT (<условие>)Этот приём позволяет избежать повторения CASE для каждого столбца.
Использование UNPIVOT (для SQL Server)
В SQL Server можно преобразовать строки в столбцы через UNPIVOT, а затем применить условие один раз:
SELECT
CASE WHEN <условие> THEN t2_value ELSE t1_value END AS result,
t3.*
FROM (
SELECT t1.id, t1._p_1, t1._p_2, ..., t2.p_1, t2.p_2, ..., t3.*
FROM t1
LEFT JOIN t2 ON t2.t1_id = t1.id
LEFT JOIN t3 ON t3.t1_id = t1.id
) src
UNPIVOT (
t2_value FOR col2 IN (p_1, p_2, ...)
) unpvt2
UNPIVOT (
t1_value FOR col1 IN (_p_1, _p_2, ...)
) unpvt1
WHERE unpvt2.col2 = unpvt1.col1Метод эффективен при большом числе однотипных полей.
Динамический SQL для генерации запроса
Если количество полей меняется, можно сформировать запрос программно (например, на Python, PHP или через хранимую процедуру). Пример на PostgreSQL с array_agg:
SELECT array_agg(
CASE WHEN <условие> THEN t2.p_i ELSE t1._p_i END
) AS r_array,
t3.*
FROM t1
LEFT JOIN t2 ON t2.t1_id = t1.id
LEFT JOIN t3 ON t3.t1_id = t1.id
GROUP BY t1.id, t3.*Результат возвращается в виде массива, который затем разворачивается на стороне приложения.
Сравнение подходов
- CASE WHEN - наглядно, но громоздко при n > 5.
- UNION ALL - компактно, но требует дублирования JOIN.
- UNPIVOT - хорошо для SQL Server, сложно для понимания.
- Динамический SQL - гибко, но требует дополнительного кода.
Рекомендации
Для простых случаев (до 5 полей) оставляйте CASE WHEN. Если полей много - используйте UNION ALL или динамическую генерацию. В PostgreSQL можно применить jsonb_build_object для возврата JSON.