Eu tenho o que é, para mim, uma pergunta interessante sobre SARGability. Nesse caso, trata-se de usar um predicado na diferença entre duas colunas de data. Aqui está a configuração:
USE [tempdb]
SET NOCOUNT ON
IF OBJECT_ID('tempdb..#sargme') IS NOT NULL
BEGIN
DROP TABLE #sargme
END
SELECT TOP 1000
IDENTITY (BIGINT, 1,1) AS ID,
CAST(DATEADD(DAY, [m].[severity] * -1, GETDATE()) AS DATE) AS [DateCol1],
CAST(DATEADD(DAY, [m].[severity], GETDATE()) AS DATE) AS [DateCol2]
INTO #sargme
FROM sys.[messages] AS [m]
ALTER TABLE [#sargme] ADD CONSTRAINT [pk_whatever] PRIMARY KEY CLUSTERED ([ID])
CREATE NONCLUSTERED INDEX [ix_dates] ON [#sargme] ([DateCol1], [DateCol2])
O que vou ver com bastante frequência é algo como isto:
/*definitely not sargable*/
SELECT
* ,
DATEDIFF(DAY, [s].[DateCol1], [s].[DateCol2])
FROM
[#sargme] AS [s]
WHERE
DATEDIFF(DAY, [s].[DateCol1], [s].[DateCol2]) >= 48;
... o que definitivamente não é SARGable. Isso resulta em uma varredura de índice, lê todas as 1000 linhas, nada bom. Linhas estimadas cheiram mal. Você nunca colocou isso em produção.
Seria bom se pudéssemos materializar CTEs, porque isso nos ajudaria a tornar isso, bem, mais SARGable-er, tecnicamente falando. Mas não, temos o mesmo plano de execução que o topo.
/*would be nice if it were sargable*/
WITH [x] AS ( SELECT
* ,
DATEDIFF(DAY, [s].[DateCol1], [s].[DateCol2]) AS [ddif]
FROM
[#sargme] AS [s])
SELECT
*
FROM
[x]
WHERE
[x].[ddif] >= 48;
E, é claro, como não estamos usando constantes, esse código não muda nada e nem sequer é metade da SARGable. Não é divertido. Mesmo plano de execução.
/*not even half sargable*/
SELECT
* ,
DATEDIFF(DAY, [s].[DateCol1], [s].[DateCol2])
FROM
[#sargme] AS [s]
WHERE
[s].[DateCol2] >= DATEADD(DAY, 48, [s].[DateCol1])
Se você estiver com sorte e obedecendo a todas as opções de ANSI SET nas cadeias de conexão, você pode adicionar uma coluna computada e pesquisá-la ...
ALTER TABLE [#sargme] ADD [ddiff] AS
DATEDIFF(DAY, DateCol1, DateCol2) PERSISTED
CREATE NONCLUSTERED INDEX [ix_dates2] ON [#sargme] ([ddiff], [DateCol1], [DateCol2])
SELECT [s].[ID] ,
[s].[DateCol1] ,
[s].[DateCol2]
FROM [#sargme] AS [s]
WHERE [ddiff] >= 48
Isso fornecerá uma busca de índice com três consultas. O estranho é que adicionamos 48 dias ao DateCol1. A consulta com DATEDIFF
na WHERE
cláusula, a CTE
e a consulta final com um predicado na coluna computada oferecem um plano muito mais agradável, com estimativas muito mais agradáveis e tudo mais.
O que me leva à pergunta: em uma única consulta, existe uma maneira SARGable de realizar essa pesquisa?
Sem tabelas temporárias, sem variáveis de tabela, sem alterar a estrutura da tabela e sem visualizações.
Estou bem com auto-junções, CTEs, subconsultas ou várias passagens sobre os dados. Pode funcionar com qualquer versão do SQL Server.
Evitar a coluna computada é uma limitação artificial, porque estou mais interessado em uma solução de consulta do que qualquer outra coisa.