在 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_id
和 customer_id
代表的特定客户的记录。这样每个客户都会有一个唯一的 vendor_id
和 customer_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_flag
和 day_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 以便有效地应用子查询。
首先我想说我是 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_id
和 customer_id
代表的特定客户的记录。这样每个客户都会有一个唯一的 vendor_id
和 customer_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_flag
和 day_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 以便有效地应用子查询。