O elenco até hoje é sargável, mas é uma boa ideia?


47

No SQL Server 2008, o tipo de dados da data foi adicionado.

A transmissão de uma datetimecoluna para dateé sargable e pode usar um índice na datetimecoluna.

select *
from T
where cast(DateTimeCol as date) = '20130101';

A outra opção que você tem é usar um intervalo.

select *
from T
where DateTimeCol >= '20130101' and
      DateTimeCol < '20130102'

Essas consultas são igualmente boas ou uma deve ser preferida à outra?


4
O que diz o plano de execução?
A_horse_with_no_name

3
Não posso deixar de notar que o LINQ2SQL gera SQL where cast(date_column as date) = 'value'quando apresentado com C # semelhante a where obj.date_column.Date == date_variable.
GSerg

6
Esse é um excelente item do Connect. :)
Rob Farley

1
O site do Connect foi removido e também Sargable na Wikipedia
Ivanzinho

Respostas:


59

O mecanismo por trás da sargabilidade da transmissão até o momento é chamado busca dinâmica .

O SQL Server chama uma função interna GetRangeThroughConvertpara obter o início e o fim do intervalo.

Surpreendentemente, esse não é o mesmo intervalo que seus valores literais.

Criando uma tabela com uma linha por página e 1440 linhas por dia

CREATE TABLE T
  (
     DateTimeCol DATETIME PRIMARY KEY,
     Filler      CHAR(8000) DEFAULT 'X'
  );

WITH Nums(Num)
     AS (SELECT number
         FROM   spt_values
         WHERE  type = 'P'
                AND number BETWEEN 1 AND 1440),
     Dates(Date)
     AS (SELECT {d '2012-12-30'} UNION ALL
         SELECT {d '2012-12-31'} UNION ALL
         SELECT {d '2013-01-01'} UNION ALL
         SELECT {d '2013-01-02'} UNION ALL
         SELECT {d '2013-01-03'})
INSERT INTO T
            (DateTimeCol)
SELECT DISTINCT DATEADD(MINUTE, Num, Date)
FROM   Nums,
       Dates 

Então correndo

SET STATISTICS IO ON;
SET STATISTICS TIME ON;

SELECT *
FROM   T
WHERE  DateTimeCol >= '20130101'
       AND DateTimeCol < '20130102'

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'; 

A primeira consulta possui 1443leituras e a segunda, 2883portanto, está lendo um dia adicional inteiro e descartando-a contra um predicado residual.

O plano mostra que o predicado de busca é

Seek Keys[1]: Start: DateTimeCol > Scalar Operator([Expr1006]), 
               End: DateTimeCol < Scalar Operator([Expr1007])

Então, em vez de >= '20130101' ... < '20130102'ler > '20121231' ... < '20130102', descarta todas as 2012-12-31linhas.

Outra desvantagem de confiar nisso é que as estimativas de cardinalidade podem não ser tão precisas quanto na consulta de intervalo tradicional. Isso pode ser visto em uma versão alterada do seu SQL Fiddle .

Todas as 100 linhas da tabela agora correspondem ao predicado (com datas de 1 minuto, todas no mesmo dia).

A segunda consulta (intervalo) estima corretamente que 100 corresponderá e usa uma verificação de índice em cluster. A CAST( AS DATE)consulta calcula incorretamente que apenas uma linha corresponderá e produzirá um plano com as principais pesquisas.

As estatísticas não são completamente ignoradas. Se todas as linhas da tabela tiverem o mesmo datetimee corresponderem ao predicado (por exemplo, 20130101 00:00:00ou 20130101 01:00:00), o plano mostrará uma varredura de índice em cluster com uma estimativa de 31.6228 linhas.

100 ^ 0.75 = 31.6228

Portanto, nesse caso, parece que a estimativa é derivada da fórmula aqui .

Se todas as linhas da tabela tiverem o mesmo datetimee não corresponderem ao predicado (por exemplo 20130102 01:00:00), ele retornará à contagem de linhas estimada de 1 e ao plano com pesquisas.

Nos casos em que a tabela possui mais de um DISTINCTvalor, as linhas estimadas parecem iguais, como se a consulta estivesse procurando exatamente 20130101 00:00:00.

Se ocorrer um passo no histograma estatístico 2013-01-01 00:00:00.000, a estimativa será baseada no EQ_ROWS(ou seja, não levando em consideração outros momentos nessa data). Caso contrário, se não houver etapa, parece que ela usa as AVG_RANGE_ROWSetapas anteriores.

Como datetimetem uma precisão de aproximadamente 3ms em muitos sistemas, haverá muito poucos valores duplicados reais e esse número será 1.


1
Olá Martin, você poderia adicionar uma TL;DRparte com alguns pontos de bala com casos diferentes, adicionando se, nesse caso, o elenco até hoje é uma boa ideia ou não?
TT.

6
@TT. Eu acho que o ponto é que não é uma boa ideia. Por que você deseja usar um método que requer uma folha de dicas?
Aaron Bertrand

10

Eu sei que isso tem uma excelente resposta de longa data de Martin, mas eu queria adicionar algumas alterações no comportamento aqui nas versões mais recentes do SQL Server. Parece que apenas foi testado até 2008R2.

Com as novas DICAS DE USO que tornam possível realizar alguma viagem no tempo com estimativa de cardinalidade, podemos ver quando as coisas mudaram.

Usando a mesma configuração do SQL Fiddle.

CREATE TABLE T ( ID INT IDENTITY PRIMARY KEY, DateTimeCol DATETIME, Filler CHAR(8000) NULL );

CREATE INDEX IX_T_DateTimeCol ON T ( DateTimeCol );


WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
     E02(N) AS (SELECT 1 FROM E00 a, E00 b),
     E04(N) AS (SELECT 1 FROM E02 a, E02 b),
     E08(N) AS (SELECT 1 FROM E04 a, E04 b),
     Num(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY E08.N) FROM E08)
INSERT INTO T(DateTimeCol)
SELECT TOP 100 DATEADD(MINUTE, Num.N, '20130101')
FROM Num;

Podemos testar os diferentes níveis da seguinte forma:

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_100' ));
GO

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_110' ));
GO 

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_120' ));
GO 

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_130' ));
GO 

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_140' ));
GO 

Os planos para todos estes estão disponíveis aqui . Os níveis de compatibilidade 100 e 110 fornecem o plano de pesquisa principal, mas a partir do nível 120, começamos a obter o mesmo plano de varredura com estimativas de 100 linhas. Isso é verdade até o nível 140 compatível.

NUTS

NUTS

NUTS

A estimativa de cardinalidade para os >= '20130101', < '20130102'planos permanece em 100, o que era esperado.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.