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

Oracleのギャップと島のソリューション-再帰の使用

    これは、ギャップと島の問題のバリエーションであり、各島の最大行数がさらに複雑になります。これは少し時間がかかりますが、シーケンスの順序によって引き起こされたグループを特定することから始めることができます:

    select t.*,
      row_number() over (partition by "Description" order by "Start") as rn,
      case when lag("SequentialOrder")
        over (partition by "Description" order by "Start") < "SequentialOrder"
        then 1 else 0 end as newblock
    from test t
    order by "Start";
    
    Start     Description MaximunRow SequentialOrder  RN   NEWBLOCK
    --------- ----------- ---------- --------------- --- ----------
    12-JUN-15 A                    3               3   1          0
    13-JUN-15 A                    3               4   2          1
    14-JUN-15 A                    3               5   3          1
    01-JUL-15 A                    3               4   4          0
    02-JUL-15 A                    3               3   5          0
    04-JUL-15 A                    3               4   6          1
    01-AUG-15 B                    2               5   1          0
    16-AUG-15 B                    2               7   2          1
    

    その後、再帰CTE を使用できます。 (11gR2以降)それに基づく:

    with u as (
      select t.*,
        row_number() over (partition by "Description" order by "Start") as rn,
        case when lag("SequentialOrder")
          over (partition by "Description" order by "Start") < "SequentialOrder"
          then 1 else 0 end as newblock
      from test t
    ),
    r ("Start", "Description", "MaximunRow", "SequentialOrder", rn, blocknum,
      pos, lastmaxrow) as (
      select u."Start", u."Description", u."MaximunRow", u."SequentialOrder", u.rn,
        1, 1, u."MaximunRow"
      from u
      where rn = 1
      union all
      select u."Start", u."Description", u."MaximunRow", u."SequentialOrder", u.rn,
        case when r.pos = r.lastmaxrow or u.newblock = 0
          then r.blocknum + 1 else r.blocknum end,
        case when r.pos = r.lastmaxrow or u.newblock = 0
          then 1 else r.pos + 1 end,
        case when r.pos = r.lastmaxrow or u.newblock = 0
          then r.lastmaxrow else u."MaximunRow" end
      from r
      join u on u."Description" = r."Description" and u.rn = r.rn + 1
    )
    select * from r
    order by "Start";
    
    Start     Description MaximunRow SequentialOrder  RN   BLOCKNUM  POS LASTMAXROW
    --------- ----------- ---------- --------------- --- ---------- ---- ----------
    12-JUN-15 A                    3               3   1          1    1          3
    13-JUN-15 A                    3               4   2          1    2          3
    14-JUN-15 A                    3               5   3          1    3          3
    01-JUL-15 A                    3               4   4          2    1          3
    02-JUL-15 A                    3               3   5          3    1          3
    04-JUL-15 A                    3               4   6          3    2          3
    01-AUG-15 B                    2               5   1          1    1          2
    16-AUG-15 B                    2               7   2          1    2          2
    

    これはblocknumを割り当てています 各行に、アンカーメンバーの説明ごとに1つから始まり、newblockの場合は再帰メンバーでインクリメントされます。 がゼロ(シーケンスブレークを示す)であるか、ブロック内のメンバーの数が以前の最大値です。 (質問で明確でないため、「前の最大値」のロジックが完全に正しくない可能性があります。)

    次に、説明と生成されたブロック番号でグループ化できます。

    with u as (
      select t.*,
        row_number() over (partition by "Description" order by "Start") as rn,
        case when lag("SequentialOrder")
          over (partition by "Description" order by "Start") < "SequentialOrder"
          then 1 else 0 end as newblock
      from test t
    ),
    r ("Start", "Description", "MaximunRow", "SequentialOrder", rn, blocknum,
      pos, lastmaxrow) as (
      select u."Start", u."Description", u."MaximunRow", u."SequentialOrder", u.rn,
        1, 1, u."MaximunRow"
      from u
      where rn = 1
      union all
      select u."Start", u."Description", u."MaximunRow", u."SequentialOrder", u.rn,
        case when r.pos = r.lastmaxrow or u.newblock = 0
          then r.blocknum + 1 else r.blocknum end,
        case when r.pos = r.lastmaxrow or u.newblock = 0
          then 1 else r.pos + 1 end,
        case when r.pos = r.lastmaxrow or u.newblock = 0
          then r.lastmaxrow else u."MaximunRow" end
      from r
      join u on u."Description" = r."Description" and u.rn = r.rn + 1
    )
    select min(r."Start") as "Start", max(r."Start") as "End", r."Description"
    from r
    group by r."Description", r.blocknum
    order by r."Description", r.blocknum;
    
    Start     End       Description
    --------- --------- -----------
    12-JUN-15 14-JUN-15 A          
    01-JUL-15 01-JUL-15 A          
    02-JUL-15 04-JUL-15 A          
    01-AUG-15 16-AUG-15 B          
    

    とにかく3より長いシーケンスがないため、サンプルデータは最大行ブレークをトリガーしません。いくつかの追加データあり:

    Insert into TEST ("Start","Description","MaximunRow","SequentialOrder") values (to_date('15-JUN-15','DD-MON-RR'),'A',3,7);
    Insert into TEST ("Start","Description","MaximunRow","SequentialOrder") values (to_date('16-JUN-15','DD-MON-RR'),'A',3,8);
    Insert into TEST ("Start","Description","MaximunRow","SequentialOrder") values (to_date('17-JUN-15','DD-MON-RR'),'A',3,10);
    Insert into TEST ("Start","Description","MaximunRow","SequentialOrder") values (to_date('18-JUN-15','DD-MON-RR'),'A',3,12);
    Insert into TEST ("Start","Description","MaximunRow","SequentialOrder") values (to_date('19-JUN-15','DD-MON-RR'),'A',3,13);
    

    同じクエリが取得します:

    Start     End       Description
    --------- --------- -----------
    12-JUN-15 14-JUN-15 A          
    15-JUN-15 17-JUN-15 A          
    18-JUN-15 19-JUN-15 A          
    01-JUL-15 01-JUL-15 A          
    02-JUL-15 04-JUL-15 A          
    01-AUG-15 16-AUG-15 B          
    

    シーケンスの変更で分割されていることがわかります ブロック内の3行をヒットしたとき。

    SQLFiddleデモ

    newblockを使用する代わりに、caseステートメントで順序を直接比較することで、前の中間CTEではなく、再帰CTEだけを使用することができます。;しかし、rn 次の行は連続していないため、次の行を見つけるよりも簡単です。




    1. 存在する場合の使用方法-PL/SQLに存在しない場合は?

    2. クエリ内のMySQLgroup_concat_max_len

    3. 3つのテーブルを持つ複雑なIFステートメント

    4. PostgreSQLクエリのWHERE日付が3年以上経過している