В настоящее время я экспериментирую с отфильтрованными индексами в SQL Server. Я пытался сжать отфильтрованную индекс вниз, поставив следующий намек из BOL на практике:Отфильтрованный указатель в SQL Server отсутствует предикат не работает должным образом
Столбец в отфильтрованной индексном выражении не должен быть ключ или включен столбец в отфильтрованной определении индекса, если отфильтрованный индекс выражение эквивалентно предикату запроса, а запрос не возвращает столбец в отфильтрованном индексном выражении с запросом .
Я воспроизвел проблему в маленьком тестовом скрипте: Моя таблица выглядит следующим образом:
CREATE TABLE #test
(
ID BIGINT NOT NULL IDENTITY(1,1),
ARCHIVEDATE DATETIME NULL,
CLOSINGDATE DATETIME NULL,
OBJECTTYPE INTEGER NOT NULL,
ACTIVE BIT NOT NULL,
FILLER1 CHAR(255) DEFAULT 'just a filler',
FILLER2 CHAR(255) DEFAULT 'just a filler',
FILLER3 CHAR(255) DEFAULT 'just a filler',
FILLER4 CHAR(255) DEFAULT 'just a filler',
FILLER5 CHAR(255) DEFAULT 'just a filler',
CONSTRAINT test_pk PRIMARY KEY CLUSTERED (ID ASC)
);
мне нужно оптимизировать следующий запрос:
SELECT
COUNT(*)
FROM
#test
WHERE
ARCHIVEDATE IS NULL
AND CLOSINGDATE IS NOT NULL
AND ISNULL(ACTIVE,1) != 0
Поэтому я построил Отфильтрованный индекс:
CREATE NONCLUSTERED INDEX idx_filterTest ON #test (/*ARCHIVEDATE ASC,*/CLOSINGDATE ASC) INCLUDE (ACTIVE) WHERE ARCHIVEDATE IS NULL;
ARCHIVEDATE уже включен в фильтр и не будет использоваться в SELECT, поэтому он не содержится в индексных ключах или не включает.
Однако, если я выполнить запрос, я получаю следующий план:
Там есть ключ поиска в кластерном индексе для ArchiveDate. Почему это так? Я воспроизвел это поведение на SQL Server 2008 и SQL Server 2016.
Если я создаю индекс с ARCHIVEDATE в ключе, я уйду только с поиском индекса. Поэтому мне кажется, что этот абзац в BOL не всегда применяется.
Вот мой полный репро сценарий:
--DROP TABLE #test;
CREATE TABLE #test
(
ID BIGINT NOT NULL IDENTITY(1,1),
ARCHIVEDATE DATETIME NULL,
CLOSINGDATE DATETIME NULL,
OBJECTTYPE INTEGER NOT NULL,
ACTIVE BIT NOT NULL,
FILLER1 CHAR(255) DEFAULT 'just a filler',
FILLER2 CHAR(255) DEFAULT 'just a filler',
FILLER3 CHAR(255) DEFAULT 'just a filler',
FILLER4 CHAR(255) DEFAULT 'just a filler',
FILLER5 CHAR(255) DEFAULT 'just a filler',
CONSTRAINT test_pk PRIMARY KEY CLUSTERED (ID ASC)
);
INSERT INTO #test
(ARCHIVEDATE, CLOSINGDATE, OBJECTTYPE, ACTIVE)
SELECT TOP 200
NULL,
dates.calcDate,
4711,
dates.number%2
FROM
(
SELECT
/* Erzeugen des Datums durch Addieren der jeweiligen Sequenznummer zum StartDate */
DATEADD(DAY, seq.number, '20120101') AS calcDate, number
FROM
(
/* Abfrage zur Erstellung einer Nummernsequenz von 0 bis 9999. Dient als Basis zur Aufbereitung aller Datumswerte im Zeitraum. Die Sequenz reicht für einen Zeitraum von ca. 30 Jahren aus. */
SELECT
a.num * 1000 + b.num * 100 + c.num * 10 + d.num AS number
FROM
(SELECT 0 AS num UNION ALL SELECT 1 AS num UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
CROSS JOIN (SELECT 0 AS num UNION ALL SELECT 1 AS num UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
CROSS JOIN (SELECT 0 AS num UNION ALL SELECT 1 AS num UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) c
CROSS JOIN (SELECT 0 AS num UNION ALL SELECT 1 AS num UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) d
) seq
WHERE
/* Einschränkung der Nummernsequenz auf die Anzahl der Tage im gewünschten Aufbereitungszeitraum */
seq.number <= 5000
) dates
ORDER BY
dates.number
;
INSERT INTO #test
(ARCHIVEDATE, CLOSINGDATE, OBJECTTYPE, ACTIVE)
SELECT TOP 1000
dates.calcDate + 3,
dates.calcDate,
4711,
dates.number%2
FROM
(
SELECT
/* Erzeugen des Datums durch Addieren der jeweiligen Sequenznummer zum StartDate */
DATEADD(DAY, seq.number, '20120101') AS calcDate, number
FROM
(
/* Abfrage zur Erstellung einer Nummernsequenz von 0 bis 9999. Dient als Basis zur Aufbereitung aller Datumswerte im Zeitraum. Die Sequenz reicht für einen Zeitraum von ca. 30 Jahren aus. */
SELECT
a.num * 1000 + b.num * 100 + c.num * 10 + d.num AS number
FROM
(SELECT 0 AS num UNION ALL SELECT 1 AS num UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
CROSS JOIN (SELECT 0 AS num UNION ALL SELECT 1 AS num UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
CROSS JOIN (SELECT 0 AS num UNION ALL SELECT 1 AS num UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) c
CROSS JOIN (SELECT 0 AS num UNION ALL SELECT 1 AS num UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) d
) seq
WHERE
/* Einschränkung der Nummernsequenz auf die Anzahl der Tage im gewünschten Aufbereitungszeitraum */
seq.number <= 5000
) dates
ORDER BY
dates.number
;
INSERT INTO #test
(ARCHIVEDATE, CLOSINGDATE, OBJECTTYPE, ACTIVE)
SELECT TOP 100000
dates.calcDate,
NULL,
4711,
dates.number%2
FROM
(
SELECT
/* Erzeugen des Datums durch Addieren der jeweiligen Sequenznummer zum StartDate */
DATEADD(DAY, seq.number, '20120101') AS calcDate, number
FROM
(
/* Abfrage zur Erstellung einer Nummernsequenz von 0 bis 9999. Dient als Basis zur Aufbereitung aller Datumswerte im Zeitraum. Die Sequenz reicht für einen Zeitraum von ca. 30 Jahren aus. */
SELECT
a.num * 1000 + b.num * 100 + c.num * 10 + d.num AS number
FROM
(SELECT 0 AS num UNION ALL SELECT 1 AS num UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
CROSS JOIN (SELECT 0 AS num UNION ALL SELECT 1 AS num UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
CROSS JOIN (SELECT 0 AS num UNION ALL SELECT 1 AS num UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) c
CROSS JOIN (SELECT 0 AS num UNION ALL SELECT 1 AS num UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) d
) seq
WHERE
/* Einschränkung der Nummernsequenz auf die Anzahl der Tage im gewünschten Aufbereitungszeitraum */
seq.number <= 5000
) dates
ORDER BY
dates.number
;
--DROP INDEX idx_filterTest ON #test;
--CREATE NONCLUSTERED INDEX idx_filterTest ON #test (ARCHIVEDATE ASC,CLOSINGDATE ASC) INCLUDE (ACTIVE) WHERE ARCHIVEDATE IS NULL;
CREATE NONCLUSTERED INDEX idx_filterTest ON #test (/*ARCHIVEDATE ASC,*/CLOSINGDATE ASC) INCLUDE (ACTIVE) WHERE ARCHIVEDATE IS NULL;
SELECT
COUNT(*)
FROM
#test
WHERE
ARCHIVEDATE IS NULL
AND CLOSINGDATE IS NOT NULL
AND ISNULL(ACTIVE,1) != 0;
Hi Jeroen, спасибо за очень полезный ответ. Теперь я знаю, что я не сделал что-то не так. Вы правы ... в том числе столбец - лучший вариант, поскольку условие фильтра сужает значения до NULL, и я не буду лучше искать его, индексируя его. –