Я использую рекурсивный CTE в древовидной структуре, чтобы перечислить всех потомков определенного узла в дереве. Если я напишу значение литерального узла в своем WHERE
предложении, SQL Server фактически применяет CTE только к этому значению, предоставляя план запроса с низким фактическим числом строк и так далее :
Однако, если я передаю значение в качестве параметра, он, похоже, реализует (спулингирует) CTE, а затем фильтрует его по факту :
Я мог неправильно читать планы. Я не заметил проблемы с производительностью, но я обеспокоен тем, что реализация CTE может вызвать проблемы с большими наборами данных, особенно в загруженной системе. Кроме того, я обычно соединяю этот обход сам с собой: я иду к предкам и обратно к потомкам (чтобы я собрал все связанные узлы). Из-за того, как мои данные, каждый набор «связанных» узлов довольно мал, поэтому реализация CTE не имеет смысла. И когда SQL Server, похоже, понимает CTE, он дает мне довольно большие цифры в своих «фактических» подсчетах.
Есть ли способ заставить параметризованную версию запроса действовать как буквальная версия? Я хочу поместить CTE в вид многократного использования.
Запрос с литералом:
CREATE PROCEDURE #c AS BEGIN;
WITH descendants AS (SELECT
t.ParentId Id
,t.Id DescendantId
FROM #tree t
WHERE t.ParentId IS NOT NULL
UNION ALL SELECT
d.Id
,t.Id DescendantId
FROM descendants d
JOIN #tree t ON d.DescendantId = t.ParentId)
SELECT d.*
FROM descendants d
WHERE d.Id = 24
ORDER BY d.Id, d.DescendantId;
END;
GO
EXEC #c;
Запрос с параметром:
CREATE PROCEDURE #c (@Id BIGINT) AS BEGIN;
WITH descendants AS (SELECT
t.ParentId Id
,t.Id DescendantId
FROM #tree t
WHERE t.ParentId IS NOT NULL
UNION ALL SELECT
d.Id
,t.Id DescendantId
FROM descendants d
JOIN #tree t ON d.DescendantId = t.ParentId)
SELECT d.*
FROM descendants d
WHERE d.Id = @Id
ORDER BY d.Id, d.DescendantId;
END;
GO
EXEC #c 24;
Код настройки:
DECLARE @count BIGINT = 100000;
CREATE TABLE #tree (
Id BIGINT NOT NULL PRIMARY KEY
,ParentId BIGINT
);
CREATE INDEX tree_23lk4j23lk4j ON #tree (ParentId);
WITH number AS (SELECT
CAST(1 AS BIGINT) Value
UNION ALL SELECT
n.Value * 2 + 1
FROM number n
WHERE n.Value * 2 + 1 <= @count
UNION ALL SELECT
n.Value * 2
FROM number n
WHERE n.Value * 2 <= @count)
INSERT #tree (Id, ParentId)
SELECT n.Value, CASE WHEN n.Value % 3 = 0 THEN n.Value / 4 END
FROM number n;