SQL window函数传播帧值
SQL window function to spread frame value
我有以下 table(项):
index (number)
type (string)
id (number)
1
Other
2244596
2
FrameStart
888
3
Other
235235
4
Other
8957567
5
Other
14124
6
FrameEnd
0
7
Other
275823
8
Other
789798
如果行在 FrameStart 和 FrameEnd 之间,是否可以添加带有帧 ID 的第 4 列,否则为空:
index (number)
type (string)
id (number)
test
1
Other
2244596
2
FrameStart
888
888
3
Other
235235
888
4
Other
8957567
888
5
Other
14124
888
6
FrameEnd
0
7
Other
275823
8
Other
789798
我试着像下面那样做
SELECT
index,
type,
id,
CASE WHEN (type = 'FrameStart') THEN id WHEN (type = 'FrameEnd') THEN null ELSE LAG(test) OVER(ORDER BY index) END as test
FROM Items
但是,当然,LAG(test)
不能用。
如果您的 index
列是真实序列,您可以使用递归 CTE(如果不是,您必须添加额外的 row_number() 列并使用它)。
当您看到 FramaStart 时,您会在 index
上完成 table,您会保留 ID,当您看到 FragmeEnd 时,您会重置它,在所有其他情况下,您复制之前的值。
例子
with t1 ( "INDEX", type, id, test) as (
select
"INDEX", type, id,
case when type = 'FrameStart' then id end as test
from tab1 where "INDEX" = (select min("INDEX") from tab1)
union all
select
tab1."INDEX", tab1.type, tab1.id,
case
when tab1.type = 'FrameStart' then tab1.id
when tab1.type = 'FrameEnd' then null else t1.test end
from tab1
join t1 on t1."INDEX"+1 = tab1."INDEX"
)
select * from t1;
INDEX TYPE ID TEST
---------- ---------- ---------- ----------
1 Other 2244596
2 FrameStart 888 888
3 Other 235235 888
4 Other 8957567 888
5 Other 14124 888
6 FrameEnd 0
7 Other 275823
8 Other 789798
从 Oracle 12c 开始,您可以使用 MATCH_RECOGNIZE
:
SELECT id,
"INDEX",
type,
CASE type
WHEN 'FrameEnd' THEN NULL
ELSE test
END AS test
FROM table_name
MATCH_RECOGNIZE (
ORDER BY "INDEX"
MEASURES
framestart.id AS test
ALL ROWS PER MATCH
PATTERN ( framestart other*? frameend | other )
DEFINE
framestart AS type = 'FrameStart',
frameend AS type = 'FrameEnd',
other AS 1 = 1
)
或者如果您还想匹配没有 frameend
的尾随 framestart
,您可以使用 PATTERN ( framestart other*? (frameend | $) | other )
。
其中,对于示例数据:
CREATE TABLE table_name ("INDEX", type, id) AS
SELECT 1, 'Other', 2244596 FROM DUAL UNION ALL
SELECT 2, 'FrameStart', 888 FROM DUAL UNION ALL
SELECT 3, 'Other', 235235 FROM DUAL UNION ALL
SELECT 4, 'Other', 8957567 FROM DUAL UNION ALL
SELECT 5, 'Other', 14124 FROM DUAL UNION ALL
SELECT 6, 'FrameEnd', 0 FROM DUAL UNION ALL
SELECT 7, 'Other', 275823 FROM DUAL UNION ALL
SELECT 8, 'Other', 789798 FROM DUAL;
注意:不要使用INDEX
(或其他保留字)作为列名。
输出:
ID
INDEX
TYPE
TEST
2244596
1
Other
null
888
2
FrameStart
888
235235
3
Other
888
8957567
4
Other
888
14124
5
Other
888
0
6
FrameEnd
null
275823
7
Other
null
789798
8
Other
null
db<>fiddle here
@MT0 用一个更好的答案打败了我,但这里有一个使用窗口函数的选项...
WITH
framed AS
(
SELECT
items.*,
SUM(CASE WHEN type IN ('FrameStart', 'FrameEnd') THEN 1 ELSE 0 END) OVER (ORDER BY ix) AS frame_id
FROM
items
)
SELECT
framed.*,
MAX(CASE WHEN type = 'FrameStart' THEN id END) OVER (PARTITION BY frame_id)
FROM
framed
ORDER BY
ix
演示:https://dbfiddle.uk/?rdbms=oracle_21&fiddle=b8a0150b46315256f189506c5fb76fc5
为 Sql 服务器解决了我认为在 Oracle 中会类似:
;with start_end_frames as
(
select indx,type,id
from Items
where type = 'FrameStart' or type = 'FrameEnd'
)
, match_start_end as
(
select indx,
lead(indx)over(order by indx) as nextIndx,
type,
lead(type)over(order by indx) as nextType,
id
from start_end_frames
)
, frame_intervals as
(
select indx,nextIndx,id
from match_start_end
where type = 'FrameStart' and nextType = 'FrameEnd'
)
select i.indx,i.type,i.id,f.id
from frame_intervals f right join Items i
on f.indx <= i.indx and i.indx < f.nextIndx
添加列后,可以通过合并对其进行更新。
下面的代码获取计算排名的第一个值。
alter table Items add test number;
MERGE INTO Items Tgt
USING
(
SELECT "index"
, FIRST_VALUE(CASE WHEN "type" LIKE 'Frame%Start' THEN id END)
OVER (PARTITION BY Rnk ORDER BY "index") as FrameStartId
FROM
(
SELECT "index", "type", id
, SUM(CASE WHEN "type" LIKE 'Frame%' THEN 1 ELSE 0 END)
OVER (ORDER BY "index") AS Rnk
FROM Items itm
ORDER BY "index"
) q
) Src
ON (Tgt."index" = Src."index" AND Src.FrameStartId IS NOT NULL)
WHEN MATCHED THEN
UPDATE SET test = Src.FrameStartId;
select *
from Items
order by "index"
index | type | ID | TEST
----: | :--------- | ------: | ---:
1 | Other | 2244596 | null
2 | FrameStart | 888 | 888
3 | Other | 235235 | 888
4 | Other | 8957567 | 888
5 | Other | 14124 | 888
6 | FrameEnd | 0 | null
7 | Other | 275823 | null
8 | Other | 789798 | null
演示 db<>fiddle here
我有以下 table(项):
index (number) | type (string) | id (number) |
---|---|---|
1 | Other | 2244596 |
2 | FrameStart | 888 |
3 | Other | 235235 |
4 | Other | 8957567 |
5 | Other | 14124 |
6 | FrameEnd | 0 |
7 | Other | 275823 |
8 | Other | 789798 |
如果行在 FrameStart 和 FrameEnd 之间,是否可以添加带有帧 ID 的第 4 列,否则为空:
index (number) | type (string) | id (number) | test |
---|---|---|---|
1 | Other | 2244596 | |
2 | FrameStart | 888 | 888 |
3 | Other | 235235 | 888 |
4 | Other | 8957567 | 888 |
5 | Other | 14124 | 888 |
6 | FrameEnd | 0 | |
7 | Other | 275823 | |
8 | Other | 789798 |
我试着像下面那样做
SELECT
index,
type,
id,
CASE WHEN (type = 'FrameStart') THEN id WHEN (type = 'FrameEnd') THEN null ELSE LAG(test) OVER(ORDER BY index) END as test
FROM Items
但是,当然,LAG(test)
不能用。
如果您的 index
列是真实序列,您可以使用递归 CTE(如果不是,您必须添加额外的 row_number() 列并使用它)。
当您看到 FramaStart 时,您会在 index
上完成 table,您会保留 ID,当您看到 FragmeEnd 时,您会重置它,在所有其他情况下,您复制之前的值。
例子
with t1 ( "INDEX", type, id, test) as (
select
"INDEX", type, id,
case when type = 'FrameStart' then id end as test
from tab1 where "INDEX" = (select min("INDEX") from tab1)
union all
select
tab1."INDEX", tab1.type, tab1.id,
case
when tab1.type = 'FrameStart' then tab1.id
when tab1.type = 'FrameEnd' then null else t1.test end
from tab1
join t1 on t1."INDEX"+1 = tab1."INDEX"
)
select * from t1;
INDEX TYPE ID TEST
---------- ---------- ---------- ----------
1 Other 2244596
2 FrameStart 888 888
3 Other 235235 888
4 Other 8957567 888
5 Other 14124 888
6 FrameEnd 0
7 Other 275823
8 Other 789798
从 Oracle 12c 开始,您可以使用 MATCH_RECOGNIZE
:
SELECT id,
"INDEX",
type,
CASE type
WHEN 'FrameEnd' THEN NULL
ELSE test
END AS test
FROM table_name
MATCH_RECOGNIZE (
ORDER BY "INDEX"
MEASURES
framestart.id AS test
ALL ROWS PER MATCH
PATTERN ( framestart other*? frameend | other )
DEFINE
framestart AS type = 'FrameStart',
frameend AS type = 'FrameEnd',
other AS 1 = 1
)
或者如果您还想匹配没有 frameend
的尾随 framestart
,您可以使用 PATTERN ( framestart other*? (frameend | $) | other )
。
其中,对于示例数据:
CREATE TABLE table_name ("INDEX", type, id) AS
SELECT 1, 'Other', 2244596 FROM DUAL UNION ALL
SELECT 2, 'FrameStart', 888 FROM DUAL UNION ALL
SELECT 3, 'Other', 235235 FROM DUAL UNION ALL
SELECT 4, 'Other', 8957567 FROM DUAL UNION ALL
SELECT 5, 'Other', 14124 FROM DUAL UNION ALL
SELECT 6, 'FrameEnd', 0 FROM DUAL UNION ALL
SELECT 7, 'Other', 275823 FROM DUAL UNION ALL
SELECT 8, 'Other', 789798 FROM DUAL;
注意:不要使用INDEX
(或其他保留字)作为列名。
输出:
ID INDEX TYPE TEST 2244596 1 Other null 888 2 FrameStart 888 235235 3 Other 888 8957567 4 Other 888 14124 5 Other 888 0 6 FrameEnd null 275823 7 Other null 789798 8 Other null
db<>fiddle here
@MT0 用一个更好的答案打败了我,但这里有一个使用窗口函数的选项...
WITH
framed AS
(
SELECT
items.*,
SUM(CASE WHEN type IN ('FrameStart', 'FrameEnd') THEN 1 ELSE 0 END) OVER (ORDER BY ix) AS frame_id
FROM
items
)
SELECT
framed.*,
MAX(CASE WHEN type = 'FrameStart' THEN id END) OVER (PARTITION BY frame_id)
FROM
framed
ORDER BY
ix
演示:https://dbfiddle.uk/?rdbms=oracle_21&fiddle=b8a0150b46315256f189506c5fb76fc5
为 Sql 服务器解决了我认为在 Oracle 中会类似:
;with start_end_frames as
(
select indx,type,id
from Items
where type = 'FrameStart' or type = 'FrameEnd'
)
, match_start_end as
(
select indx,
lead(indx)over(order by indx) as nextIndx,
type,
lead(type)over(order by indx) as nextType,
id
from start_end_frames
)
, frame_intervals as
(
select indx,nextIndx,id
from match_start_end
where type = 'FrameStart' and nextType = 'FrameEnd'
)
select i.indx,i.type,i.id,f.id
from frame_intervals f right join Items i
on f.indx <= i.indx and i.indx < f.nextIndx
添加列后,可以通过合并对其进行更新。
下面的代码获取计算排名的第一个值。
alter table Items add test number;
MERGE INTO Items Tgt USING ( SELECT "index" , FIRST_VALUE(CASE WHEN "type" LIKE 'Frame%Start' THEN id END) OVER (PARTITION BY Rnk ORDER BY "index") as FrameStartId FROM ( SELECT "index", "type", id , SUM(CASE WHEN "type" LIKE 'Frame%' THEN 1 ELSE 0 END) OVER (ORDER BY "index") AS Rnk FROM Items itm ORDER BY "index" ) q ) Src ON (Tgt."index" = Src."index" AND Src.FrameStartId IS NOT NULL) WHEN MATCHED THEN UPDATE SET test = Src.FrameStartId;
select * from Items order by "index"
index | type | ID | TEST ----: | :--------- | ------: | ---: 1 | Other | 2244596 | null 2 | FrameStart | 888 | 888 3 | Other | 235235 | 888 4 | Other | 8957567 | 888 5 | Other | 14124 | 888 6 | FrameEnd | 0 | null 7 | Other | 275823 | null 8 | Other | 789798 | null
演示 db<>fiddle here