当前行和上一行之间具有特定值的窗口函数

Posted

技术标签:

【中文标题】当前行和上一行之间具有特定值的窗口函数【英文标题】:Window Functions between current row and previous row with specific value 【发布时间】:2019-08-27 19:19:09 【问题描述】:

我希望能够将当前行与最新的前一行之间的某一列中的值与另一列中的某个值相加。

在此示例中,我想对当前行和最新行之间的 Val 列求和,RecType 为 2,按 RowNum 排序的 ID 分区。

DECLARE @ExampleTable TABLE
(
    Id INT,
    RowNum INT,
    RecType INT,
    Val INT
)


INSERT INTO @ExampleTable
    (Id, RowNum, RecType, Val)
VALUES
    (1, 1, 1, 1),
    (1, 2, 2, 2),
    (1, 3, 1, 4),
    (1, 4, 1, 8),
    (1, 5, 1, 16),
    (1, 6, 2, 32),
    (1, 7, 1, 64),

    (2, 1, 2, 1),
    (2, 2, 2, 2),
    (2, 3, 1, 4),
    (2, 4, 1, 8),
    (2, 5, 1, 16),
    (2, 6, 1, 32),
    (2, 7, 2, 64)

我希望得到如下结果:

DECLARE @Results TABLE
(
    Id  INT,
    RowNum INT,
    SumSinceLast2 INT
)


INSERT INTO @Results
    (Id, RowNum, SumSinceLast2)
VALUES
    (1, 1, 0),
    (1, 2, 0),
    (1, 3, 6), -- 4 + 2
    (1, 4, 14), -- 4 + 2 + 8
    (1, 5, 30), -- 16 + 8 + 4 + 2
    (1, 6, 62), -- 32 + 16 + 8 + 4 + 2
    (1, 7, 96), -- 64 + 32
    (2, 1, 0),
    (2, 2, 3), -- 2 + 1
    (2, 3, 6), -- 4 + 2
    (2, 4, 14), -- 8 + 4 + 2
    (2, 5, 30), -- 16 + 8 + 4 + 2
    (2, 6, 62), -- 32 + 16 + 8 + 4 + 2
    (2, 7, 126) -- 64 + 32 + 16 + 8 + 4 + 2

这是我应该能够在 SQL Server 2017 中轻松完成的事情吗?我希望在这里可以使用窗口函数。

【问题讨论】:

我觉得逻辑有点混乱。值为“2”的行与前2行相加? val=32 相加两次,在RowNums 的两组id 1 中,对吗? @Serg 正确 - 因为我想将当前行与所有先前行相加,直到并包括前一行,rec 类型为 2。所以第 6 行包含它,因为该窗口位于行之间第 2 行和第 6 行。第 7 行包含它,因为该窗口应该在第 6 行和第 7 行之间。 【参考方案1】:

这并不能完全返回您想要的结果,但结果似乎更合理。每个“2”开始一个新组。然后将这些值在组内累加求和:

select e.*,
        (case when grp_2 = 0
              then 0
              else sum(val) over (partition by id, grp_2 order by rownum)
         end) as result
 from (select e.*,
              sum(case when RecType = 2 then 1 else 0 end) over
                  (partition by id
                   order by rownum
                  ) as grp_2
       from @ExampleTable e
      ) e
 order by id, rownum;

Here 是一个 dbfiddle。

可以调整结果(这会使查询变得更加混乱)以按照您拥有它们的方式“修复”“2”的值。但是,这个版本对我来说更有意义,因为“2”不计入两个单独的组。

这是一个对“2”进行双重计算的调整版本:

 select e.*,
        (case when grp_2 = 0 or grp_2 = 1 and RecType = 2
              then 0
              when RecType <> 2
              then sum(val) over (partition by id, grp_2 order by rownum)
              else sum(val) over (partition by id, grp_2_desc) + lag(val) over (partition by id, Rectype order by rownum)
         end) as result
 from (select e.*,
              sum(case when RecType = 2 then 1 else 0 end) over
                  (partition by id
                   order by rownum
                  ) as grp_2,
              sum(case when RecType = 2 then 1 else 0 end) over
                  (partition by id
                   order by rownum desc
                  ) as grp_2_desc
       from @ExampleTable e
      ) e
 order by id, rownum;

【讨论】:

【参考方案2】:

我知道已经有解决方案了,但是既然我写了代码,我还是要把它贴在这里。

--Sum the range
select 
    et.Id
    ,a.CurrentRow
    ,sum(CASE WHEN ClosestMinRow = CurrentRow THEN 0 ELSE et.Val end) --When there is no previous 2 then set them to 0
from 
    @ExampleTable et
join 
(
    --Create begin and end range
    select 
        et.Id
        ,et.RowNum CurrentRow
        ,ISNULL(FloorRange.RowNum,et.RowNum) ClosestMinRow
    from 
        @ExampleTable ET
    OUTER Apply (
        -- Get the RecType = 2 in order to create a range
        select 
            MAX(RowNum) RowNum
        from 
            @ExampleTable et2
        WHERE 
            RecType = 2
            AND et2.RowNum < ET.RowNum
            AND et2.Id = et.Id
    ) FloorRange
) a
    ON et.Id = a.Id
    and et.RowNum between a.ClosestMinRow and CurrentRow
GROUP BY 
    et.Id
    ,a.CurrentRow
order by 
    et.Id
    ,a.CurrentRow

【讨论】:

以上是关于当前行和上一行之间具有特定值的窗口函数的主要内容,如果未能解决你的问题,请参考以下文章

在oracle sql中比较当前行和上一行

在每个当前行和上一行 BigQuery 之间查找 MAX、AVG

从当前行和上一行计算 2 列

当前行和上一行之间的秒数差异,并使用 google bigquery 将值存储在单独的列中

当前行和上一行之间的秒数差异,并在订单 ID 也使用 google bigquery 匹配时将值存储在单独的列中

eclipse常用快捷键