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 及更高版本,您也可以尝试以下解决方案。
- 我在第一个内联视图“t”中构建了一个有用的内联冒号“gaps”。
- 然后,在外部内联视图“tt”中,我基于之前的“gaps”列构建了另一个内联列“group_id”。该列的目的是填补“空白”列的空白。
- 最后,我将所有行按“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
我有一个 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 及更高版本,您也可以尝试以下解决方案。
- 我在第一个内联视图“t”中构建了一个有用的内联冒号“gaps”。
- 然后,在外部内联视图“tt”中,我基于之前的“gaps”列构建了另一个内联列“group_id”。该列的目的是填补“空白”列的空白。
- 最后,我将所有行按“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