【问题标题】:prevent evaluation of non-matching case防止评估不匹配的情况
【发布时间】:2021-06-08 17:19:02
【问题描述】:

我在 select 中有这个 case 语句:

CASE ('$5' = '')
  WHEN NOT FALSE THEN TRUE
ELSE
  ST_DWithin(latlong_g, ST_SetSRID(ST_Point($5::float, $6::float), 4326), $7, false) END

所以如果给prepare的参数是空的,case的计算结果为true(对于封闭和它在查询中使用的地方),否则应该执行else。

不幸的是,看起来 ELSE 是由 pg 解析器评估的,它被以下消息阻塞:

错误:双精度类型的输入语法无效:“”

有没有办法防止这个错误?

编辑:为清楚起见完整查询:

SELECT addritems.*, 
       St_y(adresse.latlong), 
       St_x(adresse.latlong) 
FROM   adresse 
inner join addritems 
        ON addritems.adrcd = adresse.adrcd 
       AND addritems.plz LIKE Coalesce(Nullif($2, ''), addritems.plz) 
       AND CASE ( $3 = '' ) 
                 WHEN NOT FALSE THEN TRUE 
                 ELSE St_dwithin(latlong_g, 
                                 St_setsrid(St_point($3::FLOAT, $4:: FLOAT), 4326), 
                                 $5, FALSE)
           END 

【问题讨论】:

  • 您是在为WHERE 使用这个值,还是在SELECT 中返回一个字段?什么是完整的查询?因为 CASE 不能 同时返回 TRUEST_DWithin。所以也许你想要NULL 作为第一个
  • 我在WHERE .... AND CASE 在查询 PostGIS 时调用 sql 库时使用它。参数 $5 可能设置为数值或空字符串,因此整个 CASE 应计算为 TRUE 以成为 AND TRUE,或者,如果设置了参数,则调用 ST_DWithin 函数。但是,即使输入为 '',也会评估 CASE ELSE 部分
  • Edit1:对 ST_Point 的调用中的 ' 可能不会被使用。
  • 我偶然发现了同样的问题 - ELSE 在不需要时进行评估。更简单的例子:db-fiddle.com/f/4jyoMCicNSZpjMt4jFYoz5/1737。有人可以澄清一下:这是否是 PG 错误?我检查了 PostgreSQL 10(小提琴)和 PG 11。
  • @JohnDoe 看到我更新的答案,我试图解释为什么会失败

标签: sql postgresql


【解决方案1】:

如何使用 OR:

SELECT addritems.*, St_y(adresse.latlong), St_x(adresse.latlong) 
FROM  adresse 
inner join addritems 
  ON addritems.adrcd = adresse.adrcd 
  AND addritems.plz LIKE Coalesce(Nullif($2, ''), addritems.plz) 
  AND ( 
        $3 = '' 
        OR St_dwithin(latlong_g,St_setsrid(St_point($3::FLOAT, $4::FLOAT),4326),$5,FALSE)
      )

从评论中回答亚历克斯:

原因是:Postgresql 在解析查询时,将 (1/0) 视为一个常量,并尝试通过实际潜水数字来使其更简单,而如果您将其替换为值不能为在解析步骤中评估,它将推迟到执行时间。例如,如果将 (1/0) 替换为 (1/floor(random()),则查询将成功运行,因为“random()”的值将在执行时显示。

SELECT 
    current_catalog
    ,current_catalog::text = 'test'
    ,CASE
        WHEN current_catalog::text = 'test' THEN 1  -- division by zero
       ELSE 1 / floor(random())
    END 

注意:由于 random() 介于 0 和 1 之间,因此 floor(random()) 始终为 0。

【讨论】:

  • OP 问题是关于CASE-WHEN-ELSE-END 声明。但无论如何:你能解释一下如何解决这样的查询:db-fiddle.com/f/4jyoMCicNSZpjMt4jFYoz5/1737
  • @AlexYu 看到我更新的答案,我试图解释为什么它失败了
  • 嗯。看来你是对的。这样的技巧将解决我的问题(我希望能够向 SQL 脚本添加停止器)。虽然我不确定这是否能解决原来的 OP 问题。无论如何:谢谢!
  • 我接受你的回答。您帮助我实现了简单的功能:假设我们有很多内容相似的数据库。目标是创建在一个数据库上工作并在其他数据库上停止的脚本。例如,在使用 DBeaver 时选择不正确的数据库非常简单。简单的解决方案 - 在 CASE 中检查 current_catalog 并在不需要 db 时做一些非法的事情。我首先在 ELSE 中尝试了1/0,但没有奏效。 1/floor(random()) 的技巧 - 正是我们所需要的。
  • 虽然我不确定是 OP 要求的。但无论如何,看起来他在 5 年前解决了他的问题。
【解决方案2】:

我认为你应该这样写你的 CASE

CASE 
    WHEN ('$5' = '') THEN NULL  -- or -1 some other constant, 
                                -- I guess distance cant be negative.
    ELSE ST_DWithin(latlong_g, 
                    ST_SetSRID(ST_Point('$5'::float, '$6'::float), 4326), 
                    $7, false) 
END

如果NULL为你工作,也可以使用CASE的默认值

CASE 
    WHEN ('$5' <> '') 
    THEN ST_DWithin(latlong_g, 
                    ST_SetSRID(ST_Point('$5'::float, '$6'::float), 4326), 
                    $7, false) 
    -- NO NEED ELSE mean default to NULL.
END

编辑:您需要查询后

   AND (( $3 = '')  OR
        ( St_dwithin(latlong_g, 
                     St_setsrid(St_point($3::FLOAT, $4::FLOAT), 4326), 
                     $5, FALSE)
        ))

【讨论】:

  • 我在WHERE子句中使用CASE,因此它应该评估为TRUE以匹配所有行,或者,如果设置了参数$5,则调用该函数,该函数返回真假。
  • 我告诉过你给我看你的完整查询。看起来像你想要的WHERE (('$5' == '') OR (condition filter))
  • 提供完整查询
  • 我偶然发现了与 OP 相同的问题,并且我有更简单的查询:db-fiddle.com/f/4jyoMCicNSZpjMt4jFYoz5/1736 我想这是 PostgreSQL 中的一个错误 - ELSE 总是被评估。也许你可以澄清一些事情?我弄错了吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多