MS SQL 服务器:将 SCD-2 转换为变量较少的 SCD-2
MS SQL Server: transforming SCD-2 to SCD-2 with less variables
我使用许多参数从 SCD-2 table 检索数据,我需要仅使用其中一个来构建我自己的 SCD-2。因此,我需要摆脱过多的间隔。请推荐一种算法以最佳方式执行该操作。
我从来源table收到的信息:
我需要将其转换为:
这显然很复杂,因为相同的“值”可以在多个组中重复 - 因此您不能只使用简单的 MIN/MAX 函数。您可能会在 javascript 存储过程中对此进行编码,但我想我会尝试在(几乎)纯 SQL.
中找到解决方案
挑战在于每次值更改时都尝试创建一个“组”- 这样您就可以对组内的日期进行简单的 MIN/MAX。我(希望!)解决这个问题的方法如下:
- 创建一个 CTE,只要当前行的值与上一行的值不同,计算字段就会设置为序列中的下一个值;如果没有区别,则该字段设置为 null - 这很重要,因为下一个 CTE 中的 LAG 函数处理 NULLs
- 创建第二个 CTE,其中计算分组列设置为上一列中创建的计算列中的最后一个 non-null 值 - 使用 LAG 函数集忽略空值
- 从第二个 CTE 开始,查询按键、值和分组列分组的最小和最大日期值
代码
CREATE TABLE SRC_TABLE (key1 integer, value1 integer, row_actual_from date, row_actual_to date);
INSERT INTO SRC_TABLE
VALUES
(19999923, 15, '2020-01-01', '2020-01-02'),
(19999923, 15, '2020-01-03', '2020-01-05'),
(19999923, 15, '2020-01-06', '2020-01-08'),
(19999923, 3434, '2020-01-09', '2020-01-12'),
(19999923, 3434, '2020-01-13', '2020-01-15'),
(19999923, 15, '2020-01-16', '2020-01-20'),
(19999923, 15, '2020-01-21', '9999-12-31');
create or replace sequence seq_01 start = 1 increment = 1;
WITH T1 AS (
SELECT KEY1, VALUE1, row_actual_from, row_actual_to
,CASE WHEN LAG(VALUE1,1,0) OVER (PARTITION BY KEY1 ORDER BY row_actual_from ASC) = VALUE1 THEN null ELSE seq_01.nextval END AS CHK_MIN
from SRC_TABLE
order by row_actual_from
),
T2 AS (
SELECT KEY1, VALUE1, row_actual_from, row_actual_to, CHK_MIN
,CASE WHEN CHK_MIN IS NULL THEN LAG(CHK_MIN,1,0) IGNORE NULLS OVER (PARTITION BY KEY1 ORDER BY row_actual_from ASC) ELSE CHK_MIN END AS CHK_MIN_GRP
FROM T1
)
SELECT KEY1, VALUE1, MIN(ROW_ACTUAL_FROM), MAX(ROW_ACTUAL_TO)
FROM T2
GROUP BY KEY1, VALUE1, CHK_MIN_GRP
;
结果
KEY1 VALUE1 MIN(ROW_ACTUAL_FROM) MAX(ROW_ACTUAL_TO)
19999923 15 2020-01-01 2020-01-08
19999923 3434 2020-01-09 2020-01-15
19999923 15 2020-01-16 9999-12-31
您可以使用以下步骤来获得所需的结果。当然,您可以使用 sub-selects 或 CTE 一步完成,但为了更好的可追溯性,我更喜欢临时表。
DROP TABLE IF EXISTS #source;
CREATE TABLE #source (key1 integer, value1 integer, row_actual_from date, row_actual_to date);
INSERT INTO #source
VALUES
(19999923, 15, '2020-01-01', '2020-01-02'),
(19999923, 15, '2020-01-03', '2020-01-05'),
(19999923, 15, '2020-01-06', '2020-01-08'),
(19999923, 11, '2020-01-09', '2020-01-12'),
(19999923, 3434, '2020-01-13', '2020-01-15'),
(19999923, 11, '2020-01-16', '2020-01-20'),
(19999923, 15, '2020-01-21', '2020-02-02'),
(19999923, 3434, '2020-02-03', '2020-02-10'),
(19999923, 3434, '2020-02-11', '2020-02-19'),
(19999923, 3434, '2020-02-20', '2020-02-25'),
(19999923, 99, '2020-02-26', '9999-12-31');
第 1 步:确定单个值周期的开始和结束。
请注意,在 LAG/LEAD 中,本质上是将一个值作为 NULL 替换(例如 -99),它与列中的可能值不匹配。
DROP TABLE IF EXISTS #step1;
SELECT
key1, value1, row_actual_from, row_actual_to
, period_start = CASE WHEN LAG(value1, 1, -99) OVER (PARTITION BY key1 ORDER BY row_actual_from) <> value1 THEN 1 ELSE 0 END
, period_end = CASE WHEN LEAD(value1, 1, -99) OVER (PARTITION BY key1 ORDER BY row_actual_from) <> value1 THEN 1 ELSE 0 END
INTO #step1
FROM #source
ORDER BY key1, row_actual_from;
第 2 步:筛选 start/end 行并将结束的 row_actual_to 分配给开始。
如果值的周期只有一行,则此行将 period_start 和 period_end 设置为 1,因此总和为 2。在这种情况下,row_acutal_to 的内容已经有了想要的值。
DROP TABLE IF EXISTS #step2;
SELECT
key1, value1, row_actual_from, row_actual_to, period_start, period_end
, valid_from = row_actual_from
, valid_to = CASE (period_start + period_end)
WHEN 1 THEN LEAD(row_actual_to, 1) OVER (PARTITION BY key1, value1 ORDER BY row_actual_from)
WHEN 2 THEN row_actual_to ELSE NULL END
INTO #step2
FROM #step1
WHERE (period_start + period_end) > 0
ORDER BY key1, row_actual_from;
第 3 步:过滤(调整)值周期的起始行。
SELECT key1, value1, valid_from, valid_to
FROM #step2
WHERE period_start = 1
ORDER BY key1, row_actual_from;
我使用许多参数从 SCD-2 table 检索数据,我需要仅使用其中一个来构建我自己的 SCD-2。因此,我需要摆脱过多的间隔。请推荐一种算法以最佳方式执行该操作。
我从来源table收到的信息:
我需要将其转换为:
这显然很复杂,因为相同的“值”可以在多个组中重复 - 因此您不能只使用简单的 MIN/MAX 函数。您可能会在 javascript 存储过程中对此进行编码,但我想我会尝试在(几乎)纯 SQL.
中找到解决方案挑战在于每次值更改时都尝试创建一个“组”- 这样您就可以对组内的日期进行简单的 MIN/MAX。我(希望!)解决这个问题的方法如下:
- 创建一个 CTE,只要当前行的值与上一行的值不同,计算字段就会设置为序列中的下一个值;如果没有区别,则该字段设置为 null - 这很重要,因为下一个 CTE 中的 LAG 函数处理 NULLs
- 创建第二个 CTE,其中计算分组列设置为上一列中创建的计算列中的最后一个 non-null 值 - 使用 LAG 函数集忽略空值
- 从第二个 CTE 开始,查询按键、值和分组列分组的最小和最大日期值
代码
CREATE TABLE SRC_TABLE (key1 integer, value1 integer, row_actual_from date, row_actual_to date);
INSERT INTO SRC_TABLE
VALUES
(19999923, 15, '2020-01-01', '2020-01-02'),
(19999923, 15, '2020-01-03', '2020-01-05'),
(19999923, 15, '2020-01-06', '2020-01-08'),
(19999923, 3434, '2020-01-09', '2020-01-12'),
(19999923, 3434, '2020-01-13', '2020-01-15'),
(19999923, 15, '2020-01-16', '2020-01-20'),
(19999923, 15, '2020-01-21', '9999-12-31');
create or replace sequence seq_01 start = 1 increment = 1;
WITH T1 AS (
SELECT KEY1, VALUE1, row_actual_from, row_actual_to
,CASE WHEN LAG(VALUE1,1,0) OVER (PARTITION BY KEY1 ORDER BY row_actual_from ASC) = VALUE1 THEN null ELSE seq_01.nextval END AS CHK_MIN
from SRC_TABLE
order by row_actual_from
),
T2 AS (
SELECT KEY1, VALUE1, row_actual_from, row_actual_to, CHK_MIN
,CASE WHEN CHK_MIN IS NULL THEN LAG(CHK_MIN,1,0) IGNORE NULLS OVER (PARTITION BY KEY1 ORDER BY row_actual_from ASC) ELSE CHK_MIN END AS CHK_MIN_GRP
FROM T1
)
SELECT KEY1, VALUE1, MIN(ROW_ACTUAL_FROM), MAX(ROW_ACTUAL_TO)
FROM T2
GROUP BY KEY1, VALUE1, CHK_MIN_GRP
;
结果
KEY1 VALUE1 MIN(ROW_ACTUAL_FROM) MAX(ROW_ACTUAL_TO)
19999923 15 2020-01-01 2020-01-08
19999923 3434 2020-01-09 2020-01-15
19999923 15 2020-01-16 9999-12-31
您可以使用以下步骤来获得所需的结果。当然,您可以使用 sub-selects 或 CTE 一步完成,但为了更好的可追溯性,我更喜欢临时表。
DROP TABLE IF EXISTS #source;
CREATE TABLE #source (key1 integer, value1 integer, row_actual_from date, row_actual_to date);
INSERT INTO #source
VALUES
(19999923, 15, '2020-01-01', '2020-01-02'),
(19999923, 15, '2020-01-03', '2020-01-05'),
(19999923, 15, '2020-01-06', '2020-01-08'),
(19999923, 11, '2020-01-09', '2020-01-12'),
(19999923, 3434, '2020-01-13', '2020-01-15'),
(19999923, 11, '2020-01-16', '2020-01-20'),
(19999923, 15, '2020-01-21', '2020-02-02'),
(19999923, 3434, '2020-02-03', '2020-02-10'),
(19999923, 3434, '2020-02-11', '2020-02-19'),
(19999923, 3434, '2020-02-20', '2020-02-25'),
(19999923, 99, '2020-02-26', '9999-12-31');
第 1 步:确定单个值周期的开始和结束。
请注意,在 LAG/LEAD 中,本质上是将一个值作为 NULL 替换(例如 -99),它与列中的可能值不匹配。
DROP TABLE IF EXISTS #step1;
SELECT
key1, value1, row_actual_from, row_actual_to
, period_start = CASE WHEN LAG(value1, 1, -99) OVER (PARTITION BY key1 ORDER BY row_actual_from) <> value1 THEN 1 ELSE 0 END
, period_end = CASE WHEN LEAD(value1, 1, -99) OVER (PARTITION BY key1 ORDER BY row_actual_from) <> value1 THEN 1 ELSE 0 END
INTO #step1
FROM #source
ORDER BY key1, row_actual_from;
第 2 步:筛选 start/end 行并将结束的 row_actual_to 分配给开始。
如果值的周期只有一行,则此行将 period_start 和 period_end 设置为 1,因此总和为 2。在这种情况下,row_acutal_to 的内容已经有了想要的值。
DROP TABLE IF EXISTS #step2;
SELECT
key1, value1, row_actual_from, row_actual_to, period_start, period_end
, valid_from = row_actual_from
, valid_to = CASE (period_start + period_end)
WHEN 1 THEN LEAD(row_actual_to, 1) OVER (PARTITION BY key1, value1 ORDER BY row_actual_from)
WHEN 2 THEN row_actual_to ELSE NULL END
INTO #step2
FROM #step1
WHERE (period_start + period_end) > 0
ORDER BY key1, row_actual_from;
第 3 步:过滤(调整)值周期的起始行。
SELECT key1, value1, valid_from, valid_to
FROM #step2
WHERE period_start = 1
ORDER BY key1, row_actual_from;