A maneira mais eficiente de recuperar intervalos de datas

16

Qual é a maneira mais eficiente de recuperar períodos com uma estrutura de tabela como esta?

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

Digamos que você queira um intervalo para ambos StartDatee EndDate. Então, em outras palavras, se StartDatecai entre @StartDateBegine @StartDateEnd, e EndDatecai entre @EndDateBegine @EndDateEnd, então faça alguma coisa.

Eu sei que existem algumas maneiras de provavelmente fazer isso, mas qual é o mais recomendado?

Thomas Stringer
fonte

Respostas:

29

Esse é um problema difícil de resolver em geral, mas há algumas coisas que podemos fazer para ajudar o otimizador a escolher um plano. Este script cria uma tabela com 10.000 linhas com uma distribuição pseudo-aleatória conhecida de linhas para ilustrar:

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

A primeira pergunta é como indexar esta tabela. Uma opção é fornecer dois índices nas DATETIMEcolunas, para que o otimizador possa pelo menos escolher entre procurar StartDateou EndDate.

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

Naturalmente, as desigualdades em ambos StartDatee EndDatesignificam que apenas uma coluna em cada índice pode suportar uma busca na consulta de exemplo, mas isso é o melhor que podemos fazer. Podemos considerar tornar a segunda coluna em cada índice uma INCLUDEchave e não uma chave, mas podemos ter outras consultas que podem executar uma busca de igualdade na coluna principal e uma busca de desigualdade na segunda coluna. Além disso, podemos obter melhores estatísticas dessa maneira. De qualquer forma...

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

Essa consulta usa variáveis, portanto, em geral, o otimizador adivinha a seletividade e a distribuição, resultando em uma estimativa de cardinalidade calculada de 81 linhas . De fato, a consulta produz 2076 linhas, uma discrepância que pode ser importante em um exemplo mais complexo.

No SQL Server 2008 SP1 CU5 ou posterior (ou R2 RTM CU1), podemos aproveitar a Otimização de incorporação de parâmetros para obter melhores estimativas, simplesmente adicionando OPTION (RECOMPILE)à SELECTconsulta acima. Isso causa uma compilação imediatamente antes da execução do lote, permitindo que o SQL Server 'veja' os valores reais dos parâmetros e otimize-os. Com essa alteração, a estimativa aumenta para 468 linhas (embora você precise verificar o plano de tempo de execução para ver isso). Essa estimativa é melhor que 81 linhas, mas ainda não está tão perto. As extensões de modelagem ativadas pelo sinalizador de rastreamento 2301 podem ajudar em alguns casos, mas não com esta consulta.

O problema é que as linhas qualificadas pelas duas pesquisas de intervalo se sobrepõem. Uma das suposições simplificadoras feitas no componente de estimativa de custos e cardinalidade do otimizador é que os predicados são independentes (portanto, se ambos tiverem uma seletividade de 50%, presume-se que o resultado da aplicação de ambos qualifique 50% de 50% = 25% das linhas ) Onde esse tipo de correlação é um problema, geralmente podemos contorná-lo com estatísticas de várias colunas e / ou filtradas. Com dois intervalos com pontos iniciais e finais desconhecidos, isso se torna impraticável. É aqui que às vezes precisamos recorrer à reescrita da consulta para um formulário que produza uma estimativa melhor:

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)

Esse formulário produz uma estimativa de tempo de execução de 2110 linhas (versus 2076 real). A menos que você tenha o TF 2301 ativado, nesse caso, as técnicas de modelagem mais avançadas veem o truque e produzem exatamente a mesma estimativa de antes: 468 linhas.

Um dia, o SQL Server poderá obter suporte nativo para intervalos. Se isso vier com um bom suporte estatístico, os desenvolvedores podem temer um pouco menos os planos de consulta de ajuste como este.

Paul White restabelece Monica
fonte
5

Não conheço uma solução rápida para todas as distribuições de dados, mas se todos os seus intervalos forem curtos, geralmente podemos acelerá-la. Se, por exemplo, os intervalos forem menores que um dia, em vez desta consulta:

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

podemos adicionar mais uma condição:

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

Como resultado, em vez de varrer a tabela inteira, a consulta varrerá apenas o intervalo de dois dias, o que é mais rápido. Se os intervalos puderem ser maiores, podemos armazená-los como sequências de intervalos mais curtos. Detalhes aqui: Ajustando consultas SQL com a ajuda de restrições

AK
fonte