暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

PostgreSQL18允许Grouping Sets的HAVING子句下推

原创 墨竹 2025-04-25
213

PostgreSQL18允许Grouping Sets的HAVING子句下推

虽然离PostgreSQL18发版还很久远,但是许多已经提交到git上的新特性,我们可以慢慢的揭开它的面纱,看看是否有我们感兴趣的一些新特性。今天我们来看看在PostgreSQL17之前(包括PG17)的版本中Grouping Sets的HAVING子句未下推的问题。

存在问题

PostgreSQL17之前版本的版本中Grouping Sets的HAVING子句未下推的问题?

PostgreSQL17的执行计划

在PostgreSQL17的客户端中执行sql语句

demo=# EXPLAIN (costs off)
demo-# SELECT aircraft_code, status, COUNT(*)
demo-# FROM flights
demo-# GROUP BY GROUPING SETS ((aircraft_code, status), (status))
demo-# HAVING status = 'Arrived';
                QUERY PLAN
----------------------------------------------
HashAggregate
  Hash Key: status, aircraft_code
  Hash Key: status
  Filter: ((status)::text = 'Arrived'::text)
  -> Seq Scan on flights
(5 rows)

Time: 19.832 ms

从PostgreSQL17的执行计划,可以看到执行计划中的Filter行,它位于HashAggregate节点下,但是又在Seq Scan之前,这就表示在聚合的时候才检查HAVING条件,而不是我们希望的在对表扫描的时候过滤数据。

准备测试用例

在PostgreSQL17和PostgreSQL18DEV版本数据库导入demo测试数据,demo的数据库可以从下面的地址自行下载。

https://postgrespro.com/community/demodb

利用在flights表来验证Grouping Sets的having子句的是否下推

SELECT aircraft_code, status, COUNT(*)
FROM flights
GROUP BY GROUPING SETS ((aircraft_code, status), (status))
HAVING status = 'Arrived';

问题原因

这里的主要原因是如果存在任何非空的分组集,我们就不能把HAVING条件下推到where中,因为我们没有办法判断HAVING子句是否引用了分组集可以为空的任何列,并且将这样的子句移动到WHERE中可能会改变结果。

为了方便大家快速理解,使用上述的测试用例l来讲述,在执行的SQL中的分组结果集有两个(aircraft_code, status), (status),having条件为status = 'Arrived',当然原来的SQL的having条件列都包括在分组集的列中,因此在这里可以是可以把having条件下推到where中,但是如果我们在添加一个条件aircraft_code in ('319','733','773','321')。这个时候测试的SQL语句就成为了下面的语句。

EXPLAIN (costs off)
SELECT aircraft_code, status, COUNT(*)
FROM flights
GROUP BY GROUPING SETS ((aircraft_code, status), (status))
HAVING aircraft_code in ('319','733','773','321') and status = 'Arrived';

在这个语句中,HAVING子句的新增的aircraft_code in ('319','733','773','321') 条件

demo=# SELECT aircraft_code, status, COUNT(*)
demo-# FROM flights
demo-# GROUP BY GROUPING SETS ((aircraft_code, status), (status))
demo-# HAVING aircraft_code in ('319','733','773','321') and status = 'Arrived';
aircraft_code | status | count
---------------+---------+-------
733           | Arrived | 7635
319           | Arrived | 7417
321           | Arrived | 11702
773           | Arrived | 3657
(4 rows)

假如可以把having条件下推,SQL就等价于把查询条件放到了where条件中,如下面的SQL:

demo=# SELECT aircraft_code, status, COUNT(*)
demo-# FROM flights
demo-# where aircraft_code in ('319','733','773','321') and status = 'Arrived'
demo-# GROUP BY GROUPING SETS ((aircraft_code, status), (status));
aircraft_code | status | count
---------------+---------+-------
733           | Arrived | 7635
319           | Arrived | 7417
321           | Arrived | 11702
773           | Arrived | 3657
              | Arrived | 30411
(5 rows)

从查询的结果的行数,也可以看出两个SQL是不等价的,主要是因为aircraft_code在分组集(status)中没有包括aircraft_code条件,因此如果把aircraft_code下推到where条件会导致查询的结果不一致。

解决方案

在PostgreSQL18通过使用RTE_GROUP RTE的RT索引对grouping sets标记为可空的表达式,识别那些引用"可空grouping-sets列"的子句变得容易得多:我们只需要检查RTE_GROUP RTE的RT索引是否存在于子句中。对于其他HAVING子句,则可以安全的下推。下面我们在PG18中来看一下执行计划。

PostgreSQL18的执行计划

在PostgreSQL18的客户端中执行sql语句

demo=# EXPLAIN (costs off)
demo-# SELECT aircraft_code, status, COUNT(*)
demo-# FROM flights
demo-# GROUP BY GROUPING SETS ((aircraft_code, status), (status))
demo-# HAVING status = 'Arrived';
                    QUERY PLAN
----------------------------------------------------
HashAggregate
  Hash Key: status, aircraft_code
  Hash Key: status
  -> Seq Scan on flights
        Filter: ((status)::text = 'Arrived'::text)
(5 rows)

通过上面的执行计划,可以看出Filter行,现在位于Seq Scan节点下,而过去位于HashAggregate节点下,这就意味着在扫描表行时就检查HAVING条件,也就是在聚合前进行过滤。当然,对于上面的SQL,其实是可以进行查询重写的,将条件从HAVING移到WHERE中,但是也很高兴看到开发者找到更有效的解决方案。

总结

最后,我们也来总结一下,通过这篇文章,你至少可以了解如下:

  • PG18允许Grouping Sets的HAVING子句下推,该功能是优化器自动处理

  • 理解了之前的版本Grouping SetsHAVING子句没有办法判断HAVING子句是否引用了分组集可以为空的任何列,并且将这样的子句移动到WHERE中可能会改变结果。

  • 过滤条件尽可能的放到where条件中,而不是HAVING子句中,这其实也是之前一些优化经验。

参考

https://postgrespro.com/blog/pgsql/5971769#commit_67a54b9e8

https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=67a54b9e8

– / END / –

可以通过下面的方式联系我

如果这篇文章为你带来了灵感或启发,就请帮忙点赞收藏转发;如果文章中不严谨或者错漏之处,请及时评论指正。非常感谢!

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论