Фільтрований індекс застосовується лише тоді, коли відфільтрована частина знаходиться в ПРИЄДНАННІ, а не де


10

Я створив відфільтрований індекс нижче, але коли я запускаю два запити далі вниз, цей індекс використовується тільки для пошуку в першому прикладі, який містить END_DTTM в JOIN, а не пункт де (це єдина різниця в запитах) . Хтось може пояснити, чому це відбувається?

Створення індексу

CREATE NONCLUSTERED INDEX [ix_PATIENT_LIST_BESPOKE_LIST_ID_includes] ON [dbo].[PATIENT_LIST_BESPOKE] 
(
    [LIST_ID] ASC,
    [END_DTTM] ASC
)
WHERE ([END_DTTM] IS NULL)

Запити

DECLARE @LIST_ID INT = 3655

--This one seeks on the index

SELECT  
    PATIENT_LISTS.LIST_ID
FROM    
    DBO.PATIENT_LISTS
    LEFT JOIN DBO.PATIENT_LIST_BESPOKE ON PATIENT_LISTS.LIST_ID = PATIENT_LIST_BESPOKE.LIST_ID  
                                      AND PATIENT_LIST_BESPOKE.END_DTTM IS NULL
WHERE
    PATIENT_LISTS.LIST_ID = @LIST_ID

--This one scans on the index

SELECT  
    PATIENT_LISTS.LIST_ID
FROM    
    DBO.PATIENT_LISTS
    LEFT JOIN DBO.PATIENT_LIST_BESPOKE ON PATIENT_LISTS.LIST_ID = PATIENT_LIST_BESPOKE.LIST_ID  
WHERE   
    PATIENT_LISTS.LIST_ID = @LIST_ID AND
    PATIENT_LIST_BESPOKE.END_DTTM IS NULL   

Відповіді:


12

Щоб оптимізатор збігав присудок до індексу (відфільтрований чи іншим чином), предикат повинен з’являтися поруч із операцією Get у дереві логічних запитів. Щоб полегшити це, предикати зазвичай натискають якомога ближче до листя логічного дерева до початку оптимізації.

Щоб значно спростити, реалізація стратегії фізичного індексу робить це:

Predicate + Logical Get -> Physical Get (using Index)

Запит, який вас цікавить, починається з предиката над зовнішнім приєднанням:

Predicate on T2 --+-- LOJ -- Get (T1)
                       |
                       +---- Get (T2)

Ця форма не відповідає правилу стратегії індексу, оскільки предикат не примикає до Get. Отже, перша частина відповіді полягає в тому, що відфільтрований показник зіставлення не вдасться, якщо предикат не можна буде просунути повз зовнішнє з'єднання.

Друга частина полягає лише в тому, що оптимізатор не містить необхідного правила дослідження для переміщення предиката повз зовнішнє з'єднання на збереженій стороні, тому що трансформація є такою рідкісною. Загальною рисою оптимізатора є те, що реалізуються лише найчастіше корисні правила.

Як результат, відповідність відфільтрованому індексу в цьому випадку не вдається. Щоб було зрозуміло, переписання було б дійсним у конкретному випадку, який ви цитуєте (другий запит).

Для першої форми запиту (з різною семантикою) предикат асоціюється з об'єднанням з самого початку, і логіка, що висуває предикат, може перемістити цю коротку відстань до Get, тому що вона не повинна проходити повз зовнішнє з'єднання як пояснено вище.

Довідка та додаткова інформація:


9

Це не семантично однакові запити, оскільки один може фільтрувати до об'єднання, а другий може фільтрувати після. Дозвольте проілюструвати більш простим прикладом:

CREATE TABLE dbo.Lefty(LeftyID INT PRIMARY KEY);

CREATE TABLE dbo.Righty(LeftyID INT, SomeList INT);

INSERT dbo.Lefty(LeftyID) VALUES(1),(2),(3);

INSERT dbo.Righty(LeftyID, SomeList) VALUES(1,1),(1,NULL),(2,2);

Запит 1 повертає всі три рядки:

SELECT l.LeftyID, r.SomeList
FROM dbo.Lefty AS l
LEFT OUTER JOIN dbo.Righty AS r
ON l.LeftyID = r.LeftyID
AND r.SomeList IS NULL;

Запит 2, однак, не залишає LeftyID 2:

SELECT l.LeftyID, r.SomeList
FROM dbo.Lefty AS l
LEFT OUTER JOIN dbo.Righty AS r
ON l.LeftyID = r.LeftyID
WHERE r.SomeList IS NULL;

Доказ SQLfiddle

Якщо ви намагаєтеся виконати приєднання проти напівсироти, тестований стовпчик не повинен бути нульовим . Критерії переміщення між ON і WHERE не мають логічної різниці, якщо ви маєте справу тільки з INNER, але в системі OUTER є суттєва різниця. І вам слід більше дбати про те, щоб ваші результати були правильними, ніж те, чи можна відфільтрований індекс навіть використовувати.


дякую за відповідь, але я не стверджую, що запити однакові, я запитую, чому один запит використовує відфільтрований індекс, а інший - ні.
chris

@chris Ви намагалися застосувати цей індекс підказкою? Мені було б цікаво порівняти фактичні плани після виконання з цим натяком і без нього. Мені зрозуміло, що оптимізатор ігнорує цей індекс, коли вважає, що робить анти-напівз'єднання (оскільки в цьому випадку не очікує використання нульового стовпчика), але я не впевнений, чи потрібно це зробіть із витратами чи порядком операцій або деякими основними знаннями про те, що з лівого боку потенційно набагато більше рядків, ніж ті, що знаходяться у відфільтрованому індексі. Побачення планів може допомогти.
Аарон Бертран

3

Два запити різні - за значенням та результатами. Ось переписування, тому очевидно, що роблять два запити:

-- 1st query
SELECT  
    a.LIST_ID
FROM    
      ( SELECT LIST_ID 
        FROM   DBO.PATIENT_LISTS
        WHERE  LIST_ID = @LIST_ID
      ) AS a
    LEFT JOIN  
      ( SELECT LIST_ID                    -- the filtered index
        FROM   DBO.PATIENT_LIST_BESPOKE   -- can be used
        WHERE  END_DTTM IS NULL           -- for the subquery
      ) AS b
    ON  a.LIST_ID = b.LIST_ID ;           -- and the join

і 2-е:

-- 2nd query
SELECT  
    a.LIST_ID
FROM    
      ( SELECT LIST_ID 
        FROM   DBO.PATIENT_LISTS
        WHERE  LIST_ID = @LIST_ID
      ) AS a
    JOIN  
      ( SELECT LIST_ID                    -- the filtered index
        FROM   DBO.PATIENT_LIST_BESPOKE   -- can be used
        WHERE  END_DTTM IS NULL           -- for the subquery
      ) AS b
    ON  a.LIST_ID = b.LIST_ID             -- and the join

UNION ALL

SELECT  
    a.LIST_ID
FROM    
      ( SELECT LIST_ID 
        FROM   DBO.PATIENT_LISTS
        WHERE  LIST_ID = @LIST_ID
      ) AS a
WHERE NOT EXISTS  
      ( SELECT *
        FROM   DBO.PATIENT_LIST_BESPOKE AS b
        WHERE  a.LIST_ID = b.LIST_ID         -- but not for this
      ) ;

Я думаю, зараз очевидно, що для 2-ї частини запиту 2nq відфільтрований індекс не може бути використаний.


Детально щодо цих запитів LIST_IDу першій таблиці є 4 типи значень:

  • (а) значення, які мають відповідні рядки у другій таблиці, всі з END_DTTM IS NULL.

  • (b) значення, які мають відповідні рядки у другій таблиці, і з, END_DTTM IS NULLі з END_DTTM IS NOT NULL.

  • (c) значення, які мають відповідні рядки у другій таблиці, всі з END_DTTM IS NOT NULL.

  • (d) значення, які не мають відповідних рядків у другій таблиці.

Тепер 1-й запит поверне всі значення типів (a) та (b), можливо, багато разів (стільки, скільки вони мають відповідні рядки у другій таблиці з END_DTTM IS NULL), і всі рядки типів (c) та (d) рівно один раз ( це невідповідна частина зовнішнього з'єднання).

2-й запит поверне всі значення типів (a) і (b), можливо, багато разів (стільки, скільки вони мають відповідні рядки у другій таблиці з END_DTTM IS NULL), і всі рядки типу (d) рівно один раз.
Він не поверне жодного значення типу (c), тому що приєднання знайде відповідні рядки у другій таблиці (але вони будуть END_DTTM IS NOT NULL), і вони будуть видалені наступним WHEREпунктом.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.