2

この質問は、私が最近投稿した別の質問と非常に関連していますが、解決が少し複雑になるため、新しい質問を投稿しています。オラクルの忍者やロックスターの助けを求めていますが、それは彼らの専門知識に挑戦し、実践するのに良いことだと思います。

基本的に、TableAとTableBの2つのテーブルがあります。

-- For TableA
CREATE TABLE TableA
(
  ID          VARCHAR2(10),
  LOCN        VARCHAR2(10),
  START_DATE  DATE,
  END_DATE    DATE
)
STORAGE    (
            BUFFER_POOL      DEFAULT
           )
LOGGING
NOCOMPRESS
NOCACHE
NOPARALLEL
NOMONITORING
/


-- Populate TableA
INSERT INTO TableA(ID, LOCN, START_DATE, END_DATE)
VALUES('1P1',   '01',   TO_DATE('02/04/1996', 'MM/DD/YYYY'),  TO_DATE('02/22/1996', 'MM/DD/YYYY');


INSERT INTO TableA(ID, LOCN, START_DATE, END_DATE)
VALUES('1P1',   '01',   TO_DATE('02/23/1996', 'MM/DD/YYYY'),  TO_DATE('05/28/2002', 'MM/DD/YYYY');


INSERT INTO TableA(ID, LOCN, START_DATE, END_DATE)
VALUES('1P1',   '01',   TO_DATE('05/29/2002', 'MM/DD/YYYY'),  TO_DATE('05/03/2005', 'MM/DD/YYYY');


INSERT INTO TableA(ID, LOCN, START_DATE, END_DATE)
VALUES('1P1',   '01',   TO_DATE('05/04/2005', 'MM/DD/YYYY'),  TO_DATE('05/04/2005', 'MM/DD/YYYY');


INSERT INTO TableA(ID, LOCN, START_DATE, END_DATE)
VALUES('1P2',   '30',   TO_DATE('01/31/1996', 'MM/DD/YYYY'),  TO_DATE('02/06/1996', 'MM/DD/YYYY');


INSERT INTO TableA(ID, LOCN, START_DATE, END_DATE)
VALUES('1P2',   '02',   TO_DATE('02/07/1996', 'MM/DD/YYYY'),  TO_DATE('02/13/1996', 'MM/DD/YYYY');


INSERT INTO TableA(ID, LOCN, START_DATE, END_DATE)
VALUES('1P2',   '02',   TO_DATE('02/14/1996', 'MM/DD/YYYY'),  TO_DATE('01/01/2099', 'MM/DD/YYYY');


INSERT INTO TableA(ID, LOCN, START_DATE, END_DATE)
VALUES('1P3',   '03',   TO_DATE('02/07/1996', 'MM/DD/YYYY'),  TO_DATE('02/13/1996', 'MM/DD/YYYY');


INSERT INTO TableA(ID, LOCN, START_DATE, END_DATE)
VALUES('1P3',   '03',   TO_DATE('02/14/1996', 'MM/DD/YYYY'),  TO_DATE('01/01/2099', 'MM/DD/YYYY');


INSERT INTO TableA(ID, LOCN, START_DATE, END_DATE)
VALUES('1S4',   '42',   TO_DATE('11/06/2001', 'MM/DD/YYYY'),  TO_DATE('01/01/2099', 'MM/DD/YYYY');


INSERT INTO TableA(ID, LOCN, START_DATE, END_DATE)
VALUES('3S4',   '42',   TO_DATE('11/06/2001', 'MM/DD/YYYY'),  TO_DATE('01/01/2099', 'MM/DD/YYYY');



-- For TableB
CREATE TABLE TableB
(
  ID           VARCHAR2(10),
  POSTING      VARCHAR2(20),
  DESCRIPTION  VARCHAR2(100),
  OTHER_ID     VARCHAR2(10),
  START_DATE   DATE,
  END_DATE     DATE
)
STORAGE    (
            BUFFER_POOL      DEFAULT
           )
LOGGING
NOCOMPRESS
NOCACHE
NOPARALLEL
NOMONITORING
/


INSERT INTO TableB(ID, POSTING, DESCRIPTION, OTHER_ID, START_DATE, END_DATE)
VALUES('1P1', 'PROFESSOR', 'Sch 1 Quad 1 Area', 'P1', '02/04/1996', '01/01/2099');

INSERT INTO TableB(ID, POSTING, DESCRIPTION, OTHER_ID, START_DATE, END_DATE)
VALUES('1P2', 'PROFESSOR', 'Sch 1 Quad 2 Area', 'P2', '01/31/1996', '01/01/2099');

INSERT INTO TableB(ID, POSTING, DESCRIPTION, OTHER_ID, START_DATE, END_DATE)
VALUES('1P3', 'PROFESSOR', 'Sch 1 Quad 3 Area', 'P3', '02/05/1996', '01/01/2099');

INSERT INTO TableB(ID, POSTING, DESCRIPTION, OTHER_ID, START_DATE, END_DATE)
VALUES('1S4', 'SUPERVISOR', 'Sch 1 CO Supervisor 4', '1S4', '02/05/1996', '03/18/2002');

INSERT INTO TableB(ID, POSTING, DESCRIPTION, OTHER_ID, START_DATE, END_DATE)
VALUES('1S4', 'SUPERINTENDENT', 'Sch 1 CD Superintendent', '1S4', '03/19/2002', '06/09/2009');

INSERT INTO TableB(ID, POSTING, DESCRIPTION, OTHER_ID, START_DATE, END_DATE)
VALUES('1S4', 'SUPERVISOR', 'Sch 1 CO Supervisor 4', '1S4', '06/10/2009', '01/01/2099');

INSERT INTO TableB(ID, POSTING, DESCRIPTION, OTHER_ID, START_DATE, END_DATE)
VALUES('2S5', 'SUPERVISOR', 'Sch 2 CAO Supervisor 5', '2S5', '10/26/2002', '06/09/2009');

INSERT INTO TableB(ID, POSTING, DESCRIPTION, OTHER_ID, START_DATE, END_DATE)
VALUES('2S5', 'SUPERINTENDENT', 'Sch 2 CAO Superintendent 5', '2S5', '06/10/2009', '07/14/2009');

INSERT INTO TableB(ID, POSTING, DESCRIPTION, OTHER_ID, START_DATE, END_DATE)
VALUES('2S5', 'SUPERINTENDENT', 'Sch 2 CAO Superintendent 5', 'S5', '07/15/2009', '01/01/2099');

INSERT INTO TableB(ID, POSTING, DESCRIPTION, OTHER_ID, START_DATE, END_DATE)
VALUES('3S4', 'SUPERVISOR', 'Sch 3 CO Supervisor 4', '3S4', '02/05/1996', '03/18/2002');

INSERT INTO TableB(ID, POSTING, DESCRIPTION, OTHER_ID, START_DATE, END_DATE)
VALUES('3S4', 'SUPERINTENDENT', 'Sch 3 CD Superintendent', '3S4', '03/19/2002', '06/09/2009');

INSERT INTO TableB(ID, POSTING, DESCRIPTION, OTHER_ID, START_DATE, END_DATE)
VALUES('3S4', 'SUPERVISOR', 'Sch 3 CO Supervisor 4', '3S4', '06/10/2009', '01/01/2099');

プロセスは次のようになります。TableAでは、同じID、LOCNを持ち、START_DATEとEND_DATEの日付が連続しているすべてのレコードが結合されます。

ID  LOCN    START_DATE  END_DATE
1P1 01      02/04/1996  05/04/2005
1P2 30      01/31/1996  02/06/1996
1P2 02      02/07/1996  01/01/2099
1P3 03      02/07/1996  01/01/2099
1S4 42      11/06/2001  01/01/2099
3S4 42      11/06/2001  01/01/2099

TableBでは、同じID、POSTING、OTHER_ID、および連続するSTART_DATEとEND_DATEを持つすべてのレコードも結合されます。(とにかく、この表から組み合わせることができるデータはないと思います)。

ID  POSTING         DESCRIPTION                 OTHER_ID    START_DATE  END_DATE
1P1 PROFESSOR       Sch 1 Quad 1 Area           P1          02/04/1996  01/01/2099
1P2 PROFESSOR       Sch 1 Quad 2 Area           P2          01/31/1996  01/01/2099
1P3 PROFESSOR       Sch 1 Quad 3 Area           P3          02/05/1996  01/01/2099
1S4 SUPERVISOR      Sch 1 CO Supervisor 4       1S4         02/05/1996  03/18/2002
1S4 SUPERINTENDENT  Sch 1 CD Superintendent     1S4         03/19/2002  06/09/2009
1S4 SUPERVISOR      Sch 1 CO Supervisor 4       1S4         06/10/2009  01/01/2099
2S5 SUPERVISOR      Sch 2 CAO Supervisor 5      2S5         10/26/2002  06/09/2009
2S5 SUPERINTENDENT  Sch 2 CAO Superintendent 5  2S5         06/10/2009  07/14/2009
2S5 SUPERINTENDENT  Sch 2 CAO Superintendent 5  S5          07/15/2009  01/01/2099
3S4 SUPERVISOR      Sch 3 CO Supervisor 4       3S4         02/05/1996  03/18/2002
3S4 SUPERINTENDENT  Sch 3 CD Superintendent     3S4         03/19/2002  06/09/2009
3S4 SUPERVISOR      Sch 3 CO Supervisor 4       3S4         06/10/2009  01/01/2099

IDに基づいてTableAとTableBのレコードをマージします。LOCN列はテーブルBに追加され、テーブルAの日付範囲に基づいてのみ繰り越されます。結果のデータは次のようになります。

ID  UNIT_TYPE       DESCRIPTION                 OTHER_ID    START_DATE  END_DATE    LOCN
1P1 PROFESSOR       Sch 1 Quad 1 Area           P1          02/04/1996  05/04/2005  01
1P1 PROFESSOR       Sch 1 Quad 1 Area           P1          05/05/2005  01/01/2099  {NULL}
1P2 PROFESSOR       Sch 1 Quad 2 Area           P2          01/31/1996  02/06/1996  30
1P2 PROFESSOR       Sch 1 Quad 2 Area           P2          02/07/1996  01/01/2099  02
1P3 PROFESSOR       Sch 1 Quad 3 Area           P3          02/05/1996  02/06/1996  {NULL}
1P3 PROFESSOR       Sch 1 Quad 3 Area           P3          02/07/1996  01/01/2099  03
1S4 SUPERVISOR      Sch 1 CO Supervisor 4       1S4         02/05/1996  11/05/2001  {NULL}
1S4 SUPERVISOR      Sch 1 CO Supervisor 4       1S4         11/06/2001  03/18/2002  42
1S4 SUPERINTENDENT  Sch 1 CD Superintendent     1S4         03/19/2002  06/09/2009  42
1S4 SUPERVISOR      Sch 1 CO Supervisor 4       1S4         06/10/2009  01/01/2099  42
2S5 SUPERVISOR      Sch 2 CAO Supervisor 5      2S5         10/26/2002  06/09/2009  {NULL}
2S5 SUPERINTENDENT  Sch 2 CAO Superintendent 5  2S5         06/10/2009  07/14/2009  {NULL}
2S5 SUPERINTENDENT  Sch 2 CAO Superintendent 5  S5          07/15/2009  01/01/2099  {NULL}
3S4 SUPERVISOR      Sch 3 CO Supervisor 4       3S4         02/05/1996  11/05/2001  {NULL}
3S4 SUPERVISOR      Sch 3 CO Supervisor 4       3S4         11/06/2001  03/18/2002  42
3S4 SUPERINTENDENT  Sch 3 CD Superintendent     3S4         03/19/2002  06/09/2009  42
3S4 SUPERVISOR      Sch 3 CO Supervisor 4       3S4         06/10/2009  01/01/2099  42

これを解決するために実行可能なアプローチを聞きたいです。たくさんあります。

追加:これは、TableAのレコードを折りたたむためにこれまでに作成したクエリです。

SELECT ID, LOCN, TO_CHAR(MIN(START_DATE), 'MM/DD/YYYY') START_DATE, TO_CHAR(MAX(END_DATE), 'MM/DD/YYYY') END_DATE
        FROM
             (
              SELECT ID, LOCN, START_DATE, END_DATE, MAX(GRP) OVER (ORDER BY ID, START_DATE) GRP
              FROM
                  (
                   SELECT ID, LOCN,
                          CASE WHEN START_DATE - LAG(END_DATE) OVER (PARTITION BY ID, LOCN ORDER BY START_DATE ASC) <= 1 THEN
                            NULL
                          ELSE
                            ROWNUM
                          END GRP,
                          START_DATE,
                          NVL(END_DATE, SYSDATE) END_DATE
                   FROM TableA
                   ORDER BY ID ASC, START_DATE ASC
                  )
             )
        GROUP BY ID, LOCN, GRP
        ORDER BY ID ASC, START_DATE ASC;
4

1 に答える 1

4

ロックスターは堕落した(稼いだら)ライフスタイルをリードするのに忙しく、忍者はしばらく忙しいように見えるので、私は行きます...

あなたがそれをレイアウトした方法で、あなたはTableA最初に隣接するレコードを折りたたんで、その結果を(おそらく折りたたまれた)に対して使用したいと思いますTableB。全体的な問題を解決するために別のステップが理想的であるかどうかはわかりませんが、今はそれを使用します。行を折りたたむのが最も簡単だと私が見つけた一般的な方法は、次のようなものです。

select id, locn, max(start_date) as start_date, max(end_date) as end_date
from (
    select id, locn,
        case when start_date = lag_end_date  + interval '1' day then null
            else start_date end as start_date,
        case when end_date = lead_start_date - interval '1' day then null
            else end_date end as end_date,
        row_number() over (partition by id order by start_date)
            - row_number() over (partition by id, locn
                order by start_date) as chain
    from (
        select id, locn, start_date, end_date,
            lead(start_date) over (partition by id, locn
                order by start_date) as lead_start_date,
            lag(end_date) over (partition by id, locn
                order by start_date) as lag_end_date
        from TableA
    )   
)
group by id, locn, chain
order by 1, 3, 2;

ID         LOCN       START_DATE END_DATE
---------- ---------- ---------- ----------
1P1        01         02/04/1996 05/04/2005
1P2        02         02/07/1996 01/01/2099
1P2        30         01/31/1996 02/06/1996
1P3        03         02/07/1996 01/01/2099
1S4        42         11/06/2001 01/01/2099
3S4        42         11/06/2001 01/01/2099

最も内側のselect使用法leadlag隣接する行をのぞくために(そしてあなたは前の質問でそれをほのめかしました)。

次のレイヤーは、連続する値(つまり、1つの行の開始日が前の行の終了日の翌日)をnullに設定します。その部分だけを実行すると、連続した範囲の開始と終了が表示されます。また、以前に使用したものへの切り替えchainに対処できる疑似列を追加します。に戻ったと言います。(これは私が最初にここで見たアプローチですが、ギャップと島についてももっと見てください)。それがないと、のすべての「島」が1つのブロックとして扱われ、日付範囲が重複することになります。idlocn1P2locn=30id/locn

外層ユーザーminmaxnullを削除し、最終結果を生成します。

これを使用すると、11gR2を使用している場合は、再帰CTEを使用して再帰的に結合し、すべての組み合わせを取得できます。これは、これらのうちの1つでの私の2回目の実際の刺し傷であるため、他の人がM&Mから自分自身を引き離すことができれば、欠陥や改善点を指摘する可能性があります...

with a as (
    select id, locn, max(start_date) as start_date, max(end_date) as end_date
    from (
        select id, locn,
            case when start_date = lag_end_date  + interval '1' day then null
                else start_date end as start_date,
            case when end_date = lead_start_date - interval '1' day then null
                else end_date end as end_date,
            row_number() over (partition by id order by start_date)
                - row_number() over (partition by id, locn
                    order by start_date) as chain
        from (
            select id, locn, start_date, end_date,
                lead(start_date) over (partition by id, locn
                    order by start_date) as lead_start_date,
                lag(end_date) over (partition by id, locn
                    order by start_date) as lag_end_date
            from TableA
        )
    )
    group by id, locn, chain
),
b as (
    select id, posting, description, other_id, start_date, end_date,
        row_number() over (partition by id, posting, description,
            other_id order by start_date, end_date) as rn
    from TableB
),
r (id, posting, description, other_id, rn, start_date, end_date, locn) as (
    select b.id, b.posting, b.description, b.other_id, b.rn,
        b.start_date,
        case
            when not (a.start_date > b.end_date or a.end_date < b.start_date)
                and a.start_date <= b.end_date and a.end_date < b.end_date
                then a.end_date
            when not (a.start_date > b.end_date or a.end_date < b.start_date)
                and a.start_date <= b.end_date and a.start_date > b.start_date
                then a.start_date - interval '1' day
            else b.end_date
        end as end_date,
        case
            when a.start_date <= b.start_date and a.end_date >= b.start_date
                then a.locn
        end
    from b
    left join (
        select id, locn, start_date, end_date,
            row_number() over (partition by id order by start_date) as rn
        from a
    ) a on a.id = b.id
        and a.rn = 1
    union all
    select b.id, b.posting, b.description, b.other_id, b.rn,
        case
            when a.start_date is null then r.end_date + interval '1' day
            else a.start_date
        end as start_date,
        case
            when a.start_date is null then b.end_date
            when not (a.start_date > r.end_date or a.end_date < r.start_date)
                then least(a.end_date, b.end_date)
            when a.end_date < b.end_date then a.start_date - interval '1' day
            else b.end_date
        end as end_date,
        a.locn
    from b
    join r on r.id = b.id
        and r.posting = b.posting
        and r.description = b.description
        and r.other_id = b.other_id
        and r.rn = b.rn
        and r.start_date = b.start_date
        and r.end_date < b.end_date
    left join a on a.id = r.id
        and a.start_date > r.end_date
) 
select id, posting as unit_type, description, other_id,
    start_date, end_date, locn
from r
order by id, start_date;

これはあなたが私が信じたいと思った結果を得る:

ID         UNIT_TYPE            DESCRIPTION                    OTHER_ID   START_DATE END_DATE   LOCN
---------- -------------------- ------------------------------ ---------- ---------- ---------- ----------
1P1        PROFESSOR            Sch 1 Quad 1 Area              P1         02/04/1996 05/04/2005 01
1P1        PROFESSOR            Sch 1 Quad 1 Area              P1         05/05/2005 01/01/2099
1P2        PROFESSOR            Sch 1 Quad 2 Area              P2         01/31/1996 02/06/1996 30
1P2        PROFESSOR            Sch 1 Quad 2 Area              P2         02/07/1996 01/01/2099 02
1P3        PROFESSOR            Sch 1 Quad 3 Area              P3         02/05/1996 02/06/1996
1P3        PROFESSOR            Sch 1 Quad 3 Area              P3         02/07/1996 01/01/2099 03
1S4        SUPERVISOR           Sch 1 CO Supervisor 4          1S4        02/05/1996 11/05/2001
1S4        SUPERVISOR           Sch 1 CO Supervisor 4          1S4        11/06/2001 03/18/2002 42
1S4        SUPERINTENDENT       Sch 1 CD Superintendent        1S4        03/19/2002 06/09/2009 42
1S4        SUPERVISOR           Sch 1 CO Supervisor 4          1S4        06/10/2009 01/01/2099 42
2S5        SUPERVISOR           Sch 2 CAO Supervisor 5         2S5        10/26/2002 06/09/2009
2S5        SUPERINTENDENT       Sch 2 CAO Superintendent 5     2S5        06/10/2009 07/14/2009
2S5        SUPERINTENDENT       Sch 2 CAO Superintendent 5     S5         07/15/2009 01/01/2099
3S4        SUPERVISOR           Sch 3 CO Supervisor 4          3S4        02/05/1996 11/05/2001
3S4        SUPERVISOR           Sch 3 CO Supervisor 4          3S4        11/06/2001 03/18/2002 42
3S4        SUPERINTENDENT       Sch 3 CD Superintendent        3S4        03/19/2002 06/09/2009 42
3S4        SUPERVISOR           Sch 3 CO Supervisor 4          3S4        06/10/2009 01/01/2099 42

17 rows selected.

これは3つのCTEを使用しています。a上記のように、の折りたたみバージョンですTableAbただし、行番号の列がTableB追加されたので、再帰中にレコードを段階的に保持する必要があると思います。r楽しみが始まるところです。

の最初の部分はr、各エントリの初期データを生成します。必要に応じTableBて、一致する値を使用しますTableA。ただし、複数ある可能性がある場合は、最初の一致するレコードからのみです。ここで注意が必要なのは、どうあるend_dateべきかを理解することです。重複するTableAレコードがまったくない場合は、TableB終了日である可能性があります。あるがレコードの後に​​開始する場合、TableBこれはキックインの直前に終了する必要がTableAあります。それ以外の場合は、TableAレコードがレコードの前に終了するか、後に終了するかによって異なりTableBます。

その部分だけを実行する:

with a as (...), b as (...)
select b.id, b.posting, b.description, b.other_id, b.rn,
    b.start_date,
    case
        when not (a.start_date > b.end_date or a.end_date < b.start_date)
            and a.start_date <= b.end_date and a.end_date < b.end_date
            then a.end_date
        when not (a.start_date > b.end_date or a.end_date < b.start_date)
            and a.start_date <= b.end_date and a.start_date > b.start_date
            then a.start_date - interval '1' day
        else b.end_date
    end as end_date,
    case
        when a.start_date <= b.start_date and a.end_date >= b.start_date
            then a.locn
    end
from b
left join (
    select id, locn, start_date, end_date,
        row_number() over (partition by id order by start_date) as rn
    from a
) a on a.id = b.id
    and a.rn = 1
order by id, start_date;

...これを与えます(読みやすさのために説明は抑制されています):

ID         UNIT_TYPE            OTHER_ID   START_DATE END_DATE   LOCN
---------- -------------------- ---------- ---------- ---------- ----------
1P1        PROFESSOR            P1         02/04/1996 05/04/2005 01
1P2        PROFESSOR            P2         01/31/1996 02/06/1996 30
1P3        PROFESSOR            P3         02/05/1996 02/06/1996
1S4        SUPERVISOR           1S4        02/05/1996 11/05/2001
1S4        SUPERINTENDENT       1S4        03/19/2002 06/09/2009 42
1S4        SUPERVISOR           1S4        06/10/2009 01/01/2099 42
2S5        SUPERVISOR           2S5        10/26/2002 06/09/2009
2S5        SUPERINTENDENT       2S5        06/10/2009 07/14/2009
2S5        SUPERINTENDENT       S5         07/15/2009 01/01/2099
3S4        SUPERVISOR           3S4        02/05/1996 11/05/2001
3S4        SUPERINTENDENT       3S4        03/19/2002 06/09/2009 42
3S4        SUPERVISOR           3S4        06/10/2009 01/01/2099 42

12 rows selected.

の場合IP3、最初は一致するレコードはありませんが、後で一致するレコードが開始する前日に設定されているTableAことに注意してください。end_date

の2番目の部分でrあるunion all、は再帰的な部分です。各TableBレコードについて、それ自体が結合して、生成されたレコードがend_date元のレコードよりも早いレコードを探します。これは、の場合のようにIP3、まだ入力する期間があることを意味します。次に、適切なTableAレコードを探して生成します。start_dateとの適切な値end_date。これも、レコードが重複しているかどうか、およびどのように重複しているかによって異なります。ここでいくつかのエッジケースを見逃した可能性は十分にあります。

あなたは、崩壊する連続した範囲もあるかもしれないとTableB言いました、そしてあなたはそれに私が示したものと同様の扱いを与えることができますTableA。それを行うことが、それを必要とするテーブルが1つしかない場合でも、必ずしも最良または最も明確なポイントがあるかどうかはわかりません。それがあなたがプロセスを説明した方法だったので、私は実際にそこでそれをしただけです。

再帰CTEをベーステーブルに反するように変更する場合(プロセスでわずかに単純化する可能性があります)、個々のテーブルではなく、ギャップアンドアイランドアプローチをその結果セットに適用できるため、どのテーブルを使用するかは重要ではありません。ギャップはによって引き起こされます。

于 2012-11-11T18:48:54.170 に答える