Tengo lo que es, para mí, una pregunta interesante sobre SARGability. En este caso, se trata de usar un predicado en la diferencia entre dos columnas de fecha. Aquí está la configuración:
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])
Lo que veré con bastante frecuencia es algo como esto:
/*definitely not sargable*/
SELECT
* ,
DATEDIFF(DAY, [s].[DateCol1], [s].[DateCol2])
FROM
[#sargme] AS [s]
WHERE
DATEDIFF(DAY, [s].[DateCol1], [s].[DateCol2]) >= 48;
... que definitivamente no es SARGable. Da como resultado una exploración de índice, lee las 1000 filas, no es bueno. Las filas estimadas apestan. Nunca pondrías esto en producción.
Sería bueno si pudiéramos materializar CTE, porque eso nos ayudaría a hacer esto, bueno, más SARGable, técnicamente hablando. Pero no, tenemos el mismo plan de ejecución que arriba.
/*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;
Y, por supuesto, como no estamos usando constantes, este código no cambia nada y ni siquiera es medio SARGable. No es divertido. Mismo plan de ejecución.
/*not even half sargable*/
SELECT
* ,
DATEDIFF(DAY, [s].[DateCol1], [s].[DateCol2])
FROM
[#sargme] AS [s]
WHERE
[s].[DateCol2] >= DATEADD(DAY, 48, [s].[DateCol1])
Si se siente afortunado y obedece todas las opciones de ANSI SET en sus cadenas de conexión, puede agregar una columna calculada y buscarla ...
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
Esto le dará una búsqueda de índice con tres consultas. El hombre extraño es donde agregamos 48 días a DateCol1. La consulta con DATEDIFF
en la WHERE
cláusula, la CTE
y la consulta final con un predicado en la columna calculada le dan un plan mucho más agradable con estimaciones mucho más agradables, y todo eso.
Lo que me lleva a la pregunta: en una sola consulta, ¿hay una manera SARGable de realizar esta búsqueda?
Sin tablas temporales, sin variables de tabla, sin alterar la estructura de la tabla y sin vistas.
Estoy bien con autouniones, CTE, subconsultas o pases múltiples sobre los datos. Puede funcionar con cualquier versión de SQL Server.
Evitar la columna calculada es una limitación artificial porque estoy más interesado en una solución de consulta que cualquier otra cosa.