在Oracle中实施Type 2 SCD

时间:2019-02-16 19:07:21

标签: sql oracle etl bulk-operations

首先,我想说我是stackoverflow社区的新手,而SQL本身是新手,所以请原谅我,如果我对问题的格式不正确或没有清楚说明我的要求。

我正在尝试在Oracle中实现2型SCD。源表(customer_records)的结构如下。

CREATE TABLE customer_records(
    day date,
    snapshot_day_dw_id number,
    vend_loy_acct_dw_id number,
    hub_cust_id number,
    cur_loy_tier_dw_id number
);

INSERT INTO customer_records 
(day,snapshot_day_dw_id,vend_loy_acct_dw_id,hub_cust_id,cur_loy_tier_dw_id)
VALUES
(9/24/2014,6266,71047795,476095,3103),
(10/1/2014,6273,71047795,476095,3103),
(10/8/2014,6280,71047795,476095,3103),
(10/15/2014,6287,71047795,476095,3103),
(10/22/2014,6291,71047795,476095,3102),
(10/29/2014,6330,71047795,476095,3102),
(11/05/2015,6351,71047795,476095,3102),
(11/12/2015,6440,71047795,476095,3103);

上表每周更新一次,我已经提取了由vend_loy_acct_dw_idhub_cust_id代表的特定客户的记录。这样每个客户将拥有唯一的vend_loy_acct_dw_idhub_cust_id。我正在尝试跟踪客户的层(cur_loy_tier_dw_id)中的更改。可能会发生这样的情况,即客户的等级可能会保持几个星期不变,而我们仅愿意跟踪客户等级发生变化的时间。

所需的输出(尺寸表)如下所示:

SK  Version   Date_From    Date_To    Vend_Loy_Acct_Dw_Id   Hub_Cust_Id  Cur_Cust_Tier_Id

1     1       9/24/2014    10/22/2014    71047795            476095       3103
2     2       10/22/2014   11/05/2015    71047795            476095       3102
3     3       11/05/2015   12/31/2199    71047795            476095       3103

这样,每当客户层进行更改时,我们都会在新表中进行跟踪。另外,要为最新层包括current_flag ='Y'。

我希望能够使用合并做到这一点。

2 个答案:

答案 0 :(得分:1)

这是一种在检测到更改时将具有相同层的连续记录分组的方法。

这个想法是自我联接表,并将每个记录与具有不同层的下一个记录相关联。这是通过使用NOT EXISTS条件和相关子查询来完成的。

LEFT JOIN是必需的,以避免过滤出没有下一个记录的最后一条记录(拥有当前层):为此记录,我们使用COALESCE()来设置默认结束日期。

SELECT 
    c1.day day_from,
    COALESCE(c2.day, TO_DATE('2199-12-31', 'yyyy-mm-dd')) day_to,
    c1.VEND_LOY_ACCT_DW_ID,
    c1.HUB_CUST_ID, 
    c1.CUR_LOY_TIER_DW_ID
FROM customer_records c1
LEFT JOIN customer_records c2 
    ON  c2.VEND_LOY_ACCT_DW_ID = c1.VEND_LOY_ACCT_DW_ID
    AND c2.HUB_CUST_ID         = c1.HUB_CUST_ID
    AND c2.CUR_LOY_TIER_DW_ID <> c1.CUR_LOY_TIER_DW_ID
    AND c2.DAY                 > c1.DAY
    AND NOT EXISTS (
        SELECT 1
        FROM customer_records c3
        WHERE
                c3.VEND_LOY_ACCT_DW_ID = c1.VEND_LOY_ACCT_DW_ID
            AND c3.HUB_CUST_ID         = c1.HUB_CUST_ID
            AND c3.CUR_LOY_TIER_DW_ID <> c1.CUR_LOY_TIER_DW_ID
            AND c3.DAY                 > c1.DAY
            AND c3.DAY                 < c2.DAY
    )

这将返回:

 DAY_FROM  | DAY_TO    | VEND_LOY_ACCT_DW_ID | HUB_CUST_ID | CUR_LOY_TIER_DW_ID
 :-------- | :-------- | ------------------: | ----------: | -----------------:
 24-SEP-14 | 22-OCT-14 |            71047795 |      476095 |               3103
 01-OCT-14 | 22-OCT-14 |            71047795 |      476095 |               3103
 08-OCT-14 | 22-OCT-14 |            71047795 |      476095 |               3103
 15-OCT-14 | 22-OCT-14 |            71047795 |      476095 |               3103
 22-OCT-14 | 12-NOV-15 |            71047795 |      476095 |               3102
 29-OCT-14 | 12-NOV-15 |            71047795 |      476095 |               3102
 05-NOV-15 | 12-NOV-15 |            71047795 |      476095 |               3102
 12-NOV-15 | 31-DEC-99 |            71047795 |      476095 |               3103

现在,我们可以按记录层和结束日期对记录集进行分组,以生成预期结果。 ROW_NUMBER()可以为您提供版本号。如上所述,也很容易检查哪个记录是当前记录。

SELECT 
    ROW_NUMBER() OVER(ORDER BY c2.day) version,
    DECODE(c2.day, NULL, 'Y') current_flag,
    MIN(c1.day) day_from,
    COALESCE(c2.day, TO_DATE('2199-12-31', 'yyyy-mm-dd')) day_to,
    c1.VEND_LOY_ACCT_DW_ID,
    c1.HUB_CUST_ID, 
    c1.CUR_LOY_TIER_DW_ID
FROM customer_records c1
LEFT JOIN customer_records c2 
    ON  c2.VEND_LOY_ACCT_DW_ID = c1.VEND_LOY_ACCT_DW_ID
    AND c2.HUB_CUST_ID         = c1.HUB_CUST_ID
    AND c2.CUR_LOY_TIER_DW_ID <> c1.CUR_LOY_TIER_DW_ID
    AND c2.DAY                 > c1.DAY
    AND NOT EXISTS (
        SELECT 1
        FROM customer_records c3
        WHERE
                c3.VEND_LOY_ACCT_DW_ID = c1.VEND_LOY_ACCT_DW_ID
            AND c3.HUB_CUST_ID         = c1.HUB_CUST_ID
            AND c3.CUR_LOY_TIER_DW_ID <> c1.CUR_LOY_TIER_DW_ID
            AND c3.DAY                 > c1.DAY
            AND c3.DAY                 < c2.DAY
    )
GROUP BY
    c1.VEND_LOY_ACCT_DW_ID, 
    c1.HUB_CUST_ID, 
    c1.CUR_LOY_TIER_DW_ID, 
    c2.day
ORDER BY
    day_from

结果:

VERSION | CURRENT_FLAG | DAY_FROM  | DAY_TO    | VEND_LOY_ACCT_DW_ID | HUB_CUST_ID | CUR_LOY_TIER_DW_ID
------: | :----------- | :-------- | :-------- | ------------------: | ----------: | -----------------:
      1 | N            | 24-SEP-14 | 22-OCT-14 |            71047795 |      476095 |               3103
      2 | N            | 22-OCT-14 | 12-NOV-15 |            71047795 |      476095 |               3102
      3 | Y            | 12-NOV-15 | 31-DEC-99 |            71047795 |      476095 |               3103

Demo on DB Fiddle


在Oracle中,您可以使用the MERGE syntax将任何选择转换为合并查询。您可以在所有预期的current_flagday_to列上进行匹配,如果记录已经存在,则可以更新这些列;否则,只需插入一个新的。

MERGE INTO dimensions dim
USING (
   -- above query goes here --
) cust 
    ON  dim.DAY_FROM            = cust.DAY_FROM
    AND dim.VEND_LOY_ACCT_DW_ID = cust.VEND_LOY_ACCT_DW_ID
    AND dim.HUB_CUST_ID         = cust.HUB_CUST_ID
    AND dim.CUR_LOY_TIER_DW_ID  = cust.CUR_LOY_TIER_DW_ID
WHEN MATCHED THEN UPDATE SET 
    dim.DAY_TO = cust.DAY_TO,
    dim.CURRENT_FLAG = cust.CURRENT_FLAG
WHEN NOT MATCHED THEN 
    INSERT (
        dim.DAY_FROM, 
        dim.VERSION, 
        dim.CURRENT_FLAG, 
        dim.DAY_FROM, 
        dim.DAY_TO, 
        dim.VEND_LOY_ACCT_DW_ID, 
        dim.HUB_CUST_ID, 
        dim.CUR_LOY_TIER_DW_ID
    ) VALUES (
        cust.DAY_FROM, 
        cust.VERSION, 
        cust.CURRENT_FLAG, 
        cust.DAY_FROM, 
        cust.DAY_TO, 
        cust.VEND_LOY_ACCT_DW_ID, 
        cust.HUB_CUST_ID, 
        cust.CUR_LOY_TIER_DW_ID
    )

答案 1 :(得分:0)

  

我希望能够使用合并做到这一点。

MERGE不会为您这样做。 MERGE基本上是一个case语句:对于USING子查询中的每个记录,我们可以插入上匹配的记录或更新匹配的记录。问题是,当现有客户的层更改时,您需要为两个维记录执行DML:

  • 更新先前的当前记录-设置current_flag ='N',将day_to设置为systimestamp(或其他)。
  • 插入新的当前记录。

因此,您需要有一个过程-可能是PL / SQL过程-执行UPDATE语句以关闭过期的当前记录,然后执行INSERT添加新的当前记录。

  

我认为,子查询可能不是最佳途径。

您将自己描述为对SQL相对较新的人,因此您可以为此担心,但不必担心。避免过早优化。做最简单的操作即可根据需要进行调整。子查询应该是tp识别需要更新的当前记录的最有效方法。如果我们编写明智的SQL,则Oracle数据库是主力军,可以处理大量负载。

对于您而言,这意味着:

  • 对UPDATE和INSERT使用set操作(即不是逐行)。
  • 确保使用最少的必要记录集。仅对自上次刷新维度以来已更改的基础表中的记录应用更改。在您的情况下,您需要跟踪customer_records.snapshot_day_dw_id,并且仅对具有更高snapshot_day_dw_id的记录应用更改(或者可能不行,我想您的过程是这样)。
  • 正确索引维度表,以便有效地应用子查询。