postgres:如果我们多次选择一个计算列,postgres会一次又一次地计算它吗?

mnowg1ta  于 2021-08-09  发布在  Java
关注(0)|答案(2)|浏览(336)

这是我要问的问题,

SELECT s.id, s.name AS name,
CASE WHEN (ARRAY_AGG(tgs.status) @> '{Hard} ') THEN 'Hard'
WHEN (ARRAY_AGG(tgs.status) @> '{Soft} ') THEN 'Soft'
WHEN (ARRAY_AGG(tgs.status) @> '{Fluid} ') THEN 'Fluid'
WHEN (ARRAY_AGG(tgs.status) @> '{Gummy} ') THEN 'Gummy'
WHEN (ARRAY_AGG(tgs.status) @> '{Expired} ') THEN 'Expired'
END AS status, 
COUNT(*) OVER()
FROM sweets AS s 
INNER JOIN tasty_goofy_sweets AS tgs on tgs.sweet_id = s.id
GROUP BY s.id;

在实现这一点时,我的朋友建议,我们可以使用left join-lateral并只计算一次,而不是在switch情况下每次都计算array\u agg。i、 e)实施如下

SELECT s.id, s.name AS name,
CASE WHEN (tgs.status @> '{Hard} ') THEN 'Hard'
WHEN (tgs.arr_status @> '{Soft} ') THEN 'Soft'
WHEN (tgs.arr_status @> '{Fluid} ') THEN 'Fluid'
WHEN (tgs.arr_status @> '{Gummy} ') THEN 'Gummy'
WHEN (tgs.arr_status @> '{Expired} ') THEN 'Expired'
END AS status, 
COUNT(*) OVER()
FROM sweets AS s 
LEFT JOIN LATERAL ( SELECT ARRAY_AGG(tgs.status) AS arr_status FROM tasty_goofy_sweets tgs WHERE  tgs.sweet_id = s.id
) AS tgs ON TRUE
GROUP BY s.id;

但我不确定postgres是否计算 ARRAY_AGG 珍惜每一次,我们如何才能决定哪种方法更好?我试着看着 explain analyse 对于这两个查询,后一个查询中涉及的行数都多于前者。但我不明白为什么会这样?
我直觉上觉得前一种方法更好,但有人能解释一下,哪种更好,为什么我要求太多?

f1tvaqid

f1tvaqid1#

我很肯定在一个 case 表达式 when 对于每个条件,子句将分别进行评估。这意味着你的同事是对的。可能。
文件的有效部分是:
每个条件都是一个返回布尔结果的表达式。如果条件的结果为true,则case表达式的值是该条件后面的结果,并且不处理case表达式的其余部分。
postgres有可能通过对子表达式求值一次来对其进行某种优化。然而,“case表达式的剩余部分未被处理”这句话非常有力,它表明每个子句只有在前一个子句的计算结果为false(或false)之后才会被处理 NULL ).
不管优化器是否发现一个函数只能被调用一次,横向连接保证它只被求值一次,所以对于一个昂贵的操作来说,这似乎是更安全的解决方案。

tnkciper

tnkciper2#

最有可能的是,postgres会优化掉这个倍数 array_agg() s、 只计算一次,并在每次比较中重用结果。这是非常简单的查询优化,数据库应该很容易发现。
不过,我建议您使用条件聚合来简化查询。您不需要聚合到一个数组中,只是为了检查给定的值是否存在:

select
    s.id,
    s.name
    case 
        when count(*) filter(where status = 'Hard')    > 0 then 'Hard',
        when count(*) filter(where status = 'Soft')    > 0 then 'Soft',
        when count(*) filter(where status = 'Fluid')   > 0 then 'Fluid'
        when count(*) filter(where status = 'Gummy')   > 0 then 'Gummy',
        when count(*) filter(where status = 'Expired') > 0 then 'Expired'
    end status,
    count(*) over() cnt
from sweets s
inner join tasty_goofy_sweets AS tgs on tgs.sweet_id = s.id
group by s.id;

您也可以使用横向联接和条件排序来表示此表达式,而无需聚合:

select
    s.id,
    s.name,
    tgs.status,
    count(*) over() cnt
from sweets s
cross join lateral (
    select status
    from tasty_goofy_sweets as tgs 
    where tgs.sweet_id = s.id
    order by case status 
        when 'Hard'    then 1
        when 'Soft'    then 2
        when 'Fluid'   then 3
        when 'Gummy'   then 4
        when 'Expired' then 5
    end
    limit 1
) tgs

相关问题