Postgres:如果我们多次 select 一个计算列,Postgres 会一次又一次地计算它吗?
Postgres: If we select a computed column multiple times, will Postgres compute it again and again?
这是我正在尝试的查询,
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
,后一个查询涉及的行数比前者多。但是我不明白为什么会这样?
我直觉上觉得前一种方法更好,但是有人可以推理出哪种方法更好吗?为什么或者我要求太多了?
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
我相当确定在 case
表达式中,when
子句将针对每个条件单独计算。这意味着你的同事是正确的。 . .可能。
documentation 的执行部分是:
Each condition is an expression that returns a boolean result. If the condition's result is true, the value of the CASE expression is the result that follows the condition, and the remainder of the CASE expression is not processed.
Postgres 有可能通过对子表达式求值一次来对子表达式进行某种优化。但是,声明:"the remainder of the CASE expression is not processed" 非常强大,表明每个子句只会在前一个子句评估为 false(或 NULL
)后才被处理。
无论优化器是否确定一个函数只能被调用一次,横向连接保证它会被评估一次,因此对于昂贵的操作来说这似乎是更安全的解决方案。
这是我正在尝试的查询,
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
,后一个查询涉及的行数比前者多。但是我不明白为什么会这样?
我直觉上觉得前一种方法更好,但是有人可以推理出哪种方法更好吗?为什么或者我要求太多了?
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
我相当确定在 case
表达式中,when
子句将针对每个条件单独计算。这意味着你的同事是正确的。 . .可能。
documentation 的执行部分是:
Each condition is an expression that returns a boolean result. If the condition's result is true, the value of the CASE expression is the result that follows the condition, and the remainder of the CASE expression is not processed.
Postgres 有可能通过对子表达式求值一次来对子表达式进行某种优化。但是,声明:"the remainder of the CASE expression is not processed" 非常强大,表明每个子句只会在前一个子句评估为 false(或 NULL
)后才被处理。
无论优化器是否确定一个函数只能被调用一次,横向连接保证它会被评估一次,因此对于昂贵的操作来说这似乎是更安全的解决方案。