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。我(希望!)解决这个问题的方法如下:

  1. 创建一个 CTE,只要当前行的值与上一行的值不同,计算字段就会设置为序列中的下一个值;如果没有区别,则该字段设置为 null - 这很重要,因为下一个 CTE 中的 LAG 函数处理 NULLs
  2. 创建第二个 CTE,其中计算分组列设置为上一列中创建的计算列中的最后一个 non-null 值 - 使用 LAG 函数集忽略空值
  3. 从第二个 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;