sql >> データベース >  >> RDS >> Sqlserver

ランキング関数を使用した再帰cte

    編集

    再帰に関するCTEのドキュメントを読むと、サブクエリ、group-by、topを使用できないなど、いくつかの制限があることに気付くでしょう。これらはすべて複数の行を含みます。限定的なテスト、実行プランのチェック、およびこのクエリのテストから

    with cte as (
      select 1 a, 1 b union all select 1, 2 union all select 1, 3 union all select 2, 4
    )
    , rcte (a, b, c, d) as (
      select a, b, cast(0 as int), 1 
      from cte
      union all
      select r.a, cte.b, cast(ROW_NUMBER() over (order by r.b) as int), r.d+1
      from rcte r inner join cte on cte.a=r.a
      where r.d < 2
    )
    select * 
    from rcte
    where d=2
    order by a, b
    

    結論は次のとおりです:

    1. Row_Number()は、他のテーブルが結合されて複数行の結果セットを生成する場合、CTEで機能します
    2. 番号付けの結果から、CTEは、すべての行を同時に反復しているように見えますが、複数行ごとではなく、行ごとに、すべての反復を通じて単一の行で処理されることが明らかです。これは、複数行操作に適用される関数のいずれかが再帰CTEで許可されない理由を説明します。

    私は簡単にこの結論に達しましたが、誰かが 非常に詳細に説明する のみ 17か月前...

    言い換えると、これはSQLServerによるの実装の性質です。 再帰CTEであるため、ウィンドウ関数は期待どおりに機能しません。

    他の人の利益のために、出力は次のとおりです。
    a           b           c           d
    ----------- ----------- ----------- -----------
    1           1           1           2
    1           2           1           2
    2           3           1           2
    2           4           1           2
    

    一方、cには1,1,1,1ではなく1,2,1,2が含まれていると予想しています。 ウィンドウ関数はCTEの再帰部分では機能しないはずだというドキュメントがないため、これは確かにバグのようです。

    注: row_number() bigintを返すので、anchor(c)だけをbigintとしてキャストできます。

    各反復でdが増加するため、外部でウィンドウ処理を実行できます。

    with cte as (
      select 1 a, 1 b union all select 1, 2 union all select 2, 3 union all select 2, 4
    )
    , rcte (a, b, d) as (
      select a, b, 1 
      from cte
      union all
      select a, b, d+1
      from rcte
      where d < 2
    )
    select a,b, ROW_NUMBER() over (partition by a,d order by b) c,d
    from rcte
    --where d=2
    order by d, a, b
    

    編集-洞察

    別の質問に答えている間 、再帰CTEでもう少し遊んだ。最後のORDERBYなしで実行すると、SQLServerがどのように再帰に近づいているかを確認できます。この場合、逆方向に進み、各行で深さ優先探索を完全に実行するのは興味深いことです。

    サンプルテーブル

    create table Testdata(SomeID int, OtherID int, Data varchar(max))
    insert Testdata select 1, 9, '18,20,22,alpha,beta,gamma,delta'
    insert Testdata select 2, 6, ''
    insert Testdata select 3, 8, '11,12,.'
    insert Testdata select 4, 7, '13,19,20,66,12,232,1232,12312,1312,abc,def'
    insert Testdata select 5, 8, '17,19'
    

    再帰クエリ

    ;with tmp(SomeID, OtherID, DataItem, Data) as (
    select SomeID, OtherID, LEFT(Data, CHARINDEX(',',Data+',')-1),
        STUFF(Data, 1, CHARINDEX(',',Data+','), '')
    from Testdata
    union all
    select SomeID, OtherID, LEFT(Data, CHARINDEX(',',Data+',')-1),
        STUFF(Data, 1, CHARINDEX(',',Data+','), '')
    from tmp
    where Data > ''
    )
    select SomeID, OtherID, DataItem, Data
    from tmp
    -- order by SomeID
    

    出力には、反復1で処理されたCTEアンカーが表示され、その後何らかの理由で アンカーセットの各行は、他の行を処理する前に、完全に(深さ優先)繰り返されます。

    それでも、この回答 ショー




    1. Oracleテーブルの列が別のテーブルのトリガーによって入力/更新されていることをどのように把握できますか?

    2. MySqlは、列名として動的な行の値を選択します

    3. MySQL-LIMITを使用したUPDATEクエリ

    4. SubSonic 3およびMySQL、CleanUp()メソッドの列名からアンダースコアを削除すると、linq-queryでプロパティを使用するときに例外が発生します