昨日、再帰的な CTE に回答したところ、これらが SQL Server に実装される方法に関する問題が明らかになりました (おそらく他の RDBMS でも?)。基本的にROW_NUMBER
、現在の再帰レベルに対して使用しようとすると、現在の再帰レベルの各行サブセットに対して実行されます。これが真の SET ロジックで機能し、現在の再帰レベル全体に対して実行されることを期待します。
この MSDN の記事から、私が見つけた問題は意図された機能であると思われます。
CTE の再帰部分の分析関数と集計関数は、CTE のセットではなく、現在の再帰レベルのセットに適用されます。ROW_NUMBER などの関数は、現在の再帰レベルによって渡されたデータのサブセットに対してのみ動作し、 CTE の再帰部分に渡されたデータのセット全体に対しては動作しません。詳しくは、J. 再帰 CTE での分析関数の使用を参照してください。
私の掘り下げでは、なぜこれがそのように動作するように選択されたのかを説明する場所を見つけることができませんでした? これは、セットベースの言語での手続き型アプローチに近いため、SQL の思考プロセスに反して動作し、非常に混乱を招くと思います。再帰CTEが再帰レベルで手続き型の方法で分析関数を扱う理由を誰かが知っている、または誰かが説明できますか?
これを視覚化するのに役立つコードは次のとおりです。
RowNumber
これらのコード出力のそれぞれの列に注意してください。
これはCTEのSQLFiddleです(再帰の第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;
これは、CTEが行うことを期待する2番目のSQLFiddleです(問題を表示するには、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;
私は常に SQL 再帰 CTE がこの 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;