0

我从 SCD-2 表中检索具有许多参数的数据,并且我需要仅使用其中一个来构建自己的 SCD-2。因此,我需要摆脱过多的间隔。请推荐一种算法以最佳方式执行该操作。

我从源表中收到的内容:

我从源表中收到的内容

我需要将其转换为:

我需要将其转换为:

4

2 回答 2

2

这显然很复杂,因为相同的“值”可以在多个组中重复 - 所以你不能只使用简单的 MIN/MAX 函数。您可能可以在 javascript 存储过程中对此进行编码,但我想我会尝试在(几乎)纯 SQL 中找到解决方案。

挑战是在每次值更改时尝试创建一个“组” - 这样您就可以在组内的日期上执行简单的 MIN/MAX。我(希望!)解决这个问题的方式如下:

  1. 每当当前行的值与前一行的值不同时,创建一个 CTE,其中计算字段设置为序列中的下一个值;如果没有区别,则该字段设置为 null - 这很重要,因为下一个 CTE 中的 LAG 函数处理 NULL 的方式
  2. 创建第二个 CTE,其中计算的分组列设置为上一列中创建的计算列中的最后一个非空值 - 使用 LAG 函数集忽略空值
  3. 从第 2 个 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
于 2020-10-07T17:21:52.760 回答
2

您可以使用以下步骤来获得所需的结果。当然,您可以使用子选择或 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 步:过滤开始/结束行并将 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;

于 2020-10-08T10:39:06.190 回答