従来のCEを使用してSQL Server 2014でテストしたところ、カーディナリティの推定値として9%も得られませんでした。オンラインで正確なものを見つけることができなかったので、いくつかのテストを行い、試したすべてのテストケースに適合するモデルを見つけましたが、完全であることを確認することはできません。
私が見つけたモデルでは、推定値はテーブル内の行数、フィルター処理された列の統計の平均キー長、および場合によってはフィルター処理された列のデータ型の長さから導出されます。推定に使用される2つの異なる式があります。
FLOOR(平均キー長)= 0の場合、推定式は列統計を無視し、データ型の長さに基づいて推定を作成します。VARCHAR(N)でのみテストしたため、NVARCHAR(N)には異なる式がある可能性があります。VARCHAR(N)の式は次のとおりです。
(行の推定値)=(表の行)*(-0.004869 + 0.032649 * log10(データ型の長さ))
これは非常にうまくフィットしますが、完全に正確ではありません。
x軸はデータ型の長さ、y軸は100万行のテーブルの推定行数です。
クエリオプティマイザーは、列の統計情報がない場合、または列の平均キー長を1未満にするのに十分なNULL値がある場合、この式を使用します。
たとえば、VARCHAR(50)でフィルタリングを行い、列統計を持たない150k行のテーブルがあると仮定します。行推定の予測は次のとおりです。
150000 *(-0.004869 + 0.032649 * log10(50))= 7590.1行
テストするSQL:
CREATE TABLE X_CE_LIKE_TEST_1 (
STRING VARCHAR(50)
);
CREATE STATISTICS X_STAT_CE_LIKE_TEST_1 ON X_CE_LIKE_TEST_1 (STRING) WITH NORECOMPUTE;
WITH
L0 AS (SELECT 1 AS c UNION ALL SELECT 1),
L1 AS (SELECT 1 AS c FROM L0 A CROSS JOIN L0 B),
L2 AS (SELECT 1 AS c FROM L1 A CROSS JOIN L1 B),
L3 AS (SELECT 1 AS c FROM L2 A CROSS JOIN L2 B),
L4 AS (SELECT 1 AS c FROM L3 A CROSS JOIN L3 B CROSS JOIN L2 C),
NUMS AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS NUM FROM L4)
INSERT INTO X_CE_LIKE_TEST_1 WITH (TABLOCK) (STRING)
SELECT TOP (150000) 'ZZZZZ'
FROM NUMS
ORDER BY NUM;
DECLARE @LastName VARCHAR(15) = 'BA%'
SELECT * FROM X_CE_LIKE_TEST_1
WHERE STRING LIKE @LastName;
SQL Serverは7242.47の推定行カウントを提供しますが、これは近い値です。
FLOOR(平均キー長)> = 1の場合、FLOOR(平均キー長)の値に基づく異なる式が使用されます。ここに私が試したいくつかの値の表があります:
1 1.5%
2 1.5%
3 1.64792%
4 2.07944%
5 2.41416%
6 2.68744%
7 2.91887%
8 3.11916%
9 3.29584%
10 3.45388%
FLOOR(平均キー長)<6の場合、上記の表を使用します。それ以外の場合は、次の式を使用します。
(行の見積もり)=(表の行)*(-0.003381 + 0.034539 * log10(FLOOR(平均キー長)))
これは他のものよりもフィット感が優れていますが、それでも完全に正確ではありません。
x軸は平均キー長で、y軸は100万行のテーブルの推定行数です。
別の例を挙げると、フィルター処理された列の統計用に平均キー長が5.5の1万行のテーブルがあると仮定します。行の見積もりは次のようになります。
10000 * 0.241416 = 241.416行。
テストするSQL:
CREATE TABLE X_CE_LIKE_TEST_2 (
STRING VARCHAR(50)
);
WITH
L0 AS (SELECT 1 AS c UNION ALL SELECT 1),
L1 AS (SELECT 1 AS c FROM L0 A CROSS JOIN L0 B),
L2 AS (SELECT 1 AS c FROM L1 A CROSS JOIN L1 B),
L3 AS (SELECT 1 AS c FROM L2 A CROSS JOIN L2 B),
L4 AS (SELECT 1 AS c FROM L3 A CROSS JOIN L3 B CROSS JOIN L2 C),
NUMS AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS NUM FROM L4)
INSERT INTO X_CE_LIKE_TEST_2 WITH (TABLOCK) (STRING)
SELECT TOP (10000)
CASE
WHEN NUM % 2 = 1 THEN REPLICATE('Z', 5)
ELSE REPLICATE('Z', 6)
END
FROM NUMS
ORDER BY NUM;
CREATE STATISTICS X_STAT_CE_LIKE_TEST_2 ON X_CE_LIKE_TEST_2 (STRING)
WITH NORECOMPUTE, FULLSCAN;
DECLARE @LastName VARCHAR(15) = 'BA%'
SELECT * FROM X_CE_LIKE_TEST_2
WHERE STRING LIKE @LastName;
行の見積もりは241.416で、質問の内容と一致します。テーブルにない値を使用すると、エラーが発生します。
ここのモデルは完全ではありませんが、一般的な動作をかなりよく示していると思います。