回答セクション
さまざまなT-SQLコンストラクトを使用して、これを書き換えるさまざまな方法があります。長所と短所を見て、以下の全体的な比較を行います。
最初に:使用OR
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18
OR u.Age IS NULL;
を使用OR
すると、必要な行の正確な数を読み取るより効率的なシークプランが得られますが、技術的な世界がa whole mess of malarkey
クエリプランに呼び出すものが追加されます。
また、シークはここで2回実行されることに注意してください。これは、グラフィカルオペレーターから実際に明らかです。
Table 'Users'. Scan count 2, logical reads 8233, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 469 ms, elapsed time = 473 ms.
2番目:派生テーブルを使用してUNION ALL
クエリをこのように書き換えることもできます
SELECT SUM(Records)
FROM
(
SELECT COUNT(Id)
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT COUNT(Id)
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x (Records);
これにより、同じタイプの計画が生成され、マラキーの発生がはるかに少なくなり、インデックスが検索された(検索された)回数についてより明確な程度の正直さが得られます。
OR
クエリと同じ量の読み取り(8233)を実行しますが、CPU時間を約100ミリ秒短縮します。
CPU time = 313 ms, elapsed time = 315 ms.
ただし、このプランを並列化しようとすると、2つの個別の操作がそれぞれグローバルスカラー集約と見なされるため、シリアル化されるため、ここでは本当に注意する必要COUNT
があります。トレースフラグ8649を使用して並列プランを強制すると、問題が明らかになります。
SELECT SUM(Records)
FROM
(
SELECT COUNT(Id)
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT COUNT(Id)
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x (Records)
OPTION(QUERYTRACEON 8649);
これは、クエリをわずかに変更することで回避できます。
SELECT SUM(Records)
FROM
(
SELECT 1
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT 1
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x (Records)
OPTION(QUERYTRACEON 8649);
これで、シークを実行する両方のノードは、連結演算子に到達するまで完全に並列化されます。
価値のあるものとして、完全な並列バージョンにはいくつかの良い利点があります。約100の追加読み取りと約90ミリ秒の追加CPU時間のコストで、経過時間は93ミリ秒に縮小します。
Table 'Users'. Scan count 12, logical reads 8317, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 500 ms, elapsed time = 93 ms.
CROSS APPLYはどうですか?
魔法がなければ答えは完全ではありませんCROSS APPLY
!
残念ながら、でさらに問題が発生しCOUNT
ます。
SELECT SUM(Records)
FROM dbo.Users AS u
CROSS APPLY
(
SELECT COUNT(Id)
FROM dbo.Users AS u2
WHERE u2.Id = u.Id
AND u2.Age < 18
UNION ALL
SELECT COUNT(Id)
FROM dbo.Users AS u2
WHERE u2.Id = u.Id
AND u2.Age IS NULL
) x (Records);
この計画は恐ろしいです。これは、聖パトリックの日の最後に現れるときに最終的に起こるような計画です。うまく並行していますが、何らかの理由でPK / CXをスキャンしています。えー このプランのコストは2198クエリドルです。
Table 'Users'. Scan count 7, logical reads 31676233, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 29532 ms, elapsed time = 5828 ms.
これは奇妙な選択です。非クラスター化インデックスを使用するように強制すると、コストが1798クエリドルにかなり低下するためです。
SELECT SUM(Records)
FROM dbo.Users AS u
CROSS APPLY
(
SELECT COUNT(Id)
FROM dbo.Users AS u2 WITH (INDEX(ix_Id_Age))
WHERE u2.Id = u.Id
AND u2.Age < 18
UNION ALL
SELECT COUNT(Id)
FROM dbo.Users AS u2 WITH (INDEX(ix_Id_Age))
WHERE u2.Id = u.Id
AND u2.Age IS NULL
) x (Records);
ねえ、シーク!あそこをチェックしてください。また、の魔法によりCROSS APPLY
、ほとんど完全に並行な計画を立てるために間抜けなことをする必要はありません。
Table 'Users'. Scan count 5277838, logical reads 31685303, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 27625 ms, elapsed time = 4909 ms.
クロスアプライはCOUNT
、そこに何も入っていなくてもうまくいく結果になります。
SELECT SUM(Records)
FROM dbo.Users AS u
CROSS APPLY
(
SELECT 1
FROM dbo.Users AS u2
WHERE u2.Id = u.Id
AND u2.Age < 18
UNION ALL
SELECT 1
FROM dbo.Users AS u2
WHERE u2.Id = u.Id
AND u2.Age IS NULL
) x (Records);
計画は良好に見えますが、読み取りとCPUは改善されていません。
Table 'Users'. Scan count 20, logical reads 17564, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 4844 ms, elapsed time = 863 ms.
クロスアプライを書き直して派生した結合にすると、まったく同じ結果になります。クエリプランと統計情報を再投稿するつもりはありません。実際には変更されていません。
SELECT COUNT(u.Id)
FROM dbo.Users AS u
JOIN
(
SELECT u.Id
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT u.Id
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x ON x.Id = u.Id;
リレーショナル代数:徹底的に、そしてジョー・セルコが私の夢を忘れないようにするために、少なくともいくつかの奇妙なリレーショナルのものを試す必要があります。ここに何もありません!
との試み INTERSECT
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18
INTERSECT
SELECT u.Age WHERE u.Age IS NOT NULL );
Table 'Users'. Scan count 1, logical reads 9157, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 1094 ms, elapsed time = 1090 ms.
そして、ここでの試みです EXCEPT
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18
EXCEPT
SELECT u.Age WHERE u.Age IS NULL);
Table 'Users'. Scan count 7, logical reads 9247, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 2126 ms, elapsed time = 376 ms.
そこにこれらを記述する他の方法かもしれませんが、私は多分使う人にそれを任せるEXCEPT
と、INTERSECT
より頻繁に私よりも。
カウントが必要な場合COUNT
は、クエリでちょっとした省略形として
使用します(読むのが面倒なので、より複雑なシナリオを思い付くことがあります)。カウントが必要な場合は、CASE
式を使用してほぼ同じことを行うことができます。
SELECT SUM(CASE WHEN u.Age < 18 THEN 1
WHEN u.Age IS NULL THEN 1
ELSE 0 END)
FROM dbo.Users AS u
SELECT SUM(CASE WHEN u.Age < 18 OR u.Age IS NULL THEN 1
ELSE 0 END)
FROM dbo.Users AS u
これらは両方とも同じプランを取得し、同じCPUと読み取り特性を持っています。
Table 'Users'. Scan count 1, logical reads 9157, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 719 ms, elapsed time = 719 ms.
勝者?
私のテストでは、派生テーブルに対するSUMを使用した強制並列プランが最高のパフォーマンスを発揮しました。ええ、これらのクエリの多くは、両方の述部を説明するためにいくつかのフィルター選択されたインデックスを追加することで支援できたかもしれませんが、私はいくつかの実験を他に任せたいと思いました。
SELECT SUM(Records)
FROM
(
SELECT 1
FROM dbo.Users AS u
WHERE u.Age < 18
UNION ALL
SELECT 1
FROM dbo.Users AS u
WHERE u.Age IS NULL
) x (Records)
OPTION(QUERYTRACEON 8649);
ありがとう!
NOT EXISTS ( INTERSECT / EXCEPT )
クエリはせずに動作することができますINTERSECT / EXCEPT
部品:WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18 );
もう一つの方法-使用していますEXCEPT
:SELECT COUNT(*) FROM (SELECT UserID FROM dbo.Users EXCEPT SELECT UserID FROM dbo.Users WHERE u.Age >= 18) AS u ;
(ユーザーIDがPKまたは任意の一意のNOT NULL列(S)です)。