Документация немного вводит в заблуждение. DMV является нематериализованным представлением и не имеет первичного ключа как такового. Базовые определения немного сложны, но упрощенное определение sys.query_store_plan
:
CREATE VIEW sys.query_store_plan AS
SELECT
PPM.plan_id
-- various other attributes
FROM sys.plan_persist_plan_merged AS PPM
LEFT JOIN sys.syspalvalues AS P
ON P.class = 'PFT'
AND P.[value] = plan_forcing_type;
Кроме того, sys.plan_persist_plan_merged
это также представление, хотя необходимо подключиться через выделенное соединение администратора, чтобы увидеть его определение. Опять упрощенно
CREATE VIEW sys.plan_persist_plan_merged AS
SELECT
P.plan_id as plan_id,
-- various other attributes
FROM sys.plan_persist_plan P
-- NOTE - in order to prevent potential deadlock
-- between QDS_STATEMENT_STABILITY LOCK and index locks
WITH (NOLOCK)
LEFT JOIN sys.plan_persist_plan_in_memory PM
ON P.plan_id = PM.plan_id;
Индексы по sys.plan_persist_plan
:
╔════════════════════════╦════════════════════════ ══════════════╦═════════════╗
║ index_name ║ index_description ║ index_keys ║
╠════════════════════════╬════════════════════════ ══════════════╬═════════════╣
║ plan_persist_plan_cidx ║ кластеризованный, уникальный, расположенный на PRIMARY ║ plan_id ║
║ plan_persist_plan_idx1 ║ некластеризованный, расположенный на PRIMARY ║ query_id (-) ║
╚════════════════════════╩════════════════════════ ══════════════╩═════════════╝
Так plan_id
что вынужден быть уникальным sys.plan_persist_plan
.
Теперь sys.plan_persist_plan_in_memory
это потоковая табличная функция, представляющая табличное представление данных, хранящихся только во внутренних структурах памяти. Как таковой, он не имеет никаких уникальных ограничений.
По сути, выполняемый запрос, таким образом, эквивалентен:
DECLARE @t1 table (plan_id integer NOT NULL);
DECLARE @t2 table (plan_id integer NOT NULL UNIQUE CLUSTERED);
DECLARE @t3 table (plan_id integer NULL);
SELECT
T1.plan_id
FROM @t1 AS T1
LEFT JOIN
(
SELECT
T2.plan_id
FROM @t2 AS T2
LEFT JOIN @t3 AS T3
ON T3.plan_id = T2.plan_id
) AS Q1
ON Q1.plan_id = T1.plan_id;
... который не производит исключение соединения:
Если разобраться в сути проблемы, проблема заключается во внутреннем запросе:
DECLARE @t2 table (plan_id integer NOT NULL UNIQUE CLUSTERED);
DECLARE @t3 table (plan_id integer NULL);
SELECT
T2.plan_id
FROM @t2 AS T2
LEFT JOIN @t3 AS T3
ON T3.plan_id = T2.plan_id;
... очевидно, что левое соединение может привести @t2
к дублированию строк , поскольку @t3
не имеет ограничений по уникальности plan_id
. Следовательно, соединение не может быть устранено:
Чтобы обойти это, мы можем явно сказать оптимизатору, что нам не нужны повторяющиеся plan_id
значения:
DECLARE @t2 table (plan_id integer NOT NULL UNIQUE CLUSTERED);
DECLARE @t3 table (plan_id integer NULL);
SELECT DISTINCT
T2.plan_id
FROM @t2 AS T2
LEFT JOIN @t3 AS T3
ON T3.plan_id = T2.plan_id;
Внешнее соединение с @t3
теперь может быть устранено:
Применяя это к реальному запросу:
SELECT DISTINCT
T.plan_id
FROM #tears AS T
LEFT JOIN sys.query_store_plan AS QSP
ON QSP.plan_id = T.plan_id;
Точно так же мы могли бы добавить GROUP BY T.plan_id
вместо DISTINCT
. В любом случае, оптимизатор теперь может правильно рассуждать об plan_id
атрибуте вплоть до вложенных представлений и по желанию исключать оба внешних объединения:
Обратите внимание, что создание plan_id
уникального во временной таблице будет недостаточно для исключения исключения, поскольку это не исключает неправильных результатов. Мы должны явно отклонить повторяющиеся plan_id
значения из конечного результата, чтобы оптимизатор мог применить свою магию здесь.