Le moyen le plus efficace de récupérer des plages de dates

16

Quelle est la manière la plus efficace de récupérer des plages de dates avec une structure de table comme celle-ci?

create table SomeDateTable
(
    id int identity(1, 1) not null,
    StartDate datetime not null,
    EndDate datetime not null
)
go

Disons que vous voulez une plage pour StartDateet EndDate. En d'autres termes, si se StartDatesitue entre @StartDateBeginet @StartDateEnd, et se EndDatesitue entre @EndDateBeginet @EndDateEnd, alors faites quelque chose.

Je sais qu'il y a plusieurs façons de s'y prendre, mais quel est le plus conseillé?

Thomas Stringer
la source

Réponses:

29

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 DATETIMEcolonnes, afin que l'optimiseur puisse au moins choisir de rechercher sur StartDateou 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 StartDateet EndDatemoyenne 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 INCLUDEclé 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 SELECTrequê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.

Paul White réintègre Monica
la source
5

Je ne connais pas de solution rapide pour toutes les distributions de données, mais si toutes vos plages sont courtes, nous pouvons généralement l'accélérer. Si, par exemple, les plages sont inférieures à un jour, au lieu de cette requête:

SELECT  TaskId ,    
        TaskDescription ,
        StartedAt ,    
        FinishedAt    
FROM    dbo.Tasks    
WHERE   '20101203' BETWEEN StartedAt AND FinishedAt

nous pouvons ajouter une autre condition:

SELECT  TaskId ,    
        TaskDescription ,
        StartedAt ,    
        FinishedAt    
FROM    dbo.Tasks    
WHERE   '20101203' BETWEEN StartedAt AND FinishedAt
    AND StartedAt >= '20101202'
    AND FinishedAt <= '20101204' ;

Par conséquent, au lieu d'analyser l'intégralité de la table, la requête analysera uniquement la plage de deux jours, ce qui est plus rapide. Si les plages peuvent être plus longues, nous pouvons les stocker sous forme de séquences de plus courtes. Détails ici: réglage des requêtes SQL à l'aide de contraintes

AK
la source