Il modo più efficiente per recuperare intervalli di date


16

Qual è il modo più efficiente per recuperare intervalli di date con una struttura di tabella come questa?

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

Di 'che vuoi un intervallo per entrambi StartDatee EndDate. Quindi, in altre parole, se StartDatecade tra @StartDateBegine @StartDateEnd, e EndDatecade tra @EndDateBegine @EndDateEnd, quindi fai qualcosa.

So che ci sono alcuni modi per farlo, ma qual è il più consigliato?

Risposte:


29

Questo è un problema difficile da risolvere in generale, ma ci sono un paio di cose che possiamo fare per aiutare l'ottimizzatore a scegliere un piano. Questo script crea una tabella con 10.000 righe con una distribuzione pseudo-casuale nota di righe per illustrare:

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 prima domanda è come indicizzare questa tabella. Un'opzione è fornire due indici sulle DATETIMEcolonne, in modo che l'ottimizzatore possa almeno scegliere se cercare StartDateo EndDate.

CREATE INDEX nc1 ON dbo.SomeDateTable (StartDate, EndDate)
CREATE INDEX nc2 ON dbo.SomeDateTable (EndDate, StartDate)

Naturalmente, le disuguaglianze su entrambi StartDatee EndDatesignificano che solo una colonna in ciascun indice può supportare una ricerca nella query di esempio, ma si tratta del meglio che possiamo fare. Potremmo considerare di rendere la seconda colonna in ciascun indice INCLUDEuna chiave anziché una chiave, ma potremmo avere altre query che possono eseguire una ricerca di uguaglianza sulla colonna principale e una ricerca di disuguaglianza sulla seconda colonna. Inoltre, possiamo ottenere statistiche migliori in questo modo. Comunque...

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

Questa query utilizza variabili, quindi in generale l'ottimizzatore indovina la selettività e la distribuzione, ottenendo una stima della cardinalità indovinata di 81 righe . In effetti, la query produce 2076 righe, una discrepanza che potrebbe essere importante in un esempio più complesso.

Su SQL Server 2008 SP1 CU5 o versione successiva (o R2 RTM CU1) possiamo sfruttare l' ottimizzazione dell'incorporamento dei parametri per ottenere stime migliori, semplicemente aggiungendoOPTION (RECOMPILE) alla SELECTquery sopra. Ciò provoca una compilazione appena prima dell'esecuzione del batch, consentendo a SQL Server di "vedere" i valori dei parametri reali e di ottimizzarli. Con questa modifica, la stima migliora a 468 righe (sebbene sia necessario controllare il piano di runtime per vederlo). Questa stima è migliore di 81 righe, ma non è ancora così vicina. Le estensioni di modellazione abilitate da flag di traccia 2301 possono aiutare in alcuni casi, ma non con questa query.

Il problema è che le righe qualificate dalle ricerche di due intervalli si sovrappongono. Una delle ipotesi semplificative fatte nella componente di stima dei costi e della cardinalità dell'ottimizzatore è che i predicati sono indipendenti (quindi se entrambi hanno una selettività del 50%, si presume che il risultato dell'applicazione di entrambi si qualifichi il 50% del 50% = 25% delle righe ). Laddove questo tipo di correlazione è un problema, possiamo spesso aggirare il problema con statistiche multi-colonna e / o filtrate. Con due intervalli con punti di inizio e fine sconosciuti, ciò diventa impraticabile. È qui che a volte dobbiamo ricorrere alla riscrittura della query in un modulo che produce una stima migliore:

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)

Questo modulo produce una stima di runtime di 2110 righe (rispetto al 2076 effettivo). A meno che tu non abbia TF 2301 acceso, nel qual caso le tecniche di modellazione più avanzate vedono attraverso il trucco e producono esattamente la stessa stima di prima: 468 righe.

Un giorno SQL Server potrebbe ottenere il supporto nativo per gli intervalli. Se questo viene fornito con un buon supporto statistico, gli sviluppatori potrebbero temere un po 'meno l'ottimizzazione dei piani di query come questa.


5

Non conosco una soluzione rapida per tutte le distribuzioni di dati, ma se tutti gli intervalli sono brevi, di solito possiamo accelerarlo. Se, ad esempio, gli intervalli sono più brevi di un giorno, anziché questa query:

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

possiamo aggiungere un'altra condizione:

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

Di conseguenza, anziché eseguire la scansione dell'intera tabella, la query eseguirà la scansione dell'intervallo di soli due giorni, che è più veloce. Se gli intervalli possono essere più lunghi, possiamo memorizzarli come sequenze di intervalli più brevi. Dettagli qui: Ottimizzare le query SQL con l'aiuto dei vincoli

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.