web-dev-qa-db-fra.com

Utilisation de CASE dans PostgreSQL pour affecter plusieurs colonnes à la fois

J'ai une instruction Postgres SELECT avec ces expressions:

,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

Dans ma requête particulière, il y a 5 champs dont la sortie dépend si rtp.team_id = rtp.sub_team_id évalue vrai. Je répète encore et encore les instructions CASE avec la même condition.

Existe-t-il un moyen de combiner ces expressions CASE pour basculer la sortie de plusieurs colonnes en une seule fois?

26
Elliot B.

1. Standard-SQL: LEFT JOIN Une seule ligne de valeurs

Vous pouvez LEFT JOIN une ligne de valeurs en utilisant la condition (l'évaluant ainsi une fois). Ensuite, vous pouvez ajouter des valeurs de secours par colonne avec COALESCE() .

Cette variante de syntaxe est plus courte et légèrement plus rapide avec plusieurs valeurs - particulièrement intéressante pour une condition coûteuse/longue:

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;

Étant donné que la table dérivée x se compose d'une ligne single, la jonction sans autres conditions est correcte.

Les transtypages de types explicites sont nécessaires dans la sous-requête. J'utilise text dans l'exemple (qui est de toute façon la valeur par défaut pour les littéraux de chaîne). Utilisez vos types de données réels. Le raccourci de syntaxe value::type Est spécifique à Postgres, utilisez cast(value AS type) pour SQL standard.

Si la condition n'est pas TRUE, toutes les valeurs de x sont NULL et COALESCE entre en jeu.

Ou , puisque toutes les valeurs candidates proviennent de la table rtd2 Dans votre cas particulier, LEFT JOIN À rtd2 en utilisant la condition CASE d'origine et CROSS JOIN sur une ligne avec des valeurs par défaut:

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;

Cela dépend des conditions de jointure et du reste de la requête.

2. Spécifique à PostgreSQL

2a. Développer un tableau

Si vos différentes colonnes partagent le même type de données, vous pouvez utiliser un tableau dans une sous-requête et le développer dans le externe 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;

Cela devient plus compliqué si les colonnes ne partagent pas le même type de données. Vous pouvez soit les convertir tous en text (et éventuellement les reconvertir dans le SELECT externe), soit vous pouvez ...

2b. Décomposer un type de ligne

Vous pouvez utiliser un type composite personnalisé (type de ligne) pour contenir les valeurs de différents types et simplement * -expand dans le SELECT externe. Supposons que nous ayons trois colonnes: text, integer et date. Pour répété utilisation, créez un type composite personnalisé:

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

Ou si le type d'une table existante correspond, vous pouvez simplement utiliser le nom de la table comme type composite.

Ou si vous n'avez besoin que du type temporairement, vous pouvez créer un TEMPORARY TABLE, Qui enregistre un type temporaire pour la durée de votre session:

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

Vous pouvez même le faire pour une transaction unique:

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

Ensuite, vous pouvez utiliser cette requête:

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;

Ou même juste (comme ci-dessus, plus simple, plus court, peut-être moins facile à comprendre):

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>;

L'expression CASE est évaluée une fois pour chaque colonne de cette façon. Si l'évaluation n'est pas triviale, l'autre variante avec une sous-requête sera plus rapide.

30
Erwin Brandstetter

Vous n'êtes pas sûr que ce serait une amélioration, mais vous pouvez unir le SELECT dans un sens avec lui-même dans l'autre sens:

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 ...;

Les noms de colonne peuvent être omis en toute sécurité de la deuxième requête, en supposant que vous les sortiez dans le même ordre que dans la première.

Vous souhaiterez peut-être faire de chacun d'eux une requête distincte en utilisant des expressions de table communes (CTE). Si vous craignez que cela modifie l'ordre, vous pouvez en faire une sous-requête et appliquer un ORDER BY autour de.

1
Daniel Lyons