昨天晚上,和同事讨论了一个SQL的问题,是如何选择根据某字段分组,然后取出MAX COUNT(XX) 值的数据出来。例如数据是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
test=# select * from tgroup;
id | age | point
----+-----+-------
1 | 1 | 11
2 | 1 | 32
3 | 2 | 32
4 | 2 | 13
5 | 2 | 33
6 | 2 | 38
7 | 3 | 38
8 | 2 | 38
9 | 2 | 38
10 | 2 | 38
11 | 2 | 38
(11 rows)
test=#
现在要选择出根据 POINT 分组里包含个数最大的值。大概意思是:MAX(COUNT(*)) FROM tgroup GROUP BY POINT; 所以,一开始,我们的SQL语句是(失败):
1
select MAX (COUNT (*)) FROM tgroup group by point;
然而,我们得出的错误是:
1
aggregate function calls cannot be nested
原来,聚集函数是不能嵌套调用的。
然后又想到能不能使用子查询来完成。SQL如下(成功) :
1
select max (sum ) from ( select count (*) as sum from tgroup group by point) as t;
再来一条,不使用子查询,而是使用ORDER BY 结合 LIMIT 来完成,SQL语句如下(成功):
1
select count (*) as sum from tgroup group by point order by sum desc limit 1 ;
最后使用PG里的CTE表达式最容易理解(成功):
1
with cte as (select count (*) over (partition by point) from tgroup) select max (count ) from cte;
那他们的性能是何呢?测试了一千一百五十多万的数据。每条SQL性能如何?
1
2
3
4
5
6
7
test=# select count (*) from tgroup;
count
11534336
(1 row)
test=#
使用子查询的性能(POINT没有索引)
1
2
3
4
5
6
7
test=# select max (sum ) from ( select count (*) as sum from tgroup group by point) as t;
max
6291456
(1 row)
Time: 3055.716 ms
ORDER BY 结合 LIMIT(POINT没有索引)
1
2
3
4
5
6
7
8
test=# select count (*) as sum from tgroup group by point order by sum desc limit 1 ;
sum
6291456
(1 row)
Time: 3047.152 ms
test=#
使用CTE表达式(POINT没有索引)
1
2
3
4
5
6
7
test=# with cte as (select count (*) over (partition by point) from tgroup) select max (count ) from cte;
max
6291456
(1 row)
Time: 25675.005 ms
后面为POINT添加索引,速度只有CTE表达式的加快了一倍(添加索引其实也不太科学,POINT的数据分布不均匀,重复的数据比较多,因为是通过 insert into select 的方式来生成大量数据的,只是想看一下添加索引后的效果):
1
2
3
4
5
6
7
8
test=# with cte as (select count (*) over (partition by point) from tgroup) select max (count ) from cte;
max
6291456
(1 row)
Time: 11735.775 ms
test=#
其他两种方式,并没有什么变化。看执行计划,其他两种依然是使用Seq Scan的方式,而添加了索引后,CTE的方式使用了 CTE Scan + IndexOnlyScan的方式。
1
2
3
4
5
6
7
8
9
10
11
test=
QUERY PLAN
----------------------------------------------------------------------------------------------------------
Aggregate (cost= 955503.54 ..955503.55 rows= 1 width= 8 )
CTE cte
-> WindowAgg (cost= 0.43 ..695980.98 rows= 11534336 width= 4 )
-> Index Only Scan using tgroup_point on tgroup (cost= 0.43 ..522965.94 rows= 11534336 width= 4 )
-> CTE Scan on cte (cost= 0.00 ..230686.72 rows= 11534336 width= 8 )
(5 rows)
Time: 0.909 ms
总结
看来没有 WHERE 或其他条件过滤数据而且数据量非常大的情况下,不适宜使用CTE表达式,因为它本质是一个一次性视图,生成一张这么大的视图,性能也快不到哪里去(可能使用物化视图会好点,不过没有测试过)。在大量数据情况下,还是使用普通的全表扫描比使用生成CTE再全表扫描来得快。这也应验了之前翻译篇文章的强调:CTE表达式的作用,真的不是为了加快查询速度,而仅仅是为了方便。冏 ~~。