【问题标题】:Combine CASE and BETWEEN in WHERE clause在 WHERE 子句中结合 CASE 和 BETWEEN
【发布时间】:2023-03-04 11:10:01
【问题描述】:

我正在尝试执行我认为是一个足够简单的查询,但是我遇到了语法错误并且可以使用一些指导。本质上,我有一个带有文本输入参数$1 的函数。此参数可以采用由应用程序前端控制的 7 个不同值。

根据这个参数的值,我需要在返回的整数列的某个范围之间返回一个较大表的子集。为了稍微清楚一点,函数中的返回查询看起来像:

SELECT val_1, val_2, val_3
  FROM schema_name.table_name
 WHERE val_3 BETWEEN
  CASE WHEN $1 = 'a' THEN 0 AND 1
       WHEN $1 = 'b' THEN 2 AND 7
         .
         . 
         .
       WHEN $1 = 'g' THEN 50 AND 100
   END;

但是,我遇到了一个通常含糊不清的错误:

错误:输入末尾的语法错误 第 42 行:结束; ^ ********** 错误 ********** 错误:输入末尾的语法错误 SQL 状态:42601 人物:1133

案例陈述中列出的范围都不同,它们之间没有明显的模式。我当然可以编写一个 IF 块,其中包含 7 个 ELSE 语句,基本上编写相同的选择语句 7 次,但我猜上面的内容不会太远。我以前从未需要以这种方式在WHERE 子句中使用CASE 语句。任何正确方向的提示将不胜感激。

【问题讨论】:

    标签: sql postgresql case


    【解决方案1】:

    CASE 表达式只能返回一个单个值(在我的固定版本中为boolean),而不是您尝试过的条件代码:

    SELECT val_1, val_2, val_3
    FROM   schema_name.table_name
    WHERE  CASE $1
             WHEN 'a' THEN val_3 BETWEEN 0 AND 1
             WHEN 'b' THEN val_3 BETWEEN 2 AND 7
              . 
              .
              .
             WHEN 'g' THEN val_3 BETWEEN 50 AND 100
           END;
    

    使用CASE 的“简单”形式使其更短更快。

    或者,只使用布尔逻辑:

    ...
    WHERE (
           $1 = 'a' AND val_3 BETWEEN 0 AND 1
       OR  $1 = 'b' AND val_3 BETWEEN 2 AND 7
             . 
             .
             .
       OR  $1 = 'g' AND val_3 BETWEEN 50 AND 100
          )
    

    括号不是严格需要的,因为operator precedence 无论如何都对我们有利。 ANDOR 之前绑定,BETWEEN 在两者之前绑定。但是,如果您使用 AND 添加另一个 WHERE 条件,您将需要这些括号。

    性能?

    对于这两种变体,Postgres 12(未针对早期版本进行测试)足够智能,可以根据实际的列统计信息是否仍然使用 (val_3) 上的索引。即使是准备好的语句。

    如果该函数被多次调用,
    并且您使用准备好的语句(直接或间接),
    性能至关重要,
    您的某些范围从索引中受益,而其他范围则没有
    那么您可能仍会分叉(至少)两个不同的查询,以便使用通用的、已保存的查询计划,以获得绝对最佳的性能。

    【讨论】:

    • 一个很好的答案,而且解释得很好。两种方法都简洁且性能良好。非常感谢..
    【解决方案2】:

    CASE 表达式返回 1 个标量值,而不是 2 或值的区间。
    您可以将其用作与表交叉连接的查询:

    SELECT t.val_1, t.val_2, t.val_3
    FROM schema_name.table_name t
    CROSS JOIN (
      SELECT CASE $1 
           WHEN 'a' THEN 0
           WHEN 'b' THEN 1
             .
       END val
    ) c
    WHERE t.val_3 BETWEEN 2 * c.val AND 2 * c.val + 1
    

    编辑。
    您可以使用CTE,它返回$1 的所有可能值以及每个值的间隔:

    WITH cte(val, min, max) AS (
      VALUES ('a', 0, 1), ('b', 2, 7), .........  
    )  
    SELECT t.val_1, t.val_2, t.val_3
    FROM table_name t 
    CROSS JOIN (SELECT * FROM cte WHERE val = $1) c
    WHERE t.val_3 BETWEEN c.min AND c.max 
    

    【讨论】:

    • 非常感谢@forpas 的回答。抱歉,我现在看到我的问题有点太笼统了。尽管您的答案确实适用于我添加的间隔,但实际间隔从 0 到 1,然后是 2 到 7,然后是 8 到 30,依此类推,没有数字模式,所以 WHERE t.val_3 BETWEEN 2 * c.val AND 2 * c.val + 1 不会削减它。再次道歉,我会更新问题。
    猜你喜欢
    • 2019-06-04
    • 1970-01-01
    • 1970-01-01
    • 2016-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-30
    • 2013-10-03
    相关资源
    最近更新 更多