J'ai une question intéressante pour moi sur la SARGabilité. Dans ce cas, il s'agit d'utiliser un prédicat sur la différence entre deux colonnes de date. Voici la configuration:
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])
Ce que je vais voir assez souvent, c'est quelque chose comme ceci:
/*definitely not sargable*/
SELECT
* ,
DATEDIFF(DAY, [s].[DateCol1], [s].[DateCol2])
FROM
[#sargme] AS [s]
WHERE
DATEDIFF(DAY, [s].[DateCol1], [s].[DateCol2]) >= 48;
... ce qui n'est certainement pas SARGable. Il en résulte un balayage d'index, lit les 1000 lignes, pas bon. Les lignes estimées puent. Vous ne mettriez jamais cela en production.
Ce serait bien si nous pouvions matérialiser les CTE, car cela nous aiderait à rendre cela, enfin, plus SARGable-er, techniquement parlant. Mais non, nous obtenons le même plan d'exécution qu'en haut.
/*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;
Et bien sûr, puisque nous n'utilisons pas de constantes, ce code ne change rien, et n'est même pas à moitié SARGable. Pas drôle. Même plan d'exécution.
/*not even half sargable*/
SELECT
* ,
DATEDIFF(DAY, [s].[DateCol1], [s].[DateCol2])
FROM
[#sargme] AS [s]
WHERE
[s].[DateCol2] >= DATEADD(DAY, 48, [s].[DateCol1])
Si vous vous sentez chanceux et que vous obéissez à toutes les options ANSI SET dans vos chaînes de connexion, vous pouvez ajouter une colonne calculée et la rechercher ...
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
Cela vous donnera une recherche d'index avec trois requêtes. L'homme étrange est l'endroit où nous ajoutons 48 jours à DateCol1. La requête avec DATEDIFF
dans la WHERE
clause, la CTE
et la requête finale avec un prédicat sur la colonne calculée vous donnent tous un plan beaucoup plus agréable avec des estimations beaucoup plus belles, et tout cela.
Ce qui m'amène à la question: dans une seule requête, existe-t-il un moyen SARGable d'effectuer cette recherche?
Aucune table temporaire, aucune variable de table, aucune modification de la structure de la table et aucune vue.
Je vais bien avec les auto-jointures, les CTE, les sous-requêtes ou les passages multiples sur les données. Peut fonctionner avec n'importe quelle version de SQL Server.
Éviter la colonne calculée est une limitation artificielle car je suis plus intéressé par une solution de requête qu'autre chose.