Ho una domanda interessante per me sulla SARGability. In questo caso, si tratta di utilizzare un predicato sulla differenza tra due colonne di date. Ecco la configurazione:
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])
Quello che vedrò abbastanza frequentemente è qualcosa del genere:
/*definitely not sargable*/
SELECT
* ,
DATEDIFF(DAY, [s].[DateCol1], [s].[DateCol2])
FROM
[#sargme] AS [s]
WHERE
DATEDIFF(DAY, [s].[DateCol1], [s].[DateCol2]) >= 48;
... che sicuramente non è SARGable. Ne risulta una scansione dell'indice, legge tutte le 1000 righe, non va bene. Righe stimate puzzano. Non l'avresti mai messo in produzione.
Sarebbe bello se potessimo materializzare i CTE, perché ciò ci aiuterebbe a rendere questo, beh, più SARGable, tecnicamente parlando. Ma no, otteniamo lo stesso piano di esecuzione in alto.
/*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 ovviamente, poiché non stiamo usando le costanti, questo codice non cambia nulla e non è nemmeno metà SARGable. Non è divertente. Stesso piano di esecuzione.
/*not even half sargable*/
SELECT
* ,
DATEDIFF(DAY, [s].[DateCol1], [s].[DateCol2])
FROM
[#sargme] AS [s]
WHERE
[s].[DateCol2] >= DATEADD(DAY, 48, [s].[DateCol1])
Se ti senti fortunato e stai obbedendo a tutte le opzioni ANSI SET nelle tue stringhe di connessione, potresti aggiungere una colonna calcolata e cercarla ...
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
Questo ti farà cercare un indice con tre query. Il dispari è dove aggiungiamo 48 giorni a DateCol1. La query con DATEDIFF
nella WHERE
clausola, the CTE
e la query finale con un predicato sulla colonna calcolata offrono tutti un piano molto più bello con stime molto più belle e tutto il resto.
Il che mi porta alla domanda: in una singola query, esiste un modo SARGable per eseguire questa ricerca?
Nessuna tabella temporanea, nessuna variabile di tabella, nessuna modifica della struttura della tabella e nessuna vista.
Sto bene con self-join, CTE, subquery o passaggi multipli sui dati. Può funzionare con qualsiasi versione di SQL Server.
Evitare la colonna calcolata è una limitazione artificiale perché sono più interessato a una soluzione di query che a qualsiasi altra cosa.