J'ai testé sur SQL Server 2014 avec l'héritage CE et je n'ai pas non plus obtenu 9% d'estimation de cardinalité. Je n'ai rien trouvé de précis en ligne, j'ai donc fait des tests et j'ai trouvé un modèle qui convient à tous les cas de test que j'ai essayés, mais je ne peux pas être sûr qu'il est complet.
Dans le modèle que j'ai trouvé, l'estimation est dérivée du nombre de lignes du tableau, de la longueur de clé moyenne des statistiques de la colonne filtrée et parfois de la longueur du type de données de la colonne filtrée. Il existe deux formules différentes utilisées pour l'estimation.
Si FLOOR (longueur de clé moyenne) = 0, la formule d'estimation ignore les statistiques de la colonne et crée une estimation basée sur la longueur du type de données. Je n'ai testé qu'avec VARCHAR (N), il est donc possible qu'il existe une formule différente pour NVARCHAR (N). Voici la formule pour VARCHAR (N):
(estimation des lignes) = (lignes du tableau) * (-0,004869 + 0,032649 * log10 (longueur du type de données))
Cela a un très bon ajustement, mais ce n'est pas parfaitement précis:
L'axe des x est la longueur du type de données et l'axe des y est le nombre de lignes estimées pour une table avec 1 million de lignes.
L'optimiseur de requêtes utiliserait cette formule si vous n'aviez pas de statistiques sur la colonne ou si la colonne a suffisamment de valeurs NULL pour conduire la longueur de clé moyenne en dessous de 1.
Par exemple, supposons que vous disposiez d'une table avec 150 000 lignes avec filtrage sur un VARCHAR (50) et aucune statistique de colonne. La prédiction de l'estimation de ligne est:
150000 * (-0,004869 + 0,032649 * log10 (50)) = 7590,1 lignes
SQL pour le tester:
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 donne un nombre de lignes estimé à 7242,47, ce qui est un peu proche.
Si FLOOR (longueur de clé moyenne)> = 1, une formule différente est utilisée, basée sur la valeur de FLOOR (longueur de clé moyenne). Voici un tableau de certaines des valeurs que j'ai essayées:
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%
Si FLOOR (longueur de clé moyenne) <6, utilisez le tableau ci-dessus. Sinon, utilisez l'équation suivante:
(estimation des lignes) = (lignes du tableau) * (-0,003381 + 0,034539 * log10 (FLOOR (longueur de clé moyenne)))
Celui-ci a un meilleur ajustement que l'autre, mais il n'est toujours pas parfaitement précis.
L'axe des x est la longueur de clé moyenne et l'axe des y est le nombre de lignes estimées pour une table avec 1 million de lignes.
Pour donner un autre exemple, supposons que vous disposiez d'un tableau de 10 000 lignes avec une longueur de clé moyenne de 5,5 pour les statistiques de la colonne filtrée. L'estimation de la ligne serait:
10000 * 0,241416 = 241,416 lignes.
SQL pour le tester:
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;
L'estimation de la ligne est 241.416, ce qui correspond à ce que vous avez dans la question. Il y aurait une erreur si j'utilisais une valeur qui n'était pas dans le tableau.
Les modèles ici ne sont pas parfaits mais je pense qu'ils illustrent assez bien le comportement général.