2011-10-19 21 views
4

私は数日間この問題に取り組んできましたが、今は大衆に助けを求めています。複数のスパンを1つのタイムライン(Oracle 11g)にマージ/分割する方法は?

このサイト上の以前の解決策として、私の問題は、類似したが、全く同じではありません。私の問題は、その一部だけでなく、マージを必要とするのに対し、 PL/SQL Split, separate a date into new dates according to black out dates! このソリューションは、(含める/除外)ではなくブール値です。

私は、SQL + PL/SQLの中間的/高度な把握があると思っていますが、Oracle Analytic関数が明らかに私の心を揺さぶっています。私は読んでいる/学ぶことを試みてきたが、私は時間がなくなっている。

テーブル名(COTS)、ビジネスラインなどを共有することの合法性がわからないので、私はあいまいなシナリオ/コンテキストで自分の問題を模倣します。うまくいけばそれは弁護士の精神を払拭するでしょう。

問題: 私は顧客の活動履歴を格納したテーブルを持っています。顧客は出入りすることができるので、この表には(顧客ごとに)複数の行がある可能性があります。

CREATE TABLE activity AS 
SELECT 1 AS cust_id, 
     TO_DATE('01-JAN-2010') AS start_dt, 
     TO_DATE('31-JUL-2010') AS end_dt, 
     'EAST' AS region 
FROM DUAL 
UNION 
SELECT 1 AS cust_id, 
     TO_DATE('01-FEB-2011') AS start_dt, 
     TO_DATE('31-DEC-2011') AS end_dt, 
     'EAST' AS region 
FROM DUAL; 

また、スパンごとに属性情報を格納するテーブルもあります。顧客は、一度に複数の属性タイプを持つことができ、それぞれのタイプをさまざまなタイムパンに対して複数回持つことができます。

CREATE TABLE attrib AS 
SELECT 1 AS cust_id, 
     'POWER' AS atb_cd, 
     TO_DATE('01-JAN-2009') AS atb_start_dt, 
     TO_DATE('31-JAN-2010') AS atb_end_dt, 
     'LocalNuke' AS provider, 
     1.80 AS per_kwh, 
     0 AS per_gal 
FROM DUAL 
UNION 
SELECT 1 AS cust_id, 
     'POWER' AS atb_cd, 
     TO_DATE('01-MAR-2010') AS atb_start_dt, 
     TO_DATE('31-MAR-2010') AS atb_end_dt, 
     'CoalGuys' AS provider, 
     1.60 AS per_kwh, 
     0 AS per_gal 
FROM DUAL 
UNION 
SELECT 1 AS cust_id, 
     'POWER' AS atb_cd, 
     TO_DATE('01-JUN-2010') AS atb_start_dt, 
     TO_DATE('30-SEP-2010') AS atb_end_dt, 
     'LocalNuke' AS provider, 
     1.70 AS per_kwh, 
     0 AS per_gal 
FROM DUAL 
UNION 
SELECT 1 AS cust_id, 
     'POWER' AS atb_cd, 
     TO_DATE('01-MAR-2011') AS atb_start_dt, 
     TO_DATE('31-DEC-9999') AS atb_end_dt, 
     'GeoHeat' AS provider, 
     1.10 AS per_kwh, 
     0 AS per_gal 
FROM DUAL 
UNION 
SELECT 1 AS cust_id, 
     'WATER' AS atb_cd, 
     TO_DATE('01-MAR-2010') AS atb_start_dt, 
     TO_DATE('31-DEC-9999') AS atb_end_dt, 
     'GlacialGold' AS provider, 
     0 AS per_kwh, 
     0.60 AS per_gal 
FROM DUAL; 

データ奇妙は私ができるように私は、「現実の世界」に関連されることなく、現実世界のように、このシナリオを作ってみました、意図的です。

この結果は、架空の会社との顧客の活動にスパンを制限し、すべての重複する日付を分割してタイムラインを形成する必要があります。データ要素は、報告のために併合する必要があります。

視覚:

Cust: 
     |----------------------|    |------------------------| 
Power: 
|-------------| |--| |-------|    |----------------------> 
Water: 
        |------------------------------------------------------>  
Expected Result: 
     |----|----|--|----|----|    |----|-------------------| 

ソリューションは、他の属性を含めるようにスケーラブルでなければなりません。最後に、この非正規化情報をテーブルに入れて、いつでも顧客のデータを報告することができました。たとえば、特定の日に活動、電力、水があった場合、その日のper_kwh、per_gal、およびactivityデータをエクスポートできるはずです。

の出力例(表形式):スロー・バイ・スロー2つの非同期カーソル処理を使用して(行・バイ(要件はちょうどアクティビティ/パワーに似ていたとき)

CUST_ID FROM_DT  THRU_DT  REGION POWER_PROVIDER WATER_PROVIDER PER_KWH PER_GAL 
------- ----------- ----------- ------ -------------- -------------- ------- ------- 
1  01-JAN-2010 31-JAN-2010 EAST LocalNuke      1.80  0 
1  01-FEB-2010 28-FEB-2010 EAST         0  0 
1  01-MAR-2010 31-MAR-2010 EAST CoalGuys  GlacialGold  1.60  0.60 
1  01-APR-2010 31-MAY-2010 EAST     GlacialGold  0  0.60 
1  01-JUN-2010 31-JUL-2010 EAST LocalNuke  GlacialGold  1.70  0.60 
1  01-FEB-2011 28-FEB-2011 EAST     GlacialGold  0  0.60 
1  01-MAR-2011 31-DEC-2011 EAST GeoHeat   GlacialGold  1.10  0.60 

は、私は約2年前に何かを書きました-行)。

パフォーマンスは重要ですが、ストレート/バルクSQLソリューションを見つけようとしている最大の理由はメンテナンスです。私の元のソリューションのif/elseカーソルネストは、すでにそれに従うのが難しく、少なくとも2つ以上の "属性"スパンで分割することにより、指数関数的に悪化します。

あなたが提供できるすべてのヘルプに感謝します。

+0

あなたが望むクエリの出力を投稿できますか?ビジュアルは役に立ちますが、ビジュアルを表形式のクエリ結果に変換する方法を理解していません。 –

+0

期待される表出力を含めるように変更された質問 - @JustinCaveさんが推奨する – Brock

答えて

1

これが機能する場合があります。連続した領域を結合するわけではありませんが、それでもジョブは完了するはずです。

WITH 

    milestone AS 
    (
    SELECT cust_id, start_dt  AS point_in_time FROM ACTIVITY 
    UNION 
    SELECT cust_id, atb_start_dt AS point_in_time FROM ATTRIB 
    UNION 
    SELECT cust_id, LEAST(end_dt,  TO_DATE('30-DEC-9999')) + 1 AS point_in_time FROM ACTIVITY 
    UNION 
    SELECT cust_id, LEAST(atb_end_dt, TO_DATE('30-DEC-9999')) + 1 AS point_in_time FROM ATTRIB 
) 

SELECT 
    milestone.cust_id     AS cust_id, 
    milestone.point_in_time   AS from_dt, 
    LEAD(point_in_time) 
    OVER (PARTITION BY milestone.cust_id ORDER BY milestone.point_in_time) - 1 
            AS thru_dt, 
    activity.region     AS region, 
    power_attrib.provider    AS power_provider, 
    water_attrib.provider    AS water_provider, 
    COALESCE(power_attrib.per_kwh, 0) AS per_kwh, 
    COALESCE(water_attrib.per_gal, 0) AS per_gal 
FROM 
    MILESTONE 

    LEFT OUTER JOIN ACTIVITY 
    ON milestone.cust_id = activity.cust_id 
     AND milestone.point_in_time BETWEEN activity.start_dt AND activity.end_dt 

    LEFT OUTER JOIN ATTRIB power_attrib 
    ON milestone.cust_id = power_attrib.cust_id 
     AND power_attrib.atb_cd = 'POWER' 
     AND milestone.point_in_time BETWEEN power_attrib.atb_start_dt AND power_attrib.atb_end_dt 

    LEFT OUTER JOIN ATTRIB water_attrib 
    ON milestone.cust_id = water_attrib.cust_id 
     AND water_attrib.atb_cd = 'WATER' 
     AND milestone.point_in_time BETWEEN water_attrib.atb_start_dt AND water_attrib.atb_end_dt 
+0

ありがとうございました!私は、あなたのフィードバックを理解/ディスレビューし、適用する時間が必要です。後で結果と(成功した場合)賞の回答で更新します。 – Brock

+0

これは非常に有望です。私は私のシステムのためにそれを書き直して、いくつかのことを微調整しなければならなかった(非アクティブな行を望まない)。私はまだ答えを承認する前に明日もう少しテストをしたいと思っています(a.k.a.私はまだあなたの注意を抱いていますが)。あなたのソリューションはまっすぐ進む(エレガントな)私は自分で蹴りたい! – Brock

+0

@Brock:喜んで助けになる。私は先日これを解決しなければなりませんでした。 –

1

これは実際には非常に厄介な問題であり、あなたは大きな混乱の原因となることが予想されます。あなたが持つ中心的な問題は、あなたがattribテーブルのギャップのための "psudeo"行を作る必要があるということです。これは問題である。

私はあなたの問題のカットダウンバージョンを取っただけで、POWER属性のギャップを製造しようとしました。私はattiduteを取って、すべての帰属列に先行することができるようにしました。

  • 私はあなたのサンプルの結果の行5は、間違った終了日を持っていると思う:この

    SELECT PS.cust_id 
        , G.is_gap 
        , DECODE(G.is_gap, 'Y', PS.prev_start, PS.atb_start_dt) AS start_date 
        , DECODE(G.is_gap, 'Y', PS.prev_end, PS.atb_end_dt) AS end_date 
        , DECODE(G.is_gap, 'Y', NULL, PS.provider) AS provider 
        , DECODE(G.is_gap, 'Y', NULL, PS.per_kwh) AS per_kwh 
        , DECODE(G.is_gap, 'Y', NULL, PS.per_gal) AS per_gal 
    FROM 
        ( SELECT P.cust_id 
          , P.atb_start_dt 
          , P.atb_end_dt 
          , P.provider 
          , P.per_kwh 
          , P.per_gal 
          , P.atb_start_dt - 1  AS prev_end 
          , NVL(MAX(P.atb_end_dt) OVER (ORDER BY P.atb_end_dt 
             ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) + 1 
            , '01-JAN-1900') AS prev_start 
         FROM attrib  P 
         WHERE P.atb_cd = 'POWER' 
        ) PS 
    , ( SELECT DECODE(LEVEL,1,'Y','N') AS is_gap 
         FROM DUAL 
         CONNECT BY LEVEL <= 2 
        ) G 
    WHERE ( PS.prev_end > PS.prev_start 
         OR G.is_gap = 'N') 
    ORDER BY 3 
    /
    

    を思い付いは私にこれらの結果

    CUST_ID I START_DATE END_DATE PROVIDER PER_KWH PER_GAL 
    ------- - ---------- ---------- ----------- ------- ------- 
         1 Y 01-JAN-00 31-DEC-08 
         1 N 01-JAN-09 31-JAN-10 LocalNuke 1.8  0 
         1 N 01-FEB-10 31-MAR-10 CoalGuys 1.6  0 
         1 Y 01-APR-10 31-MAY-10 
         1 N 01-JUN-10 30-SEP-10 LocalNuke 1.7  0 
         1 Y 01-OCT-10 28-FEB-11 
         1 N 01-MAR-11 31-DEC-99 GeoHeat  1.1  0 
    

    いくつかの注意を与えました。 activityが終了したので、それは31-JUL-2010である必要がありますか?

  • 私はそれが1つだけ先行、トレーリングギャップを生成しないので、遠い将来に走るアクティビティがない場合
  • がネジ止めされるのでしょう隙間がなかった時にテストするために01-FEB-2010CoalGuys開始日を更新しました。いつもUNIONが1つだと思います。
  • 9999は何も試してみるとエラーになるので、年として9999を使用しないでください。問題になることはありませんでしたが、後でギャップを埋めるために行くならば、驚異でした。

これは完全な解決策から離れています。一度顧客に投げ込むと、それはずっと難しくなります。しかし、マスタークエリに含めるには、おそらく上記のインラインビューとしての勇気が必要です。それから、水のために同じことをしなければならないでしょう。その後、日付範囲のチェックで2つを一緒に参加させ、最後の日付の結果にLEASTGREATESTを使用する必要があります。

申し訳ありませんが、私はこれを苦労の問題から仕事のように感じていたので、私の答えは不完全なままにしました。それが役に立てば幸い。

+1

はい、期待される出力が正しくない行5について正しくあります。私は今それを修正します。それは消化して、試してみるのに私はしばらく時間がかかるでしょう...しかし、私はノートと思考プロセスを提供していただき、ありがとうございました。たとえあなたのコメントがわからなくても、アナリティクスを使ってこのような新しい考え方を練るのに役立ちます。 – Brock

関連する問題