これは、特定の条件に一致するレコード数のカウントに関連しています。 invoice amount > $100
。
私は好む傾向があります
COUNT(CASE WHEN invoice_amount > 100 THEN 1 END)
ただし、これは有効です
SUM(CASE WHEN invoice_amount > 100 THEN 1 ELSE 0 END)
COUNTが2つの理由で望ましいと思いました。
COUNT
に対する意図を伝えますCOUNT
おそらくは単純なi += 1
演算はどこかで行われますが、SUMはその式を単純な整数値と見なすことができません。特定のRDBMSの違いについて具体的な事実はありますか?
あなたは主にすでにあなた自身に質問に答えました。追加する一口があります:
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()
とは異なり Paulはコメント済み とは異なり、COUNT()
はNULL
を返すことはないため、便利な場合があります。関連:
Postgres 9.4以降、FILTER
句もあります。詳細:
上記のすべてよりも5〜10%ほど速い:
_COUNT(*) FILTER (WHERE amount > 100)
_
Ifクエリがテストケースと同じくらい簡単で、カウントが1つだけで、それ以外の場合は、次のように書き換えることができます。
_SELECT count(*) FROM tbl WHERE amount > 100;
_
これは、インデックスがなくても、真のパフォーマンスの王様です。
適切なインデックスを使用すると、特にインデックスのみのスキャンの場合、桁違いに高速になります。
集約FILTER
句を含むPostgres 10の新しい一連のテストを実行し、小さいカウントと大きいカウントのインデックスの役割を示しました。
簡単なセットアップ:
_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);
_
実際の時間は、バックグラウンドノイズとテストベッドの仕様により、かなり異なります。より大きなテストのセットからtypical最高の時間を表示します。これらの2つのケースは本質を捉えるべきです:
テスト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 <> fiddle ここ
テスト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 <> fiddle ここ
各セットの最後のテストでは、index-onlyスキャンを使用したため、すべての行の3分の1をカウントするのに役立ちました。プレーンインデックスまたはビットマップインデックススキャンは、すべての行の約5%以上を含む場合、シーケンシャルスキャンと競合することはできません。
確認するには、PostgreSQL 9.1.6の実際のテーブルで_EXPLAIN ANALYZE
_を使用して簡単なテストを実行しました。
_kat_id > 50
_の条件で修飾された184568行の74208。すべてのクエリは同じ結果を返します。私はそれぞれ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
_
実際のパフォーマンスの違いはほとんどありません。
これは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 に加えて SUMを実行しています。そうは言っても、COUNT(*)
は異なり、COUNT([Expr1004])
よりも高速である必要があります(NULLをスキップして、ロジックを増やします)。合理的なオプティマイザは、SUMバージョンのSUM([Expr1004])
内の_[Expr1004]
_が「int」型であることを認識するため、整数レジスタを利用します。
いずれの場合でも、ほとんどのRDBMSではCOUNT
バージョンの方が高速であると私はまだ信じていますが、テストからの結論は、将来SUM(.. 1.. 0..)
を使用するつもりであるということです。少なくともSQL Serverの場合、COUNT
を使用するとANSI WARNINGSが発生する以外の理由はありません。
トレースを作成した私の経験では、約10,000,000のクエリの両方のメソッドで、Count(*)が約2倍のCPUを使用し、少し速く実行されていることに気付きました。しかし、私のクエリにはフィルターがありません。
カウント(*)
CPU...........: 1828
Execution time: 470 ms
Sum(1)
CPU...........: 3859
Execution time: 681 ms