从列的子集计算最小记录版本集

Calculate minimum set of record versions from a subset of columns

我正在尝试破解一个看起来很容易解释的 SQL 问题:

  1. 我有一个 table,其中包含给定实体的多个版本(如 SCD 类型 2 维度)- 下面的 table temp.test

内容:

DROP TABLE IF EXISTS temp.test;
CREATE TEMP TABLE temp.test (
  row_id   INTEGER IDENTITY (1, 1),
  id       VARCHAR,
  start_ts TIMESTAMP,
  end_ts   TIMESTAMP,
  level1   VARCHAR,
  level2   VARCHAR
);

INSERT INTO temp.test (id, start_ts, end_ts, level1, level2) VALUES
  ('a', '1970-01-01 00:00:00.000000', '2017-12-31 23:59:59.999999', 'ABC1', 'ABC2'),
  ('a', '2018-01-01 00:00:00.000000', '2018-12-31 23:59:59.999999', 'DEF1', 'DEF2'),
  ('a', '2019-01-01 00:00:00.000000', '2019-12-31 23:59:59.999999', 'ABC1', 'GHI2'),
  ('a', '2020-01-01 00:00:00.000000', '2020-12-31 23:59:59.999999', 'ABC1', 'JKL2');
  1. 我基本上想结束:

    -- Desired output
    ('a', '1970-01-01 00:00:00.000000', '2017-12-31 23:59:59.999999', 'ABC1'),
    ('a', '2018-01-01 00:00:00.000000', '2018-12-31 23:59:59.999999', 'DEF1'),
    ('a', '2019-01-01 00:00:00.000000', '2020-12-31 23:59:59.999999', 'ABC1'),
    
  2. 意思是,我想要列 level1 的最小版本集。请注意,第 3 行和第 4 行将重复,但在这种情况下,我们将获得 min(start_ts)max(end_ts) 来计算版本。

  3. 这是我尝试过的方法,但我惨遭失败...

    -- Wrong
    SELECT
      id,
      min(start_ts) AS start_ts,
      max(end_ts)   AS end_ts,
      level1
    FROM temp.test
    GROUP BY id, level1
    ORDER BY 2;
    
    -- Wrong
    SELECT DISTINCT
      id,
      FIRST_VALUE(start_ts) OVER(PARTITION BY id, level1 ORDER BY start_ts) AS start_ts,
      LAST_VALUE(end_ts) OVER(PARTITION BY id, level1 ORDER BY start_ts)    AS end_ts,
      level1
    FROM temp.test
    ORDER BY 2;
    

必须有一些神奇的方法来获得我需要的输出。你有什么建议?

注意:我使用的是 Snowflake,但这只是标准 SQL。

这是一个缺口和孤岛问题。在这种情况下,我会使用 row_number() 方法:

SELECT id, level1,
       MIN(start_ts) as start_ts, MAX(end_ts) as end_ts
FROM (SELECT t.*,
             ROW_NUMBER() OVER (PARTITION BY id ORDER BY start_ts) as seqnum_i,
             ROW_NUMBER() OVER (PARTITION BY id, level1 ORDER BY start_ts) as seqnum_il,
      FROM temp.test t
     ) t
GROUP BY id, level1, (seqnum_i - seqnum_il);

请注意,这假设开始和结束时间戳中没有间隔。

这是如何工作的并不是很明显。我一般建议你只盯着子查询的结果看。通常很明显,两个行号之间的差异标识了您要聚合的组。