在 Oracle 中实施 Type 2 SCD

Implementing Type 2 SCD in Oracle

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

我正在尝试在 Oracle 中实现 2 类 SCD。源table (customer_records) 的结构如下所示。

CREATE TABLE customer_records(
    day date,
    snapshot_day number,
    vendor_id number,
    customer_id number,
    rank number
);

INSERT INTO customer_records 
(day,snapshot_day,vendor_id,customer_id,rank)
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);

以上 table 每周更新一次,我已经提取了 vendor_idcustomer_id 代表的特定客户的记录。这样每个客户都会有一个唯一的 vendor_idcustomer_id。我正在尝试跟踪客户等级 (rank) 的变化。客户的等级可能会在数周内保持不变,而我们只愿意在客户等级发生变化时进行跟踪。

所需的输出(维度 table)将如下所示:

SK  Version   Date_From    Date_To    Vendor_id   Customer_Id  Rank_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

这样,每当客户的等级发生变化时,我们都会在新的 table 中进行跟踪。此外,想要包含最新层的 current_flag = 'Y'。

我希望能够使用合并来完成。

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

想法是自加入 table,并将每条记录关联到具有不同层的下一条记录。这是使用带有相关子查询的 NOT EXISTS 条件完成的。

需要

LEFT JOIN,以避免过滤掉最后一条记录(拥有当前层),该记录还没有下一条记录:对于这条记录,我们使用 COALESCE() 来设置设置默认结束日期。

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

这个returns:

 DAY_FROM  | DAY_TO    | Vendor_ID | Customer_ID | rank
 :-------- | :-------- | ------------------: | ----------: | -----------------:
 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.Vendor_ID,
    c1.Customer_ID, 
    c1.rank
FROM customer_records c1
LEFT JOIN customer_records c2 
    ON  c2.Vendor_ID = c1.Vendor_ID
    AND c2.Customer_ID         = c1.Customer_ID
    AND c2.rank <> c1.rank
    AND c2.DAY                 > c1.DAY
    AND NOT EXISTS (
        SELECT 1
        FROM customer_records c3
        WHERE
                c3.Vendor_Id = c1.Vendor_Id
            AND c3.Customer_ID         = c1.Customer_ID
            AND c3.rank <> c1.rank
            AND c3.DAY                 > c1.DAY
            AND c3.DAY                 < c2.DAY
    )
GROUP BY
    c1.Vendor_Id, 
    c1.Customer_ID, 
    c1.rank, 
    c2.day
ORDER BY
    day_from

结果:

VERSION | CURRENT_FLAG | DAY_FROM  | DAY_TO    | Vendor_ID | Customer_ID | rank
------: | :----------- | :-------- | :-------- | ------------------: | ----------: | -----------------:
      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

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

MERGE INTO dimensions dim
USING (
   -- above query goes here --
) cust 
    ON  dim.DAY_FROM            = cust.DAY_FROM
    AND dim.vendor_id = cust.vendor_id
    AND dim.Customer_ID         = cust.Customer_ID
    AND dim.rank  = cust.rank
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.vendor_id, 
        dim.customer_id, 
        dim.rank
    ) VALUES (
        cust.DAY_FROM, 
        cust.VERSION, 
        cust.CURRENT_FLAG, 
        cust.DAY_FROM, 
        cust.DAY_TO, 
        cust.vendor_id, 
        cust.Customer_ID, 
        cust.rank
    )

I want to be able to do it using merge.

MERGE 不会为您完成。 MERGE 基本上是一个 case 语句:对于 USING 子查询中的每条记录,我们可以插入匹配的记录或更新匹配的记录。问题是,当现有客户的等级发生变化时,您需要为 两个 维度记录执行 DML:

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

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

Sub-query may not be the best route I believe.

您形容自己对 SQL 比较陌生,因此您可能会担心这一点,但请不要担心。避免过早优化。做最简单的事情,并根据需要对其进行调整。子查询应该是识别您需要更新的当前记录的最有效方式。 Oracle 数据库是主力军,可以处理大量负载,前提是我们编写合理 SQL.

在您的情况下,这意味着:

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