首页 > 解决方案 > Postgres:如果我们多次选择一个计算列,Postgres 会一次又一次地计算它吗?

问题描述

这是我正在尝试的查询,

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 case 中计算 array_agg。即)执行如下

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这两个查询,后者查询中涉及的行数比前者多。但我不明白为什么会这样?

我凭直觉觉得前一种方法更好,但是有人可以解释一下,哪种更好,为什么还是我要求太多?

标签: sqlarrayspostgresqljoinlateral-join

解决方案


最有可能的是,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

推荐阅读