【问题标题】:Postgres: If we select a computed column multiple times, will Postgres compute it again and again?Postgres:如果我们多次选择一个计算列,Postgres 会一次又一次地计算它吗?
【发布时间】:2020-10-04 03:05:02
【问题描述】:

这是我正在尝试的查询,

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

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

【问题讨论】:

    标签: sql arrays postgresql join lateral-join


    【解决方案1】:

    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
    

    【讨论】:

    • 哦! array_aggregation 是否会减慢查询速度
    • @LunaLovegood:是的,数组聚合似乎不是最优的。我怀疑最快的方法是横向连接 - 但你需要根据你的真实数据集评估它。
    • @GMB 。 . .由于 case 表达式的顺序排序,如果表达式只计算一次,我会感到有点惊讶。
    • @GMB 在您建议的第二种方法中,您为什么选择CROSS JOIN,考虑到我们有tgs.sweet_id = s.id 的外键关系,LEFT JOIN 会更合适吗?
    • @GMB 同样在第一种方法中不会为每个案例语句计算 count(*) 吗?
    【解决方案2】:

    我相当肯定,在case 表达式中,when 子句将针对每个条件分别进行评估。这意味着你的同事是正确的。 . .大概吧。

    documentation 的操作部分是:

    每个条件都是一个返回布尔结果的表达式。如果条件的结果为真,则 CASE 表达式的值是条件后面的结果,CASE 表达式的其余部分不被处理。

    Postgres 有可能通过评估一次子表达式来对子表达式进行某种优化。但是,声明:“CASE 表达式的其余部分未处理”非常强大,它表明每个子句只会在前面的子句评估为 false(或 NULL)之后才被处理。

    无论优化器是否确定一个函数只能被调用一次,横向连接都保证它会被评估一次,因此对于昂贵的操作来说,这似乎是更安全的解决方案。

    【讨论】:

    • 带 LEFT LATERAL 的查询似乎有点慢,影响更多的行? 参考:explain.depesz.com/s/xVta 没有 LEFT LATERAL 的查询explain.depesz.com/s/PZkX 可能会因数据而异。 ?
    • @LunaLovegood 。 . .你有tgs.sweet_id 的索引吗?
    • @LunaLovegood 。 . .呃。等效查询将使用inner join,而不是left join。两者都使用left join,或者两者都使用inner join。此外,在使用lateral join 时删除group by。这是不必要的——但 Postgres 可能不会优化它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多