O SQL Server avalia funções uma vez para cada linha?

9

Eu tenho uma consulta como esta:

SELECT col1
FROM   MyTable
WHERE  
    DATEADD(dd, 0, DATEDIFF(dd, 0, GETDATE())) 
       BETWEEN col2 
       AND     col3
;

Isso fornece uma dica de ferramenta no plano de execução semelhante a esta:

Dica de execução

A dateaddparte dos predicados de busca é executada para todas as linhas da consulta? Ou o SQL Server calcula o valor uma vez para toda a consulta?

Stuart Blackler
fonte

Respostas:

13

Certas funções conhecidas como constantes de tempo de execução passam pelo processo chamado dobragem constante . Ao 'dobrar' uma constante, uma expressão é avaliada no início da execução da consulta, o resultado é armazenado em cache e o resultado, quando necessário. A expressão na sua consulta DATEADD(dd, 0, DATEDIFF(dd, 0, getdate()))é uma constante de tempo de execução e, portanto, será dobrada e avaliada apenas uma vez por consulta.

Como trivial: a RAND()função que se esperaria que fosse desdobrável é realmente dobrável, o que leva a algum comportamento inesperado. Mas outros, por exemplo NEWID(), não são dobráveis ​​e forçarão uma avaliação por linha.

Remus Rusanu
fonte
2
@StuartBlackler - Aqui está uma demonstração de como as dobras do SQL Server funcionam GETDATE().
Nick Chammas
2

Os planos de execução são ótimos, mas às vezes eles simplesmente não dizem a verdade. Então, aqui está uma prova baseada no teste de desempenho.

(e a linha inferior - a expressão não está sendo avaliada para cada linha)


;with t(i) as (select 0 union all select i+1 from t where i < 9)
select getdate()-1 as col1,getdate() as col2,getdate() as col3 
into #t 
from t t0,t t1,t t2,t t3,t t4,t t5,t t6,t t7

(100000000 linhas afetadas)

Essa é a consulta OP e leva cerca de 12 segundos para ser executada

SELECT col1
FROM   #t
WHERE  
    DATEADD(dd, 0, DATEDIFF(dd, 0, GETDATE())) 
       BETWEEN col2 
       AND     col3
;

Essa consulta que armazena a data em um parâmetro antes da execução, leva aproximadamente o mesmo tempo, 12 segundos.

declare @dt datetime = DATEADD(dd, 0, DATEDIFF(dd, 0, GETDATE())) 

SELECT col1
FROM   #t
WHERE  
      @dt
       BETWEEN col2 
       AND     col3
;

E apenas para verificar os resultados -
Essa consulta que calcula a col1 e, portanto, precisa recalcular a expressão para cada linha leva cerca de 30 segundos para ser executada.

SELECT col1
FROM   #t
WHERE  
    DATEADD(dd, 0, DATEDIFF(dd, 0, col1)) 
       BETWEEN col2 
       AND     col3
;

Todas as consultas foram executadas mostrando repetidamente as mesmas métricas

David Markovitz
fonte