У мене є цікаве питання щодо SARGability. У цьому випадку мова йде про використання присудка на різницю між двома стовпцями дати. Ось налаштування:
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])
Що я бачу досить часто, - це щось подібне:
/*definitely not sargable*/
SELECT
* ,
DATEDIFF(DAY, [s].[DateCol1], [s].[DateCol2])
FROM
[#sargme] AS [s]
WHERE
DATEDIFF(DAY, [s].[DateCol1], [s].[DateCol2]) >= 48;
... що, безумовно, НЕ МОЖЛИВО. Це призводить до сканування індексу, зчитує всі 1000 рядків, нічого хорошого. Орієнтовні ряди смердять. Ви б ніколи цього не ставили у виробництво.
Було б добре, якби ми змогли реалізувати CTE, тому що це допоможе нам зробити це, ну, більш SARGable-er, технічно кажучи. Але ні, ми отримуємо той же план виконання, що і вгорі.
/*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;
І звичайно, оскільки ми не використовуємо константи, цей код нічого не змінює і навіть не є наполовину SARGable. Не смішно. План виконання.
/*not even half sargable*/
SELECT
* ,
DATEDIFF(DAY, [s].[DateCol1], [s].[DateCol2])
FROM
[#sargme] AS [s]
WHERE
[s].[DateCol2] >= DATEADD(DAY, 48, [s].[DateCol1])
Якщо вам пощастило, і ви підкоряєтесь усім параметрам ANSI SET у рядках підключення, ви можете додати обчислений стовпець і здійснити пошук у ньому ...
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
Це допоможе вам отримати індекс з трьома запитами. Незвичайна людина - це додавання до DateCol1 48 днів. Запит з DATEDIFF
в WHERE
статті, то CTE
і остаточний запит з предикатом на обчислюваному стовпці все дає вам набагато приємніше план з набагато більш гарними оцінками, і все це.
Що приводить мене до питання: чи в одному запиті існує SARGable спосіб здійснення цього пошуку?
Немає тимчасових таблиць, немає змінних таблиць, не змінюється структура таблиці та немає переглядів.
Мені добре з самостійними приєднаннями, CTE, підзапитами або декількома передачами даних. Може працювати з будь-якою версією SQL Server.
Уникнення обчислених стовпців є штучним обмеженням, оскільки мене більше цікавить рішення запиту, ніж будь-що інше.