SQL 查询在不破坏时间顺序的情况下对连续记录进行分组

SQL query to group consecutive records without destroying the chronological order

我有一个 table 看起来像这样:

GR I VON BIS
1 a 1 2
2 b 2 3
1 c 3 4
1 d 4 5
3 e 5 6

“GR”列是我要用于分组的信息。
“I”列包含一些 none 重要信息。
“VON”列最初包含一个日期值 - 为了便于阅读,我在这里使用了一个数字。
“BIS”列也是如此。

因此记录包含一些在“VON”和“BIS”指定的时间间隔内有效的信息,应按“GR”分组。

我正在尝试构建查询以获得如下结果:

GR I VON BIS
1 a 1 2
2 b 2 3
1 c 3 5
3 e 5 6

这意味着,我想合并具有相同分组信息的行 - 在示例中合并记录 3 和 4。

我想到的是这个查询 - 但这只适用于不同的组:

select distinct  gr
      ,first_value(i) over(partition by gr order by von asc) i
      ,first_value(von) over(partition by gr order by von asc) von
      ,first_value(bis) over(partition by gr order by von desc) bis
  from table_test
;

根据我们的测试数据得出这个结果,这显然是错误的:

GR I VON BIS
1 a 1 5
2 b 2 3
3 e 5 6

关于如何通过查询解决这个问题有什么想法吗? (我知道我可以使用 PL/SQL,但它必须是纯 SQL ...)

从 Oracle 12 开始,您可以使用 MATCH_RECOGNIZE 执行 row-by-row 处理:

SELECT *
FROM   table_test
MATCH_RECOGNIZE(
  ORDER BY von
  MEASURES
    FIRST(gr) AS gr,
    FIRST(i) AS i,
    FIRST(von) AS von,
    LAST(bis) AS bis
  PATTERN (same_gr+)
  DEFINE same_gr AS FIRST(gr) = gr
)

其中,对于您的示例数据:

CREATE TABLE table_test (GR, I, VON, BIS) AS
SELECT 1, 'a', 1, 2 FROM DUAL UNION ALL
SELECT 2, 'b', 2, 3 FROM DUAL UNION ALL
SELECT 1, 'c', 3, 4 FROM DUAL UNION ALL
SELECT 1, 'd', 4, 5 FROM DUAL UNION ALL
SELECT 3, 'e', 5, 6 FROM DUAL;

输出:

GR I VON BIS
1 a 1 2
2 b 2 3
1 c 3 5
3 e 5 6

db<>fiddle here

如果您使用的不是 Oracle 12c 及更高版本,您也可以尝试以下解决方案。

  1. 我在第一个内联视图“t”中构建了一个有用的内联冒号“gaps”。
  2. 然后,在外部内联视图“tt”中,我基于之前的“gaps”列构建了另一个内联列“group_id”。该列的目的是填补“空白”列的空白。
  3. 最后,我将所有行按“GR”和“group_id”列分组,然后应用 min()keep() 子句以获得所需的输出
SELECT   GR
       , MIN(I)KEEP(dense_rank FIRST ORDER BY VON ASC) AS I
       , MIN(VON)KEEP(dense_rank FIRST ORDER BY VON ASC) AS VON
       , MIN(VON)KEEP(dense_rank LAST ORDER BY VON ASC) AS BIS         
FROM (
  SELECT  GR, I, VON, BIS, last_value( GAPS IGNORE NULLS )OVER( ORDER BY von ) group_id
   FROM (
     select t.*
     , CASE WHEN GR != LAG(GR, 1, -GR) OVER ( ORDER BY VON ) 
                 THEN ROW_NUMBER()OVER(ORDER BY VON)
            ELSE NULL
       END AS gaps      
    from your_tab t
  ) t
) tt
GROUP BY GR, group_id
ORDER BY VON
;

db<>fiddle here