C'est un problème difficile à résoudre en général, mais il y a quelques choses que nous pouvons faire pour aider l'optimiseur à choisir un plan. Ce script crée une table avec 10 000 lignes avec une distribution pseudo-aléatoire connue de lignes pour illustrer:
CREATE TABLE dbo.SomeDateTable
(
Id INTEGER IDENTITY(1, 1) PRIMARY KEY NOT NULL,
StartDate DATETIME NOT NULL,
EndDate DATETIME NOT NULL
);
GO
SET STATISTICS XML OFF
SET NOCOUNT ON;
DECLARE
@i INTEGER = 1,
@s FLOAT = RAND(20120104),
@e FLOAT = RAND();
WHILE @i <= 10000
BEGIN
INSERT dbo.SomeDateTable
(
StartDate,
EndDate
)
VALUES
(
DATEADD(DAY, @s * 365, {d '2009-01-01'}),
DATEADD(DAY, @s * 365 + @e * 14, {d '2009-01-01'})
)
SELECT
@s = RAND(),
@e = RAND(),
@i += 1
END
La première question est de savoir comment indexer ce tableau. Une option consiste à fournir deux index sur les DATETIME
colonnes, afin que l'optimiseur puisse au moins choisir de rechercher sur StartDate
ou EndDate
.
CREATE INDEX nc1 ON dbo.SomeDateTable (StartDate, EndDate)
CREATE INDEX nc2 ON dbo.SomeDateTable (EndDate, StartDate)
Bien entendu, les inégalités sur les deux StartDate
et EndDate
moyenne qu'une seule colonne dans chaque index peut prendre en charge une rechercher dans la requête par exemple, mais cela est sur le point le mieux que nous pouvons faire. Nous pourrions envisager de faire de la deuxième colonne de chaque index une INCLUDE
clé plutôt qu'une clé, mais nous pourrions avoir d'autres requêtes qui peuvent effectuer une recherche d'égalité sur la colonne de tête et une recherche d'inégalité sur la deuxième colonne. De plus, nous pouvons obtenir de meilleures statistiques de cette façon. En tous cas...
DECLARE
@StartDateBegin DATETIME = {d '2009-08-01'},
@StartDateEnd DATETIME = {d '2009-10-15'},
@EndDateBegin DATETIME = {d '2009-08-05'},
@EndDateEnd DATETIME = {d '2009-10-22'}
SELECT
COUNT_BIG(*)
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.StartDate BETWEEN @StartDateBegin AND @StartDateEnd
AND sdt.EndDate BETWEEN @EndDateBegin AND @EndDateEnd
Cette requête utilise des variables, donc en général, l’optimiseur devine la sélectivité et la distribution, ce qui donne une estimation de la cardinalité de 81 lignes . En fait, la requête produit 2076 lignes, une différence qui pourrait être importante dans un exemple plus complexe.
Sur SQL Server 2008 SP1 CU5 ou version ultérieure (ou R2 RTM CU1), nous pouvons tirer parti de l' optimisation de l'intégration des paramètres pour obtenir de meilleures estimations, simplement en ajoutant OPTION (RECOMPILE)
à la SELECT
requête ci-dessus. Cela provoque une compilation juste avant l'exécution du lot, permettant à SQL Server de «voir» les valeurs réelles des paramètres et d'optimiser celles-ci. Avec cette modification, l'estimation s'améliore à 468 lignes (bien que vous ayez besoin de vérifier le plan d'exécution pour le voir). Cette estimation est meilleure que 81 lignes, mais toujours pas si proche. Les extensions de modélisation activées par l' indicateur de trace 2301 peuvent aider dans certains cas, mais pas avec cette requête.
Le problème est où les lignes qualifiées par les deux recherches de plage se chevauchent. L'une des hypothèses simplificatrices faites dans la composante d'estimation des coûts et de la cardinalité de l'optimiseur est que les prédicats sont indépendants (donc si les deux ont une sélectivité de 50%, le résultat de l'application des deux est supposé qualifier 50% de 50% = 25% des lignes ). Lorsque ce type de corrélation est un problème, nous pouvons souvent le contourner avec des statistiques multi-colonnes et / ou filtrées. Avec deux plages avec des points de début et de fin inconnus, cela devient impossible. C'est là que nous devons parfois recourir à la réécriture de la requête dans un formulaire qui produit une meilleure estimation:
SELECT COUNT(*) FROM
(
SELECT
sdt.Id
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.StartDate BETWEEN @StartDateBegin AND @StartDateEnd
INTERSECT
SELECT
sdt.Id
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.EndDate BETWEEN @EndDateBegin AND @EndDateEnd
) AS intersected (id)
OPTION (RECOMPILE)
Ce formulaire produit une estimation d'exécution de 2110 lignes (contre 2076 réelles). Sauf si vous avez TF 2301 allumé, auquel cas les techniques de modélisation les plus avancées voient à travers l'astuce et produisent exactement la même estimation qu'auparavant: 468 lignes.
Un jour, SQL Server peut obtenir une prise en charge native des intervalles. Si cela vient avec un bon support statistique, les développeurs pourraient redouter un peu moins les plans de requête de réglage comme celui-ci.