这涉及对符合特定条件(例如,条件)的记录数进行计数。 invoice amount > 0

我更喜欢

COUNT(CASE WHEN invoice_amount > 100 THEN 1 END)


但是,这同样有效。

SUM(CASE WHEN invoice_amount > 100 THEN 1 ELSE 0 END)


我认为COUNT更可取,原因有两个:


传达意图,可能是COUNT
涉及到
在某处进行简单的COUNT操作,而SUM无法依靠它的表达式为简单的整数值。

是否有人对特定RDBMS的区别有具体的认识? br />

#1 楼

您大多数已经自己回答了这个问题。我要补充一点:
在PostgreSQL(和其他支持boolean类型的RDBMS)中,您可以直接使用boolean的测试结果。将其转换为integerSUM()
SUM((amount > 100)::int))

,或在NULLIF()表达式和COUNT()中使用:
COUNT(NULLIF(amount > 100, FALSE))

,或简单的OR NULL:或其他各种表达方式。性能几乎相同。 COUNT()通常比SUM()快很多。与SUM()不同,就像Paul已经评论过的那样,COUNT()从不返回NULL,这可能很方便。相关:

查询优化还是缺少索引?

自Postgres 9.4起,还有汇总的FILTER子句。请参阅:

单个SELECT语句中多个范围的返回计数

比上述所有方法都快大约5-10%:
COUNT(amount > 100 OR NULL)

如果查询与您的测试用例一样简单,只包含一个计数,没有其他内容,则可以重写:
COUNT(*) FILTER (WHERE amount > 100)

...这是性能的真正王者,即使没有索引也是如此。
使用适用的索引,它可以快几个数量级,尤其是对于仅索引的扫描。
基准测试
Postgres 13
(我添加了一个没有新并行性的测试。)
Postgres 10
我为Postgres 10运行了一系列新测试,包括合计FILTER子句并演示了
简单设置:
SELECT count(*) FROM tbl WHERE amount > 100;

实际时间由于背景噪声和测试台的特性而有很大差异。显示来自更多测试的典型最佳时间。这两种情况应该抓住本质:
测试1占所有行的1%
CREATE TABLE tbl (
   tbl_id int
 , amount int NOT NULL
);

INSERT INTO tbl
SELECT g, (random() * 150)::int
FROM   generate_series (1, 1000000) g;

-- only relevant for the last test
CREATE INDEX ON tbl (amount);
测试2占所有行的33% />
SELECT COUNT(NULLIF(amount > 148, FALSE))            FROM tbl; -- 140 ms
SELECT SUM((amount > 148)::int)                      FROM tbl; -- 136 ms
SELECT SUM(CASE WHEN amount > 148 THEN 1 ELSE 0 END) FROM tbl; -- 133 ms
SELECT COUNT(CASE WHEN amount > 148 THEN 1 END)      FROM tbl; -- 130 ms
SELECT COUNT((amount > 148) OR NULL)                 FROM tbl; -- 130 ms
SELECT COUNT(*) FILTER (WHERE amount > 148)          FROM tbl; -- 118 ms -- !

SELECT count(*) FROM tbl WHERE amount > 148; -- without index  --  75 ms -- !!
SELECT count(*) FROM tbl WHERE amount > 148; -- with index     --   1.4 ms -- !!!

db <>在这里拨弄
每组中的最后一个测试使用仅索引扫描,这就是为什么它有助于计数所有行的三分之一的原因。当涉及全部行的大约5%或更多时,纯索引或位图索引扫描无法与顺序扫描竞争。
Postgres 9.1的旧测试
为了验证我在现实生活中对EXPLAIN ANALYZE进行了快速测试在PostgreSQL 9.1.6。
74208中的184568行符合条件kat_id > 50。所有查询返回相同的结果。我分别轮流运行了10次以排除缓存影响,并附加了最佳结果,请注意:
SELECT COUNT(NULLIF(amount > 100, FALSE))            FROM tbl; -- 140 ms
SELECT SUM((amount > 100)::int)                      FROM tbl; -- 138 ms
SELECT SUM(CASE WHEN amount > 100 THEN 1 ELSE 0 END) FROM tbl; -- 139 ms
SELECT COUNT(CASE WHEN amount > 100 THEN 1 END)      FROM tbl; -- 138 ms
SELECT COUNT(amount > 100 OR NULL)                   FROM tbl; -- 137 ms
SELECT COUNT(*) FILTER (WHERE amount > 100)          FROM tbl; -- 132 ms -- !

SELECT count(*) FROM tbl WHERE amount > 100; -- without index  -- 102 ms -- !!
SELECT count(*) FROM tbl WHERE amount > 100; -- with index     --  55 ms -- !!!

性能几乎没有任何真正的区别。

评论


FILTER解决方案是否击败了“慢”群体的任何变化?

– Andriy M
16年5月17日在12:47

@AndriyM:我发现聚合FILTER的时间比上面的表达式要快一些(用9.5页测试)。你也一样吗? (如果可能的话,WHERE仍然是性能之王)。

–欧文·布兰德斯特(Erwin Brandstetter)
16年5月18日,1:12

还没有方便的PG,所以无法判断。无论如何,我只是希望您使用最后一个解决方案的时间来更新您的答案,只是为了完整性:)

– Andriy M
16年5月18日在5:17



@AndriyM:我终于找到了新的基准。在我的测试中,FILTER解决方案通常更快。

–欧文·布兰德斯特(Erwin Brandstetter)
18年5月6日在1:29

#2 楼

这是我在SQL Server 2012 RTM上的测试。

if object_id('tempdb..#temp1') is not null drop table #temp1;
if object_id('tempdb..#timer') is not null drop table #timer;
if object_id('tempdb..#bigtimer') is not null drop table #bigtimer;
GO

select a.*
into #temp1
from master..spt_values a
join master..spt_values b on b.type='p' and b.number < 1000;

alter table #temp1 add id int identity(10,20) primary key clustered;

create table #timer (
    id int identity primary key,
    which bit not null,
    started datetime2 not null,
    completed datetime2 not null,
);
create table #bigtimer (
    id int identity primary key,
    which bit not null,
    started datetime2 not null,
    completed datetime2 not null,
);
GO

--set ansi_warnings on;
set nocount on;
dbcc dropcleanbuffers with NO_INFOMSGS;
dbcc freeproccache with NO_INFOMSGS;
declare @bigstart datetime2;
declare @start datetime2, @dump bigint, @counter int;

set @bigstart = sysdatetime();
set @counter = 1;
while @counter <= 100
begin
    set @start = sysdatetime();
    select @dump = count(case when number < 100 then 1 end) from #temp1;
    insert #timer values (0, @start, sysdatetime());
    set @counter += 1;
end;
insert #bigtimer values (0, @bigstart, sysdatetime());
set nocount off;
GO

set nocount on;
dbcc dropcleanbuffers with NO_INFOMSGS;
dbcc freeproccache with NO_INFOMSGS;
declare @bigstart datetime2;
declare @start datetime2, @dump bigint, @counter int;

set @bigstart = sysdatetime();
set @counter = 1;
while @counter <= 100
begin
    set @start = sysdatetime();
    select @dump = SUM(case when number < 100 then 1 else 0 end) from #temp1;
    insert #timer values (1, @start, sysdatetime());
    set @counter += 1;
end;
insert #bigtimer values (1, @bigstart, sysdatetime());
set nocount off;
GO


分别查看各个运行和批次

select which, min(datediff(mcs, started, completed)), max(datediff(mcs, started, completed)),
            avg(datediff(mcs, started, completed))
from #timer group by which
select which, min(datediff(mcs, started, completed)), max(datediff(mcs, started, completed)),
            avg(datediff(mcs, started, completed))
from #bigtimer group by which


运行5次(重复)后的结果尚无定论。

which                                       ** Individual
----- ----------- ----------- -----------
0     93600       187201      103927
1     93600       187201      103864

which                                       ** Batch
----- ----------- ----------- -----------
0     10108817    10545619    10398978
1     10327219    10498818    10386498


表明运行条件下的可变性远大于两者之间的差异。以SQL Server计时器的粒度衡量时的实现。两种版本都可以放在首位,而我得到的最大方差是2.5%。

,但是采用另一种方法:

set showplan_text on;
GO
select SUM(case when number < 100 then 1 else 0 end) from #temp1;
select count(case when number < 100 then 1 end) from #temp1;


StmtText(SUM)

  |--Compute Scalar(DEFINE:([Expr1003]=CASE WHEN [Expr1011]=(0) THEN NULL ELSE [Expr1012] END))
       |--Stream Aggregate(DEFINE:([Expr1011]=Count(*), [Expr1012]=SUM([Expr1004])))
            |--Compute Scalar(DEFINE:([Expr1004]=CASE WHEN [tempdb].[dbo].[#temp1].[number]<(100) THEN (1) ELSE (0) END))
                 |--Clustered Index Scan(OBJECT:([tempdb].[dbo].[#temp1]))


StmtText(COUNT)

  |--Compute Scalar(DEFINE:([Expr1003]=CONVERT_IMPLICIT(int,[Expr1008],0)))
       |--Stream Aggregate(DEFINE:([Expr1008]=COUNT([Expr1004])))
            |--Compute Scalar(DEFINE:([Expr1004]=CASE WHEN [tempdb].[dbo].[#temp1].[number]<(100) THEN (1) ELSE NULL END))
                 |--Clustered Index Scan(OBJECT:([tempdb].[dbo].[#temp1]))


SUM版本的功能更多。除了求和,它还执行一个COUNT。话虽如此,COUNT(*)是不同的,应该比COUNT([Expr1004])更快(跳过NULL,更多逻辑)。合理的优化程序将认识到SUM版本中[Expr1004]中的SUM([Expr1004])是“ int”类型,因此要使用整数寄存器。

无论如何,我仍然相信COUNT版本在大多数情况下会更快RDBMS,我从测试中得出的结论是,将来我将继续使用SUM(.. 1.. 0..),至少出于SQL Server的原因,除了使用COUNT时出现ANSI警告之外,没有其他原因。

#3 楼

在我进行跟踪的经验中,对于大约10,000,000个查询中的这两种方法,我注意到Count(*)使用大约两倍的CPU,并且运行速度更快。但是我的查询没有过滤器。


CPU...........: 1828   
Execution time:  470 ms  


Sum(1)

CPU...........: 3859  
Execution time:  681 ms  


评论


您应指定用于测试的RDBMS。

–EAmez
19年8月26日在11:16