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