Вчера я ответил рекурсивному CTE, который выявил проблему с тем, как они реализованы в SQL Server (возможно, в других СУБД тоже?). В основном, когда я пытаюсь использовать ROW_NUMBER
для текущего рекурсивного уровня, он выполняется против каждой строки подмножества текущего рекурсивного уровня. Я ожидал бы, что это будет работать в истинной логике SET и будет работать против всего текущего рекурсивного уровня.
Похоже, что из этой статьи MSDN, проблема, которую я нашел, - это назначенная функциональность:
Аналитические и агрегатные функции в рекурсивной части CTE применяется к набору для текущего уровня рекурсии, а не к набору для CTE. Функции, такие как ROW_NUMBER, работают только с подмножеством данные, переданные им по текущему уровню рекурсии, а не по всему набор данных, положенный на рекурсивную часть CTE. Для большего информация, см. J. Использование аналитических функций в рекурсивном CTE.
В моем копании я не мог найти нигде, что объясняет, почему это было выбрано для работы так, как это делается? Это скорее процедурный подход на языке, основанный на наборе, поэтому это работает против моего мыслительного процесса SQL и, по моему мнению, довольно запутанно. Кто-нибудь знает и/или может кто-нибудь объяснить, почему рекурсивный CTE обрабатывает аналитические функции на уровне рекурсии процедурно?
Вот код, который поможет визуализировать это:
Обратите внимание, столбец RowNumber
в каждом из этих выходов кода.
Вот SQLFiddle для CTE (только показывающий 2-й уровень рекурсии)
WITH myCTE
AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY Score desc) AS RowNumber, 1 AS RecurseLevel
FROM tblGroups
WHERE ParentId IS NULL
UNION ALL
SELECT tblGroups.*,
ROW_NUMBER() OVER (ORDER BY myCTE.RowNumber , tblGroups.Score desc) AS RowNumber,
RecurseLevel + 1 AS RecurseLevel
FROM tblGroups
JOIN myCTE
ON myCTE.GroupID = tblGroups.ParentID
)
SELECT *
FROM myCTE
WHERE RecurseLevel = 2;
WITH myCTE
AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY Score desc) AS RowNumber, 1 AS RecurseLevel
FROM tblGroups
WHERE ParentId IS NULL
)
SELECT tblGroups.*,
ROW_NUMBER() OVER (ORDER BY myCTE.RowNumber , tblGroups.Score desc) AS RowNumber,
RecurseLevel + 1 AS RecurseLevel
FROM tblGroups
JOIN myCTE
ON myCTE.GroupID = tblGroups.ParentID;
Я всегда предполагал, что рекурсивный CTE SQL будет больше похож на на этот цикл while
DECLARE @RecursionLevel INT
SET @RecursionLevel = 0
SELECT *, ROW_NUMBER() OVER (ORDER BY Score desc) AS RowNumber, @RecursionLevel AS recurseLevel
INTO #RecursiveTable
FROM tblGroups
WHERE ParentId IS NULL
WHILE EXISTS( SELECT tblGroups.* FROM tblGroups JOIN #RecursiveTable ON #RecursiveTable.GroupID = tblGroups.ParentID WHERE recurseLevel = @RecursionLevel)
BEGIN
INSERT INTO #RecursiveTable
SELECT tblGroups.*,
ROW_NUMBER() OVER (ORDER BY #RecursiveTable.RowNumber , tblGroups.Score desc) AS RowNumber,
recurseLevel + 1 AS recurseLevel
FROM tblGroups
JOIN #RecursiveTable
ON #RecursiveTable.GroupID = tblGroups.ParentID
WHERE recurseLevel = @RecursionLevel
SET @RecursionLevel = @RecursionLevel + 1
END
SELECT * FROM #RecursiveTable ORDER BY RecurseLevel;