识别随时间变化

Identifying changes over time

肯定有类似的问题出现过,但是我一直没能找到...

我有一个包含时间序列数据的原始数据集,包括 'from' 和 'to' 日期字段。

问题是,当加载数据时,即使没有值发生变化,也会创建新记录('to' 添加到旧记录的日期,新记录 'from' 加载日期)。 我想将其转换为 table,它仅显示每个 genuine 更改的行 - 以及反映这一点的从/到日期。

例如,源数据如下所示:

ID Col1 Col2 Col3 From To
Test1 1 1 1 01/01/2020 31/12/9999
Test2 1 2 3 01/01/2020 30/06/2020
Test2 1 2 3 01/07/2020 30/09/2020
Test2 3 2 1 01/10/2020 31/12/9999

Test2 的前两条记录(第 2 行和第 3 行)基本相同 - 在 2020 年 1 月 7 日加载第二行时没有变化。我想要 01/01/2020 - 30/09/2020 期间没有变化的单行:

ID Col1 Col2 Col3 From To
Test1 1 1 1 01/01/2020 31/12/9999
Test2 1 2 3 01/01/2020 30/09/2020
Test2 3 2 1 01/10/2020 31/12/9999

对于这个简化的示例,我可以通过按每一列(日期除外)分组并使用 MIN from date/ MAX end date 来实现:

SELECT
ID, Col1, Col2, Col3, MIN(From) AS From, MAX(To) as TO
FROM TABLE
GROUP BY ID, Col1, Col2, Col3

但是,如果值发生变化然后又变回之前的值,这将不起作用,例如

ID Col1 Col2 Col3 From To
Test1 1 1 1 01/01/2020 31/12/9999
Test2 1 2 3 01/01/2020 30/04/2020
Test2 1 2 3 01/05/2020 30/06/2020
Test2 3 2 1 01/07/2020 30/10/2020
Test2 1 2 3 01/11/2020 31/12/9999

在上面的代码中简单地使用 MIN/MAX 会 return 这个 - 所以看起来两组值在 2020 年 1 月 7 日 - 2020 年 10 月 30 日期间有效:

ID Col1 Col2 Col3 From To
Test1 1 1 1 01/01/2020 31/12/9999
Test2 1 2 3 01/01/2020 31/12/9999
Test2 3 2 1 01/07/2020 30/10/2020

而实际上第一组值在此期间之前和之后均有效,但在此期间无效。 从 01/01/2020 到 30/06/2020 期间,当此 ID 没有变化时,它应该 return 一行而不是两行,但是当值不同时,它应该是另一行,然后是另一行,它恢复为初始值,但具有新的起始日期。

ID Col1 Col2 Col3 From To
Test1 1 1 1 01/01/2020 31/12/9999
Test2 1 2 3 01/01/2020 30/06/2020
Test2 3 2 1 01/07/2020 30/10/2020
Test2 1 2 3 01/11/2020 31/12/9999

我正在努力概念化如何处理这个问题。 我猜我需要以某种方式使用 LAG,但不确定如何应用它 - 例如,首先对阶段中的所有内容进行排名 table,然后使用 LAG 比较整行的串联?

我确定我最终会找到一个捏造的方法,但我毫不怀疑这个问题已经解决了很多次所以希望有人能给我指出一个比我不可避免地想出的更简单/更整洁的解决方案与...

高级差距和岛屿

我认为这是一个高级的“差距和孤岛”问题。使用它作为搜索词,您会发现大量关于该主题的文献。唯一的区别是通常只跟踪一列,但你有 3.

无间隙假设

此脚本的一个主要假设是重叠日期之间没有间隙,换句话说,它假设前几行 ToDate = 当前 FromDate - 1 天。 不确定是否需要考虑差距,只需将标准添加到 IsChanged 来检查就很简单

Multi-Column 差距和孤岛解决方案

DROP TABLE IF EXISTS #Grouping
DROP TABLE IF EXISTS #Test
CREATE TABLE #Test (ID INT IDENTITY(1,1),TestName Varchar(10),Col1 INT,Col2 INT,Col3 INT,FromDate Date,ToDate DATE)

INSERT INTO #Test VALUES
('Test1',1,1,1,'2020-01-01','9999-12-31')
,('Test2',1,2,3,'2020-01-01','2020-04-30')
,('Test2',1,2,3,'2020-05-01','2020-06-30')
,('Test2',3,2,1,'2020-07-01','2020-10-30')
,('Test2',1,2,3,'2020-11-01','9999-12-31')

;WITH cte_Prev AS (
    SELECT *
    ,PrevCol1 = LAG(Col1) OVER (PARTITION BY TestName       ORDER BY FromDate)
    ,PrevCol2 = LAG(Col2) OVER (PARTITION BY TestName       ORDER BY FromDate)
    ,PrevCol3 = LAG(Col3) OVER (PARTITION BY TestName       ORDER BY FromDate)
    FROM #Test
), cte_Compare AS (
    SELECT *
    ,IsChanged = CASE
        WHEN Col1 = PrevCol1
            AND Col2 = PrevCol2
            AND Col3 = PrevCol3
        THEN 0 /*No change*/
        ELSE 1 /*Iterate so new group created */
    END
    FROM cte_Prev
)

SELECT *,GroupID = SUM(IsChanged) OVER (PARTITION BY TestName ORDER BY ID)
INTO #Grouping
FROM cte_Compare

/*Raw unformatted data so you can see how it works*/
SELECT *
FROM #Grouping

/*Aggregated results*/
SELECT GroupID,TestName,Col1,Col2,Col3
    ,FromDate = MIN(FromDate)
    ,ToDate = MAX(ToDate)
    ,NumberOfRowsCollapsedIntoOneRow = COUNT(*)
FROM #Grouping
GROUP BY GroupID,TestName,Col1,Col2,Col3