0

同じデータを格納する複数のレイヤーを持つデータ ウェアハウスを構築しています。中間層の 1 つのデータはすべて、タイプ 2 の緩やかに変化するディメンションであるかのように、開始日と終了日でバージョン管理されます。これらのテーブルをクエリすると、問題が発生します。通常、テーブルにはクエリよりも多くの列があるため、クエリ内の隣接するバージョンの開始日と終了日は異なりますが、それ以外は同一です。これらのバージョンを組み合わせて、テーブルの行が変更された日付ではなく、クエリの列が変更された日付を表示したいと考えています。

ほとんど機能するSQLがいくつかあります:

create table versions 
(id int
, name varchar(100) Not null
, RowStartDate datetime Not null
, RowEndDate datetime Not null
, primary key (id,RowStartDate)
, check (RowStartDate < RowEndDate));

insert into versions values 
 (1,'A','2014-01-01','9999-12-31')
,(2,'B','2014-01-01','2014-12-31')
,(2,'B','2014-12-31','9999-12-31')
,(3,'C','2014-01-01','2014-12-31')
,(3,'CC','2014-12-31','2015-12-31')
,(3,'CC','2015-12-31','9999-12-31')
,(4,'D','2014-01-01','2014-12-31')
,(4,'DD','2014-12-31','2015-12-31')
,(4,'DD','2015-12-31','2016-12-31')
,(4,'D','2016-12-31','9999-12-31')
,(5,'E','2014-01-01','2014-12-31')
,(5,'E','2014-12-31','2015-12-31')
,(5,'E','2015-12-31','2016-12-31')
,(5,'E','2016-12-31','2017-12-31')
,(5,'E','2017-12-31','9999-12-31')
;

WITH CTE_detect_duplicates AS (SELECT [id]
      ,[name]
      ,[RowStartDate]
      ,[RowEndDate]
      ,LAST_VALUE(RowEndDate) OVER (PARTITION BY id, name ORDER BY RowStartDate, RowEndDate ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) as LastEndDate
      ,rank() OVER (PARTITION BY id, name ORDER BY RowStartDate, RowEndDate) as duplicateNumber
  FROM versions
 )
SELECT [id]
      ,[name]
      ,[RowStartDate]
      ,LastEndDate as RowEndDate
FROM CTE_detect_duplicates
WHERE duplicateNumber = 1

ここでの問題は、3 つの行が必要な場合に、ID "4" に対して 2 つの行を返すことです。実際:

ID名 RowStartDate RowEndDate
4 D 2014-01-01 00:00:00.000 9999-12-31 00:00:00.000
4 DD 2014-12-31 00:00:00.000 2016-12-31 00:00:00.000
希望:
ID名 RowStartDate RowEndDate
4 日 2014-01-01 00:00:00.000 2014-12-31 00:00:00.000
4 DD 2014-12-31 00:00:00.000 2016-12-31 00:00:00.000
4 日 2016-12-31 00:00:00.000 9999-12-31 00:00:00.000
値 DD が正しい期間に対して値 D が正しくないため、my クエリの最初の行 (4,'D') のバージョン日付が正しくありません。

純粋な SQL またはインラインのテーブル値関数でこれらの重複を削除できるようにしたい (これを行う複数ステートメントのテーブル値関数を作成するジェネレーターがありますが、結果の関数のパフォーマンスが低下します)。誰にもアイデアはありますか?

4

1 に答える 1

2

複数の CTE を含む次のクエリは、更新の日付範囲を圧縮し、重複する値を削除します。

1 最初のランクは、RowStartDate に基づいて、各 ID グループ内で割り当てられます。

next_rank_no2 次に、同じ値を持つランクの範囲の最大ランク ( ) をNAME決定します。したがって、例のデータでは、id=5 の行 1 は next_rank_no=5 になり、id=4 の行 2 は next_rank_no=3 になります。このバージョンは、NAME列のみを処理します。追加の列を処理する場合は、それらも条件に含める必要があります。たとえば、LOCATION列を含める場合、結合条件は次のようになります。

  left join sorted_versions sv2 on sv2.id = sv1.id and sv2.rank_no > sv1.rank_no and sv2.name = sv1.name and sv2.location = sv1.location
  left join sorted_versions sv3 on sv3.id = sv1.id and sv3.rank_no > sv1.rank_no and (sv3.name <> sv1.name or sv3.location <> sv1.location)

3 最後に、各 ID の最初の行が選択されます。次に、に対応する行next_rank_noが再帰的に選択されます。

with sorted_versions as --ranks are assigned within each id group
(
  select 
    v1.id,
    v1.name,
    v1.RowStartDate,
    v1.RowEndDate,
    rank() over (partition by v1.id order by v1.RowStartDate) rank_no
  from versions v1
  left join versions v2 on (v1.id = v2.id and v2.RowStartDate = v1.RowEndDate)
),
next_rank as --the maximum rank of the range of ranks which has the same value for NAME
(
  select 
  sv1.id id, sv1.rank_no rank_no, COALESCE(min(sv3.rank_no)-1 , COALESCE(max(sv2.rank_no), sv1.rank_no)) next_rank_no
  from sorted_versions sv1
  left join sorted_versions sv2 on sv2.id = sv1.id and sv2.rank_no > sv1.rank_no and sv2.name = sv1.name
  left join sorted_versions sv3 on sv3.id = sv1.id and sv3.rank_no > sv1.rank_no and sv3.name <> sv1.name
  group by sv1.id, sv1.rank_no
),
versions_cte as --the rowenddate of the "maximum rank" is selected 
(
  select sv.id, sv.name, sv.rowstartdate, sv3.rowenddate, nr.next_rank_no rank_no
  from sorted_versions sv
  inner join next_rank nr on sv.id = nr.id and sv.rank_no = nr.rank_no and sv.rank_no = 1
  inner join sorted_versions sv3 on nr.id = sv3.id and nr.next_rank_no = sv3.rank_no  
  union all
  select
    sv2.id,
    sv2.name, 
    sv2.rowstartdate,
    sv3.rowenddate,
    nr.next_rank_no
  from versions_cte vc
  inner join sorted_versions sv2 on sv2.id = vc.id and sv2.rank_no = vc.rank_no + 1
  inner join next_rank nr on sv2.id = nr.id and sv2.rank_no = nr.rank_no  
  inner join sorted_versions sv3 on nr.id = sv3.id and nr.next_rank_no = sv3.rank_no
)
select id, name, rowstartdate, rowenddate
from versions_cte
order by id, rowstartdate;

SQL Fiddle demo

于 2014-07-10T18:56:58.620 に答える