Answers:
您大多数已经自己回答了这个问题。我要补充一些内容:
在PostgreSQL(以及其他支持该boolean
类型的RDBMS )中,您可以boolean
直接使用测试结果。将其投射到integer
和SUM()
:
SUM((amount > 100)::int))
或在NULLIF()
表达式中使用它和COUNT()
:
COUNT(NULLIF(amount > 100, FALSE))
或简单地OR NULL
:
COUNT(amount > 100 OR NULL)
或其他各种表达方式。性能几乎相同。COUNT()
通常比快一点SUM()
。不同于SUM()
与像保罗已经评论,COUNT()
从来没有回报NULL
,这可能是方便。有关:
从Postgres 9.4开始,还有FILTER
子句。细节:
它比上述所有方法快大约5-10%:
COUNT(*) FILTER (WHERE amount > 100)
如果查询与您的测试用例一样简单,仅包含一个计数而没有其他内容,则可以重写:
SELECT count(*) FROM tbl WHERE amount > 100;
即使没有索引,这才是真正的性能之王。
使用适用的索引,可以快几个数量级,尤其是仅索引扫描时。
我为Postgres 10进行了一系列新的测试,包括聚合FILTER
子句并演示了大小索引的作用。
设置简单:
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);
由于背景噪声和测试台的特性,实际时间相差很大。显示来自更多测试的典型最佳时间。这两种情况应该抓住本质:
测试1占所有行的1%
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 <> 在这里拨弄
测试2占所有行的33%
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 -- !!!
db <> 在这里拨弄
每组中的最后一个测试使用仅索引扫描,这就是为什么它有助于计数所有行的三分之一的原因。当涉及所有行的大约5%或更多时,纯索引或位图索引扫描无法与顺序扫描竞争。
为了验证,我EXPLAIN ANALYZE
在PostgreSQL 9.1.6中的真实表上进行了快速测试。
184568行中的74208个具有该条件kat_id > 50
。所有查询返回相同的结果。我依次轮流运行了10次以排除缓存影响,并附加了最佳结果,如下所示:
SELECT SUM((kat_id > 50)::int) FROM log_kat; -- 438 ms
SELECT COUNT(NULLIF(kat_id > 50, FALSE)) FROM log_kat; -- 437 ms
SELECT COUNT(CASE WHEN kat_id > 50 THEN 1 END) FROM log_kat; -- 437 ms
SELECT COUNT((kat_id > 50) OR NULL) FROM log_kat; -- 436 ms
SELECT SUM(CASE WHEN kat_id > 50 THEN 1 ELSE 0 END) FROM log_kat; -- 432 ms
性能几乎没有任何实际差异。
FILTER
与上面的表达式相比(与pg 9.5进行测试),我发现集合的时间略短一些。你也一样吗?(WHERE
仍然是表现之王-尽可能)。
FILTER
解决方案是典型的在我的测试速度更快。
这是我在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;
|--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]))
|--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,更多逻辑)。一个合理的优化器将认识到,[Expr1004]
在SUM([Expr1004])
在SUM形式是一种“INT”型等利用的整数寄存器。
无论如何,尽管我仍然相信该COUNT
版本在大多数RDBMS中会更快,但是从测试中得出的结论是,我SUM(.. 1.. 0..)
将来会继续使用,至少对于SQL Server而言,除了使用ANSI WARNINGS时会提出其他原因COUNT
。
在我进行跟踪的经验中,对于大约10,000,000个查询中的两种方法,我注意到Count(*)使用大约两倍的CPU,并且运行速度更快。但是我的查询没有过滤条件。
计数(*)
CPU...........: 1828
Execution time: 470 ms
总和(1)
CPU...........: 3859
Execution time: 681 ms