【问题标题】:Implementing Type 2 SCD in Oracle在 Oracle 中实现类型 2 SCD
【发布时间】:2019-02-16 19:07:21
【问题描述】:

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

我正在尝试在 Oracle 中实现类型 2 SCD。源表 (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);

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

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

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

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

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

【问题讨论】:

    标签: sql oracle etl bulk-operations


    【解决方案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.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
        )
    

    这会返回:

     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
    

    结果:

    版本 | CURRENT_FLAG | DAY_FROM | DAY_TO |供应商 ID |客户ID |秩 ------: | :----------- | :-------- | :-------- | ------------------: | ----------: | -----------------: 1 | N | 24-SEP-14 | 14 年 10 月 22 日 | 71047795 | 476095 | 3103 2 | N | 14 年 10 月 22 日 | 15 年 11 月 12 日 | 71047795 | 476095 | 3102 3 |是 | 15 年 11 月 12 日 | 99 年 12 月 31 日 | 71047795 | 476095 | 3103

    在 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.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
        )
    

    【讨论】:

    • 太棒了,先生。我真的很感谢你花时间写这些。但是,您认为有没有一种方法可以通过合并语句来完成。该表非常大,每次更新源表时,我都必须更新维度表以跟踪更改发生的时间。子查询可能不是我认为的最佳途径。
    • @user8740527 :我用MERGE 查询更新了我的答案
    • @user8740527 :如果我的回答正确回答了您的问题,请点击绿色复选标志accept it。这表明您的问题已解决的社区。​​span>
    • 我们不允许在同一个子句中进行更新和插入吗?所以假设我想做一些事情,比如在特定条件下匹配时,更新和插入?
    • @user8740527:更新插入是什么意思?合并查询检查目标表中是否已存在记录:如果存在,则更新现有记录,否则创建新记录。
    【解决方案2】:

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

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

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

    所以你需要一个进程——可能是一个 PL/SQL 过程——它执行一个 UPDATE 语句来关闭过期的当前记录,然后执行一个 INSERT 来添加新的当前记录。

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

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

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

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

    【讨论】:

    • 谢谢。非常感谢您的回复,并感谢您的所有动力。 :)
    猜你喜欢
    • 2020-04-22
    • 2021-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多