在PostgreSQL中使用CASE一次影响多个列

时间:2012-12-04 22:44:21

标签: sql postgresql case

我有一个带有这些表达式的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是否为真。我一遍又一遍地重复具有相同条件的CASE语句。

有没有什么方法可以合并这些CASE表达式来一次切换多列的输出?

2 个答案:

答案 0 :(得分:28)

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特定的,对标准SQL使用cast(value AS type)

如果条件不是TRUE,则x中的所有值都为NULL,并且COALESCE会启动。

,因为在您的特定情况下,所有候选值都来自表rtd2LEFT JOINrtd2使用原始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中*展开它。假设我们有三列:textintegerdate。对于重复使用,请创建自定义复合类型:

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表达式一次。如果评估不是微不足道的话,那么带有子查询的另一个变体会更快。

答案 1 :(得分:1)

不确定这会有什么改进,但你可以通过另一种方式将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