Подтвердить что ты не робот

Использование CASE в PostgreSQL для одновременного воздействия на несколько столбцов

У меня есть оператор Postgres SELECT с этими выражениями:

,CASE WHEN (rtp.team_id = rtp.sub_team_id)
 THEN 'testing'
 ELSE TRIM(rtd2.team_name)
 END AS testing_testing
,CASE WHEN (rtp.team_id = rtp.sub_team_id)
 THEN 'test example'
 ELSE TRIM(rtd2.normal_data)
 END AS test_response
,CASE WHEN (rtp.team_id = rtp.sub_team_id)
 THEN 'test example #2'
 ELSE TRIM(rtd2.normal_data_2)
 END AS another_example

В моем конкретном запросе есть 5 полей, выход которых зависит от того, оценивает ли значение rtp.team_id = rtp.sub_team_id значение true. Я повторяю утверждения CASE с тем же условием снова и снова.

Есть ли способ объединить эти выражения CASE для переключения вывода нескольких столбцов за один снимок?

4b9b3361

Ответ 1

1. Standard-SQL: LEFT JOIN одна строка значений

Вы можете LEFT JOIN строку значений с использованием условия (тем самым оценив его один раз). Затем вы можете добавить резервные значения для столбца с COALESCE().

Этот вариант синтаксиса короче и немного быстрее с несколькими значениями, особенно интересными для дорогостоящего/длительного состояния:

SELECT COALESCE(x.txt1, trim(r2.team_name))     AS testing_testing
     , COALESCE(x.txt2, trim(r2.normal_data))   AS test_response
     , COALESCE(x.txt3, trim(r2.normal_data_2)) AS another_example
FROM   rtp
JOIN   rtd2 r2 ON <unknown condition> -- missing context in question
LEFT   JOIN (
   SELECT 'testing'::text         AS txt1
        , 'test example'::text    AS txt2
        , 'test example #2'::text AS txt3
   ) x ON rtp.team_id = rtp.sub_team_id;

Так как производная таблица x состоит из одной строки, объединение без дополнительных условий прекрасное.

В подзапросе необходимы явные приведения типов. Я использую text в примере (который по умолчанию используется для строковых литералов). Используйте свои фактические типы данных. Ярлык синтаксиса value::type зависит от Postgres, используйте cast(value AS type) для стандартного SQL.

Если условие не TRUE, все значения в x равны NULL, а COALESCE срабатывает.

Или, так как все значения кандидата берутся из таблицы rtd2 в вашем конкретном случае, LEFT JOIN - rtd2, используя исходное условие CASE и CROSS JOIN в строку со значением по умолчанию значения:

SELECT COALESCE(trim(r2.team_name),     x.txt1) AS testing_testing
     , COALESCE(trim(r2.normal_data),   x.txt2) AS test_response
     , COALESCE(trim(r2.normal_data_2), x.txt3) AS another_example
FROM   rtp
LEFT   JOIN rtd2 r2 ON <unknown condition>  -- missing context in question
                   AND rtp.team_id = rtp.sub_team_id
CROSS  JOIN (
   SELECT 'testing'::text         AS txt1
        , 'test example'::text    AS txt2
        , 'test example #2'::text AS txt3
   ) x;

Это зависит от условий соединения и остальной части запроса.

2. PostgreSQL конкретного

2a. Разверните массив

Если ваши разные столбцы используют тот же тип данных, вы можете использовать массив в подзапросе и развернуть его во внешнем SELECT:

SELECT x.combo[1], x.combo[2], x.combo[3]
FROM  (
   SELECT CASE WHEN rtp.team_id = rtp.sub_team_id
            THEN '{test1,test2,test3}'::text[]
            ELSE ARRAY[trim(r2.team_name)
                     , trim(r2.normal_data)
                     , trim(r2.normal_data_2)]
          END AS combo
   FROM   rtp
   JOIN   rtd2 r2 ON <unknown condition>
   ) x;

Это становится более сложным, если столбцы не используют один и тот же тип данных. Вы можете либо перевести их все на text (и, возможно, преобразовать обратно во внешний SELECT), либо вы можете...

2b. Разделить тип строки

Вы можете использовать собственный составной тип (тип строки) для хранения значений различных типов и просто * -расширить его во внешнем SELECT. Скажем, у нас есть три столбца: text, integer и date. Для многократного использования создайте настраиваемый составной тип:

CREATE TYPE my_type (t1 text, t2 int, t3 date);

Или, если тип существующей таблицы соответствует, вы можете просто использовать имя таблицы как составной тип.

Или, если вам нужен только тип временно, вы можете создать TEMPORARY TABLE, который регистрирует временный тип в течение всего сеанса:

CREATE TEMP TABLE my_type (t1 text, t2 int, t3 date);

Вы можете сделать это только для одной транзакции:

CREATE TEMP TABLE my_type (t1 text, t2 int, t3 date) ON COMMIT DROP;

Затем вы можете использовать этот запрос:

SELECT (x.combo).*  -- parenthesis required
FROM  (
   SELECT CASE WHEN rtp.team_id = rtp.sub_team_id
             THEN ('test', 3, now()::date)::my_type  -- example values
             ELSE (r2.team_name
                 , r2.int_col
                 , r2.date_col)::my_type
          END AS combo
   FROM   rtp
   JOIN   rtd2 r2 ON <unknown condition>
   ) x;

Или даже просто (так же, как выше, проще, короче, может быть, менее легко понять):

SELECT (CASE WHEN rtp.team_id = rtp.sub_team_id
           THEN ('test', 3, now()::date)::my_type
           ELSE (r2.team_name, r2.int_col, r2.date_col)::my_type
        END).*
FROM   rtp
JOIN   rtd2 r2 ON <unknown condition>;

Выражение CASE оценивается один раз для каждого столбца таким образом. Если оценка не является тривиальной, другой вариант с подзапросом будет быстрее.

Ответ 2

Не уверен, что это будет улучшением, но вы можете объединить SELECT одним способом с самим собой:

SELECT 
  ...,
  'testing' AS testing_testing,
  'test example' AS test_response,
  'test example #2' AS another_example, ...
FROM ...
WHERE rtp.team_id = rtp.sub_team_id AND ...
UNION 
SELECT
  ...,
  TRIM(rtd2.team_name) AS testing_testing,
  TRIM(rtd2.normal_data) AS test_response,
  TRIM(rtd2.normal_data_2) AS another_example, ...
WHERE rtp.team_id <> rtp.sub_team_id AND ...;

Имена столбцов можно безопасно исключить из второго запроса, предполагая, что вы выведете их в том же порядке, что и в первом.

Вы можете сделать каждый из них отдельным запросом, используя общие табличные выражения (CTE). Если вы беспокоитесь об этом изменении порядка, вы можете сделать его подзапросом и применить к нему ORDER BY.