使用 SQL 在 Redshift Table 中创建 SCD 历史记录

Creating SCD History in Redshift Table with SQL

问题

我的起始 table 看起来像这样:

我正在尝试编写一个 SQL 查询,将此数据写入格式相似的 table,但带有额外的装饰,指示记录何时过期以及哪些记录处于活动状态。结果将如下所示:

identifier                       | loaddate   | loadenddate | activeflag | symbol
723a90699e99ec9e00216910910384bd | 2020-04-01 | 2020-04-07  | 0          | DXB
723a90699e99ec9e00216910910384bd | 2020-04-08 | 2999-12-31  | 1          | DXB CL

请注意,有 1000 多个不同的标识符,其中一些在各种不同的时间范围内有一个、两个、三个以上的不同符号。

要求

  1. 任何时候第一次看到一个标识符,它必须在最终 table 中创建,今天的日期作为加载日期和 2999-12-31 loadenddate 和 activeflag=1
  2. 当第二天看到该标识符时,如果符号已更改,则仅添加一行。如果有,通过将前一行的加载结束日期设置为新行的加载日期 - 1 天和 activeflag = 0
  3. 来“终止”前一行
  4. sql 查询(或多个查询)需要能够在源 table 上每天重新运行,以便它们正确处理现有数据目的地 table 以及目的地 table 为空白(初始 运行)

到目前为止我得到了什么

要最初加载(而不是重复),我有以下 SQL:

INSERT INTO finaltable(
listinghashkey
symbol,
loaddate,
loadenddate,
activeflag
)
SELECT
s.listinghashkey
s.symbol,
MAX(s.loaddate),
'2999-12-31 00:00:00.0',
1
FROM
startingtable s
LEFT JOIN finaltable f ON s.listinghashkey = f.listinghashkey
WHERE (f.listinghashkey IS NULL)
GROUP BY s.listinghashkey, s.symbol

将您的初始格式转换为新格式非常简单,因为存在间隙和孤岛问题:

select identifier, symbol, min(loaddate),
       nullif(max(loaddate), max_loaddate)
from (select s.*,
             max(loaddate) over () as max_loaddate,
             row_number() over (partition by identifier order by loaddate) as seqnum,
             row_number() over (partition by identifier, symbol order by loaddate) as seqnum_2
      from startingtable
     ) s
group by identifier, symbol, (seqnum - seqnum_2);

这避开了“有效”标志和任意未来日期。它只是用 NULL 来表示无限的未来。 (您可以轻松地调整您的版本的逻辑;这只是使用起来更简单。)

如果你有这个 table 并且你想添加下一个加载日期,那么你可以使用 union all 完整地 构建下一个版本 ].思路是将处理分为四步:

  • 已经关闭的历史记录,因此新数据不会影响它们。
  • 记录新数据与现有数据一致的地方,因此没有任何变化。
  • 不在新数据中的记录,因此需要关闭现有记录。
  • 新记录。

SQL 看起来像:

-- all completed records
select ft.identifier, ft.symbol, ft.loaddate, ft.loadenddate
from finaltable ft
where loadenddate is not null
union all
-- Basically copy over records where the new data is consistent
select ft.identifer, ft.symbol, ft.loaddate, ft.loadenddate
from finaltable ft join
     oneload ol
     on ft.identifier = ol.identifier and
        ft.symbol = ol.symbol
where ft.loadenddate is null
union all
-- close records that are not in the new batch
select ft.identifer, ft.symbol, ft.loaddate, m.loaddate - interval '1 day'
from finaltable ft cross join
     (select max(loaddate) as loaddate
      from oneload
     ) m left join
     oneload ol
     on ft.identifier = ol.identifier and
        ft.symbol = ol.symbol
where ft.loadenddate is null
-- finally add new records
select ol.identifer, ol.symbol, ol.loaddate, null
from oneload ol left join
     finaltable ft
     on ft.identifier = ol.identifier and
        ft.symbol = ol.symbol and
        ft.loadenddate is null
where ft.identifier is null;

我更喜欢将此作为 select/替换操作而不是一系列 insert/update 步骤(或可能使用 merge)。不过你这是基本思路。