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 DATETIME
colunas, para que o otimizador possa pelo menos escolher entre procurar StartDate
ou EndDate
.
CREATE INDEX nc1 ON dbo.SomeDateTable (StartDate, EndDate)
CREATE INDEX nc2 ON dbo.SomeDateTable (EndDate, StartDate)
Naturalmente, as desigualdades em ambos StartDate
e EndDate
significam 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 INCLUDE
chave 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)
à SELECT
consulta 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.