Как заменить множество 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.

    Часто задаваемые вопросы