リテラル値のみを使用するWHERE句でISNULL()を置き換える別の方法は何ですか?


55

これが何でないか:

これは、ユーザー入力を受け入れるか、変数を使用するキャッチオールクエリに関する質問ではありません。

これは、厳密に述語と比較するために値をカナリア値に置き換える句でISNULL()使用されるクエリと、それらのクエリをSQL Server でSARGableに書き換えるさまざまな方法に関するものです。WHERENULL

向こうに席がありませんか?

このクエリ例は、SQL Server 2016のStack Overflowデータベースのローカルコピーに対するもので、NULL年齢または18歳未満のユーザーを探します。

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE ISNULL(u.Age, 17) < 18;

クエリプランは、非常に思慮深い非クラスター化インデックスのスキャンを示しています。

ナッツ

スキャン演算子は、SQL Serverの最新バージョンの実際の実行計画XMLへの追加のおかげで、すべてのスティンキン行を読み取ったことを示します。

ナッツ

全体として、9157の読み取りを行い、約0.5秒の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 = 485 ms,  elapsed time = 483 ms.

質問: このクエリをより効率的に、そしておそらくSARGableにするために書き換える方法は何ですか?

他の提案をお気軽にご提供ください。私は私の答えは必ずしもあるとは思わない答え、そしてそこに十分にスマート人々は良いかもしれ代替案を考え出すためにそこにあります。

自分のコンピューターで一緒にプレイしたい場合は、SOデータベースダウンロードしてください。

ありがとう!

回答:


57

回答セクション

さまざまな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);

ありがとう!


1
NOT EXISTS ( INTERSECT / EXCEPT )クエリはせずに動作することができますINTERSECT / EXCEPT部品:WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18 );もう一つの方法-使用していますEXCEPTSELECT COUNT(*) FROM (SELECT UserID FROM dbo.Users EXCEPT SELECT UserID FROM dbo.Users WHERE u.Age >= 18) AS u ; (ユーザーIDがPKまたは任意の一意のNOT NULL列(S)です)。
ypercubeᵀᴹ

これはテストされましたか?SELECT result = (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age < 18) + (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age IS NULL) ;テストした数百万のバージョンを見逃してしまった場合は申し訳ありません!
ypercubeᵀᴹ

@ypercubeᵀᴹ 、そのための計画です。少し異なりますが、UNION ALL計画と同様の特性があります(360ms CPU、11k読み取り)。
エリックダーリン

ちょっとエリック、ちょうどsqlの世界を歩き回っていて、単にあなたを困らせるために「計算された列」と言うために飛び込みました。<3
るつぼ

17

110 GBのデータベースを1つのテーブルだけで復元するゲームではなかったので、独自のデータを作成しました。年齢分布はStack Overflowにあるものと一致する必要がありますが、明らかにテーブル自体は一致しません。クエリがとにかくインデックスにヒットするため、これが問題になるとは思いません。SQL Server 2016 SP1を搭載した4 CPUコンピューターでテストしています。注意すべきことの1つは、これをすぐに終了するクエリでは、実際の実行プランを含めないことが重要です。それは物事をかなり遅くすることができます。

私は、エリックの優れた答えのいくつかの解決策を検討することから始めました。これについて:

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);

sys.dm_exec_sessionsから10回の試行をて、次の結果が得られました(クエリは自然に並行しました)。

╔══════════╦════════════════════╦═══════════════╗
 cpu_time  total_elapsed_time  logical_reads 
╠══════════╬════════════════════╬═══════════════╣
     3532                 975          60830 
╚══════════╩════════════════════╩═══════════════╝

Erikの方がうまく機能したクエリは、実際には私のマシンではパフォーマンスが低下しました。

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);

10回の試行の結果:

╔══════════╦════════════════════╦═══════════════╗
 cpu_time  total_elapsed_time  logical_reads 
╠══════════╬════════════════════╬═══════════════╣
     5704                1636          60850 
╚══════════╩════════════════════╩═══════════════╝

なぜそんなに悪いのかすぐに説明することはできませんが、クエリプランのほぼすべての演算子を強制的に並列にしたい理由は明確ではありません。元の計画では、すべての行を検索するシリアルゾーンがありますAGE < 18。数千行しかない。私のマシンでは、クエリのその部分に対して9回の論理読み取りが行われ、9ミリ秒のCPU時間と経過時間が報告されます。行のグローバル集計のシリアルゾーンもありますAGE IS NULLが、DOPごとに1行しか処理しません。私のマシンでは、これはわずか4行です。

私の要点は、何百万もの行があるNULLため、forで行を見つけるクエリの部分を最適化することが最も重要だということAgeです。列にある単純なページ圧縮ページよりも、データをカバーするページが少ないインデックスを作成することはできませんでした。行ごとに最小のインデックスサイズがあるか、試行したトリックでは多くのインデックススペースを回避できないと思います。したがって、データを取得するためにほぼ同じ数の論理読み取りに固執している場合、それを高速化する唯一の方法はクエリをより並列にすることですが、これはTFを使用したErikのクエリとは異なる方法で行う必要があります8649.上記のクエリでは、CPU時間と経過時間の比率が3.62であり、これはかなり良好です。理想は、私のマシンの4.0の比率です。

改善の可能性のある領域の1つは、スレッド間で作業をより均等に分割することです。以下のスクリーンショットでは、CPUの1つが少し休憩することにしたことがわかります。

怠threadなスレッド

インデックススキャンは、並列に実装できる数少ない演算子の1つであり、行をスレッドに分散する方法については何もできません。チャンスの要素もありますが、かなり一貫して、1つの作業不足のスレッドを見ました。これを回避する方法の1つは、ネストされたループ結合の内部で並列処理を行うことです。ネストされたループの内側の部分はすべてシリアルに実装されますが、多くのシリアルスレッドは同時に実行できます。有利な並列分散方法(ラウンドロビンなど)が得られる限り、各スレッドに送信される行数を正確に制御できます。

DOP 4でクエリを実行しているためNULL、テーブル内の行を4つのバケットに均等に分割する必要があります。これを行う1つの方法は、計算列に一連のインデックスを作成することです。

ALTER TABLE dbo.Users
ADD Compute_bucket_0 AS (CASE WHEN Age IS NULL AND Id % 4 = 0 THEN 1 ELSE NULL END),
Compute_bucket_1 AS (CASE WHEN Age IS NULL AND Id % 4 = 1 THEN 1 ELSE NULL END),
Compute_bucket_2 AS (CASE WHEN Age IS NULL AND Id % 4 = 2 THEN 1 ELSE NULL END),
Compute_bucket_3 AS (CASE WHEN Age IS NULL AND Id % 4 = 3 THEN 1 ELSE NULL END);

CREATE INDEX IX_Compute_bucket_0 ON dbo.Users (Compute_bucket_0) WITH (DATA_COMPRESSION = PAGE);
CREATE INDEX IX_Compute_bucket_1 ON dbo.Users (Compute_bucket_1) WITH (DATA_COMPRESSION = PAGE);
CREATE INDEX IX_Compute_bucket_2 ON dbo.Users (Compute_bucket_2) WITH (DATA_COMPRESSION = PAGE);
CREATE INDEX IX_Compute_bucket_3 ON dbo.Users (Compute_bucket_3) WITH (DATA_COMPRESSION = PAGE);

なぜ4つの別々のインデックスが1つのインデックスよりもやや速いのかはよくわかりませんが、テストで見つけたものです。

並列ネストループプランを取得するには、ドキュメント化されていないトレースフラグ8649を使用します。また、オプティマイザーが必要以上の行を処理しないようにするために、少し奇妙にコードを記述します。以下は、うまくいくように見える1つの実装です。

SELECT SUM(t.cnt) + (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age < 18)
FROM 
(VALUES (0), (1), (2), (3)) v(x)
CROSS APPLY 
(
    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_0 = CASE WHEN v.x = 0 THEN 1 ELSE NULL END

    UNION ALL

    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_1 = CASE WHEN v.x = 1 THEN 1 ELSE NULL END

    UNION ALL

    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_2 = CASE WHEN v.x = 2 THEN 1 ELSE NULL END

    UNION ALL

    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_3 = CASE WHEN v.x = 3 THEN 1 ELSE NULL END
) t
OPTION (QUERYTRACEON 8649);

10回の試行の結果:

╔══════════╦════════════════════╦═══════════════╗
 cpu_time  total_elapsed_time  logical_reads 
╠══════════╬════════════════════╬═══════════════╣
     3093                 803          62008 
╚══════════╩════════════════════╩═══════════════╝

このクエリでは、CPUと経過時間の比率は3.85です!ランタイムを17ミリ秒短縮し、計算に必要な計算列とインデックスは4つだけになりました!各インデックスは同じ行数に非常に近く、各スレッドは1つのインデックスのみをスキャンするため、各スレッドは全体的に同じ行数に非常に近い処理を行います。

よく分かれた仕事

最後に、簡単なボタンを押して、クラスター化されていないCCIをAge列に追加することもできます。

CREATE NONCLUSTERED COLUMNSTORE INDEX X_NCCI ON dbo.Users (Age);

私のマシンでは、次のクエリが3ミリ秒で終了します。

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18 OR u.Age IS NULL;

それは打ちにくいです。


7

Stack Overflowデータベースのローカルコピーはありませんが、いくつかのクエリを試すことができました。私の考えは、システムカタログビューからユーザーの数を取得することでした(基礎となるテーブルから行の数を直接取得するのではなく)。次に、Erikの条件に一致する(または一致しない)行の数を取得し、簡単な計算を行います。

私が使用スタックExchangeデータエクスプローラ(と一緒にSET STATISTICS TIME ON;とのSET STATISTICS IO ON;クエリをテストします)。参考のために、いくつかのクエリとCPU / IO統計を以下に示します。

クエリ1

--Erik's query From initial question.
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE ISNULL(u.Age, 17) < 18;

SQL Serverの実行時間:CPU時間= 0ミリ秒、経過時間= 0ミリ秒。(1行が返されました)

テーブル「ユーザー」。スキャンカウント17、論理読み取り201567、物理読み取り0、先読み読み取り2740、lob論理読み取り0、lob物理読み取り0、lob先読み0。

SQL Serverの実行時間:CPU時間= 1829ミリ秒、経過時間= 296ミリ秒。

クエリ2

--Erik's "OR" query.
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18
OR u.Age IS NULL;

SQL Serverの実行時間:CPU時間= 0ミリ秒、経過時間= 0ミリ秒。(1行が返されました)

テーブル「ユーザー」。スキャン数17、論理読み取り201567、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。

SQL Serverの実行時間:CPU時間= 2500ミリ秒、経過時間= 147ミリ秒。

クエリ3

--Erik's derived tables/UNION ALL query.
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);

SQL Serverの実行時間:CPU時間= 0ミリ秒、経過時間= 0ミリ秒。(1行が返されました)

テーブル「ユーザー」。スキャン数34、論理読み取り403134、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。

SQL Serverの実行時間:CPU時間= 3156ミリ秒、経過時間= 215ミリ秒。

最初の試み

これは、少なくとも経過時間に関しては、ここにリストしたErikのすべてのクエリよりも遅くなりました。

SELECT SUM(p.Rows)  -
  (
    SELECT COUNT(*)
    FROM dbo.Users AS u
    WHERE u.Age >= 18
  ) 
FROM sys.objects o
JOIN sys.partitions p
    ON p.object_id = o.object_id
WHERE p.index_id < 2
AND o.name = 'Users'
AND SCHEMA_NAME(o.schema_id) = 'dbo'
GROUP BY o.schema_id, o.name

SQL Serverの実行時間:CPU時間= 0ミリ秒、経過時間= 0ミリ秒。(1行が返されました)

テーブル「ワークテーブル」。スキャンカウント0、論理読み取り0、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み0。テーブル 'sysrowsets'。スキャンカウント2、論理読み取り10、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み0。テーブル 'sysschobjs'。スキャンカウント1、論理読み取り4、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。表「ユーザー」。スキャン数1、論理読み取り201567、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。

SQL Serverの実行時間:CPU時間= 593ミリ秒、経過時間= 598ミリ秒。

2回目

ここでは、(サブクエリではなく)ユーザーの総数を格納する変数を選択しました。スキャン回数は、1回目の試行と比較して1から17に増加しました。論理読み取りは同じままでした。ただし、経過時間は大幅に減少しました。

DECLARE @Total INT;

SELECT @Total = SUM(p.Rows)
FROM sys.objects o
JOIN sys.partitions p
    ON p.object_id = o.object_id
WHERE p.index_id < 2
AND o.name = 'Users'
AND SCHEMA_NAME(o.schema_id) = 'dbo'
GROUP BY o.schema_id, o.name

SELECT @Total - COUNT(*)
FROM dbo.Users AS u
WHERE u.Age >= 18

SQL Serverの実行時間:CPU時間= 0ミリ秒、経過時間= 0ミリ秒。テーブル「ワークテーブル」。スキャンカウント0、論理読み取り0、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み0。テーブル 'sysrowsets'。スキャンカウント2、論理読み取り10、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み0。テーブル 'sysschobjs'。スキャンカウント1、論理読み取り4、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0

SQL Serverの実行時間:CPU時間= 0ミリ秒、経過時間= 1ミリ秒。(1行が返されました)

テーブル「ユーザー」。スキャン数17、論理読み取り201567、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。

SQL Serverの実行時間:CPU時間= 1471ミリ秒、経過時間= 98ミリ秒。

その他の注意: 以下に示すように、DBCC TRACEONはStack Exchange Data Explorerでは許可されていません。

ユーザー 'STACKEXCHANGE \ svc_sede'には、DBCC TRACEONを実行する権限がありません。


1
それらはおそらく私と同じインデックスを持っていないため、違いがあります。そして、誰が知っていますか?たぶん私のホームサーバーはより良いハードウェア上にあるかもしれません;)すばらしい答えです!
エリックダーリン

最初の試行で次のクエリを使用する必要がありました(sys.objectsオーバーヘッドの多くを取り除くため、はるかに高速になります): SELECT SUM(p.Rows) - (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age >= 18 ) FROM sys.partitions p WHERE p.index_id < 2 AND p.object_id = OBJECT_ID('dbo.Users')
Thomas Franz

PSは:インメモリ・インデックス(NONCLUSTEREDハッシュ)インデックスID = 0/1を有していないことに注意して、共通のヒープ/クラスタ化インデックスを持っているように)
トーマスフランツ

1

変数を使用しますか?

declare @int1 int = ( select count(*) from table_1 where bb <= 1 )
declare @int2 int = ( select count(*) from table_1 where bb is null )
select @int1 + @int2;

コメントごとに変数をスキップできます

SELECT (select count(*) from table_1 where bb <= 1) 
     + (select count(*) from table_1 where bb is null);

3
また、:SELECT (select count(*) from table_1 where bb <= 1) + (select count(*) from table_1 where bb is null);
ypercubeᵀᴹ

3
CPUとIOをチェックしながら、それを試してみたいかもしれません。ヒント:エリックの答えの1つと同じです。
ブレントオザー

0

よく使う SET ANSI_NULLS OFF;

SET ANSI_NULLS OFF; 
SET STATISTICS TIME ON;
SET STATISTICS IO ON;

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE age=NULL or age<18

Table 'Users'. Scan count 17, logical reads 201567

 SQL Server Execution Times:
 CPU time = 2344 ms,  elapsed time = 166 ms.

これはちょうど私の頭に浮かんだものです。ちょうどhttps://data.stackexchange.comでこれを実行しました

しかし、@ blitz_erikほど効率的ではありません


0

簡単な解決策は、count(*)-count(年齢> = 18)を計算することです:

SELECT
    (SELECT COUNT(*) FROM Users) -
    (SELECT COUNT(*) FROM Users WHERE Age >= 18);

または:

SELECT COUNT(*)
     - COUNT(CASE WHEN Age >= 18)
FROM Users;

結果はこちら

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.