2016-03-18 12 views
1

ゆっくりと変化するディメンション(タイプ2ディメンション)を作成しようとしていますが、論理的に書き出す方法が少し失われています。穀物がPerson | Country | Department | Login Timeのソーステーブルがあるとします。 Person | Country | Department | Eff Start time | Eff End Timeでこのディメンション表を作成したいとします。変更するたびに列を選択するにはどうすればよいですか?

データは次のようになります。

Person | Country | Department | Login Time 
------------------------------------------ 
Bob | CANADA | Marketing | 2009-01-01 
Bob | CANADA | Marketing | 2009-02-01 
Bob | USA  | Marketing | 2009-03-01 
Bob | USA  | Sales  | 2009-04-01 
Bob | MEX  | Product | 2009-05-01 
Bob | MEX  | Product | 2009-06-01 
Bob | MEX  | Product | 2009-07-01 
Bob | CANADA | Marketing | 2009-08-01 

を私はタイプ2次元で欲しいのは、次のようになります。

Person | Country | Department | Eff Start time | Eff End Time 
------------------------------------------------------------------ 
Bob | CANADA | Marketing | 2009-01-01  | 2009-03-01 
Bob | USA  | Marketing | 2009-03-01  | 2009-04-01 
Bob | USA  | Sales  | 2009-04-01  | 2009-05-01 
Bob | MEX  | Product | 2009-05-01  | 2009-08-01 
Bob | CANADA | Marketing | 2009-08-01  | NULL 

は、ボブの名前、国や部門が更新されていないと仮定します2009-08-01以降です。NULL

ここではどのような機能が最適でしょうか?これはPostgresの風味を使用するNetezza上にあります。

明らかGROUP BYは、後にあるため、同じグループでここで働いていないでしょう(私はこれを示すために、最後の行でBob | CANADA | Marketingに追加。

EDIT

人、国、および部門のハッシュ列を含め、

SELECT PERSON, COUNTRY, DEPARTMENT 
FROM table t1 
where 
    person = person 
    AND t1.hash <> hash_function(person, country, department) 
+0

あなたの要件を詳しく教えてください。変更するたびに列を選択すると、正確に何をしようとしていますか?あなたが望むものの例が役に立つでしょう。 –

+0

ええ、私はより良いタイトルを使用できました。基本的に、私が与えた例の文脈では、私は、歴史のある時点で、あるいは現時点で、人、その国、そしてその部門を見つけたいと思っています。これを行うには、このディメンションが変更される可能性があるため、これらの列が変更されたときにいつでも見つけることができます – simplycoding

+0

指定した編集に対して、 –

答えて

1

回答

create table so (
    person varchar(32) 
    ,country varchar(32) 
    ,department varchar(32) 
    ,login_time date 
) distribute on random; 

insert into so values ('Bob','CANADA','Marketing','2009-01-01'); 
insert into so values ('Bob','CANADA','Marketing','2009-02-01'); 
insert into so values ('Bob','USA','Marketing','2009-03-01'); 
insert into so values ('Bob','USA','Sales','2009-04-01'); 
insert into so values ('Bob','MEX','Product','2009-05-01'); 
insert into so values ('Bob','MEX','Product','2009-06-01'); 
insert into so values ('Bob','MEX','Product','2009-07-01'); 
insert into so values ('Bob','CANADA','Marketing','2009-08-01'); 

/* ************************************************************************** */ 

with prm as (--Create an ordinal primary key. 
    select 
    * 
    ,row_number() over (
     partition by person 
     order by login_time 
    ) rwn 
    from 
    so 
), chn as (--Chain events to their previous and next event. 
    select 
    cur.rwn 
    ,cur.person 
    ,cur.country 
    ,cur.department 
    ,cur.login_time cur_login 
    ,case 
     when 
     cur.country = prv.country 
     and cur.department = prv.department 
     then 1 
     else 0 
    end prv_equal 
    ,case 
     when 
     (
      cur.country = nxt.country 
      and cur.department = nxt.department 
     ) or nxt.rwn is null --No next record should be equivalent to matching. 
     then 1 
     else 0 
    end nxt_equal 
    ,case prv_equal 
     when 0 then cur_login 
     else null 
    end eff_login_start_sparse 
    ,case 
     when eff_login_start_sparse is null 
     then max(eff_login_start_sparse) over (
      partition by cur.person 
      order by rwn 
      rows unbounded preceding --The secret sauce. 
     ) 
     else eff_login_start_sparse 
    end eff_login_start 
    ,case nxt_equal 
     when 0 then cur_login 
     else null 
    end eff_login_end 
    from 
    prm cur 
    left outer join prm nxt on 
     cur.person = nxt.person 
     and cur.rwn + 1 = nxt.rwn 
    left outer join prm prv on 
     cur.person = prv.person 
     and cur.rwn - 1 = prv.rwn 
), grp as (--Group by login starts. 
    select 
    person 
    ,country 
    ,department 
    ,eff_login_start 
    ,max(eff_login_end) eff_login_end 
    from 
    chn 
    group by 
    person 
    ,country 
    ,department 
    ,eff_login_start 
), led as (--Change the effective end to be the next start, if desired. 
    select 
    person 
    ,country 
    ,department 
    ,eff_login_start 
    ,case 
     when eff_login_end is null 
     then null 
     else 
     lead(eff_login_start) over (
      partition by person 
      order by eff_login_start 
     ) 
    end eff_login_end 
    from 
    grp 
) 
select * from led order by eff_login_start; 
のロジックを使用しての理にかなって、正しいですか?思考

このコードは、次の表を返します。

PERSON | COUNTRY | DEPARTMENT | EFF_LOGIN_START | EFF_LOGIN_END 
--------+---------+------------+-----------------+--------------- 
Bob | CANADA | Marketing | 2009-01-01  | 2009-03-01 
Bob | USA  | Marketing | 2009-03-01  | 2009-04-01 
Bob | USA  | Sales  | 2009-04-01  | 2009-05-01 
Bob | MEX  | Product | 2009-05-01  | 2009-08-01 
Bob | CANADA | Marketing | 2009-08-01  | 

説明

私は、過去数年間にこの四、五回を解決し、正式にそれを書き留めておく無視している必要があります。私はそれを行うチャンスを持ってうれしいので、これは大きな質問です。

これを試してみると、問題を行列形式で書き留めておきたいと思います。入力は、すべての値がSCDの同じキーを持つと仮定します。私たちは、キー値の上に全体の時間を分割することがあります。Cvは、我々はSCDのキー値は、このデータでは同じであると仮定、もう一度(と比較する必要があります値です

Cv | Ce 
----|---- 
A | 10 
A | 11 
B | 14 
C | 16 
D | 18 
D | 25 
D | 34 
A | 40 

解とは無関係です)、Ceはイベント時間です。

まず、序数の主キーが必要です。私はテーブルにこのCkを指定しました。これにより、前と次のイベントを取得するためにテーブルに参加することができます。私はこれらの列をPk(前のキー)、Nk(次のキー)、Pv、Nvと呼んでいます。

Cv | Ce | Ck | Pk | Pv | Nk | Nv | 
----|----|----|----|----|----|----| 
A | 10 | 1 | | | 2 | A | 
A | 11 | 2 | 1 | A | 3 | B | 
B | 14 | 3 | 2 | A | 4 | C | 
C | 16 | 4 | 3 | B | 5 | D | 
D | 18 | 5 | 4 | C | 6 | D | 
D | 25 | 6 | 5 | D | 7 | D | 
D | 34 | 7 | 6 | D | 8 | A | 
A | 40 | 8 | 7 | D | | | 

今、私たちは、私たちが連続イベントブロックの最初や最後にしているかどうかを確認するためにいくつかの列が必要です。私はこれらのPcとNcを連続して呼びます。 PcはPv = Cv => trueと定義される。 1は真を表し、0は偽を表す。 NCは(私たちは、なぜ分で表示されます)trueにヌルの場合のデフォルトことを除いて、同様に定義されて

Cv | Ce | Ck | Pk | Pv | Nk | Nv | Pc | Nc | 
----|----|----|----|----|----|----|----|----| 
A | 10 | 1 | | | 2 | A | 0 | 1 | 
A | 11 | 2 | 1 | A | 3 | B | 1 | 0 | 
B | 14 | 3 | 2 | A | 4 | C | 0 | 0 | 
C | 16 | 4 | 3 | B | 5 | D | 0 | 0 | 
D | 18 | 5 | 4 | C | 6 | D | 0 | 1 | 
D | 25 | 6 | 5 | D | 7 | D | 1 | 1 | 
D | 34 | 7 | 6 | D | 8 | A | 1 | 0 | 
A | 40 | 8 | 7 | D | | | 0 | 1 | 

今、あなたは、PC、NCの1,1の組み合わせが完全に無用であるかを確認するために開始することができます記録。6行目のBobのMex/Productの組み合わせは、SCDを構築するときにはほとんど役に立たない情報なので、直感的にわかります。

だから、無駄な情報を取り除きましょう。ここでは2つの新しい列を追加します:Snと呼ばれるほぼ完全な有効開始時間とEeという実際に完了した有効終了時間。 Pcが0で、EeとをCEに取り込まれNcが0

Cv | Ce | Ck | Pk | Pv | Nk | Nv | Pc | Nc | Sn | Ee | 
----|----|----|----|----|----|----|----|----|----|----| 
A | 10 | 1 | | | 2 | A | 0 | 1 | 10 | | 
A | 11 | 2 | 1 | A | 3 | B | 1 | 0 | | 11 | 
B | 14 | 3 | 2 | A | 4 | C | 0 | 0 | 14 | 14 | 
C | 16 | 4 | 3 | B | 5 | D | 0 | 0 | 16 | 16 | 
D | 18 | 5 | 4 | C | 6 | D | 0 | 1 | 18 | | 
D | 25 | 6 | 5 | D | 7 | D | 1 | 1 | | | 
D | 34 | 7 | 6 | D | 8 | A | 1 | 0 | | 34 | 
A | 40 | 8 | 7 | D | | | 0 | 1 | 40 | | 

あるときにこれが本当に近いように見えますが、我々はまだ(CVによって、我々はグループができないという問題点があるときSnがCeのが移入されています人/国/部門)。私たちが必要とするのは、Snがすべてのそれらのヌルに以前のSnの値を設定することです。 rwn < rwnでこのテーブルに参加して最大値を得ることができますが、私は怠け者になり、Netezzaの解析関数とrows unbounded preceding節を使用します。これは私が今説明した方法のショートカットです。それで、我々は次のように定義されたEs、efffective startという別の列を作成します。

case 
    when Sn is null 
    then max(Sn) over (
     partition by k --key value of the SCD 
     order by Ck 
     rows unbounded preceding 
    ) 
    else Sn 
end Es 

この定義では、これを取得します。

Cv | Ce | Ck | Pk | Pv | Nk | Nv | Pc | Nc | Sn | Ee | Es | 
----|----|----|----|----|----|----|----|----|----|----|----| 
A | 10 | 1 | | | 2 | A | 0 | 1 | 10 | | 10 | 
A | 11 | 2 | 1 | A | 3 | B | 1 | 0 | | 11 | 10 | 
B | 14 | 3 | 2 | A | 4 | C | 0 | 0 | 14 | 14 | 14 | 
C | 16 | 4 | 3 | B | 5 | D | 0 | 0 | 16 | 16 | 16 | 
D | 18 | 5 | 4 | C | 6 | D | 0 | 1 | 18 | | 18 | 
D | 25 | 6 | 5 | D | 7 | D | 1 | 1 | | | 18 | 
D | 34 | 7 | 6 | D | 8 | A | 1 | 0 | | 34 | 18 | 
A | 40 | 8 | 7 | D | | | 0 | 1 | 40 | | 40 | 

残りはささいです。 Esによってグループ化し、このテーブルを得るためにEeの最大値を取得します。

Cv | Es | Ee | 
----|----|----| 
A | 10 | 11 | 
B | 14 | 14 | 
C | 16 | 16 | 
D | 18 | 34 | 
A | 40 | | 

あなたは、次の開始と効果的な終了時刻を移入自体に再びテーブルに参加したり、それをつかむためにlead()ウィンドウ関数を使用したい場合。

関連する問題