如何使用SQL中的动态函数得到蛇纹?

How to use dynamic functions in SQL to get a snake pattern?

我正在尝试对数据集做出临时决策。让我用一个简单的 table:

来解释这个问题
Time    Var1    Var2    Var3    Var4    Var5    Var6    Total
0:00    1.69    3.27    4.80    2.14    0.70    2.14    
0:05    2.73    2.73    1.60    1.20    0.46    2.14    
0:10    5.45    2.69    4.62    1.15    1.03    4.29    16.28
0:15    2.07    4.74    2.14    1.50    0.43    2.37    
0:20    1.71    4.62    1.79    1.29    0.73    2.37    
0:25    1.88    3.60    4.00    2.09    0.56    2.25    
0:30    5.22    8.57    1.54    2.20    0.48    1.13    14.13
0:35    5.00    5.63    2.93    1.32    1.03    2.05    
0:40    4.29    5.29    2.55    1.14    0.38    1.48

我想做的是对同一时间段内的所有变量求和,直到达到 5 或大于 5。一旦达到大于 5,我减去 5 得到余数,然后继续添加余数赋给下一个时间段的下一个变量,依此类推。对于第一个合计显示的结果,路径将是下一个:

Time    Var1    Var2    Var3    Var4    Var5    Var6    Total
0:00    1.69    3.27    4.80            
0:05                            1.20        
0:10                                    1.03    4.29    16.28

我正在尝试查找每个时间段的路径和总数。

我的 table 是垂直制作的,而不是示例中所示的水平制作。到目前为止,我已经使用以下方法获得了所有变量的 cumulative_addition:

sum(value) over(partition by variable, time order by variable) as cumulative_addition

但我不知道这是否应该是我应该迈出的第一步。我还考虑过在查询中组合超前和滞后函数,但是当我尝试增加时间段时遇到问题:

 Time   Variable    Value   Cumulative_addition
0:00    Var1        1.69    1.69
0:00    Var2        3.27    4.96
0:00    Var3        4.80    9.76
0:00    Var4        2.14    11.90
0:00    Var5        0.70    12.60
0:00    Var6        2.14    14.74
0:05    Var1        2.73    17.47
0:05    Var2        2.73    20.20
0:05    Var3        1.60    21.80
0:05    Var4        1.20    23.00

所以在 9.76 中我应该执行 9.76-5=4.76 并跳转到 0:05 并在 var4 中继续添加直到我再次达到 5。

你认为我可以使用 window 函数解决这个问题吗?

提前感谢您的帮助

据我所知,这不是答案,只是测试用例。 @zhivab 请评论。

-- mock table to start with
declare @t table(
 [Time] time
,Var1 decimal(5,2)
,Var2 decimal(5,2)
,Var3 decimal(5,2)
,Var4 decimal(5,2)
,Var5 decimal(5,2)
,Var6 decimal(5,2)    
)
insert @t([Time], Var1, Var2, Var3, Var4, Var5, Var6)
values    
 ('0:00',1.69, 3.27, 4.80, 2.14,  0.70, 2.14)    
,('0:05',2.73, 2.73, 1.60, 1.20,  0.46, 2.14)    
,('0:10',5.45, 2.69, 4.62, 1.15,  1.03, 4.29)    
,('0:15',2.07, 4.74, 2.14, 1.50,  0.43, 2.37)    
,('0:20',1.71, 4.62, 1.79, 1.29,  0.73, 2.37)    
,('0:25',1.88, 3.60, 4.00, 2.09,  0.56, 2.25)    
,('0:30',5.22, 8.57, 1.54, 2.20,  0.48, 1.13)    
,('0:35',5.00, 5.63, 2.93, 1.32,  1.03, 2.05)    
,('0:40',4.29, 5.29, 2.55, 1.14,  0.38, 1.48);
-- this is how the real table looks like
with realTable as(
    select [Time], n, val 
    from @t
    cross apply ( values (1, Var1), (2, Var2),(3, Var3),(4, Var4),(5, Var5)) a (n, val )
)
-- how data are grouped, 3 levels tgrp + tsubgrp + n
select [Time], datediff(MINUTE, '00:00', [Time]) / 15 tgrp
     , datediff(MINUTE, '00:00', [Time]) % 15 tsubgrp
     , n, val
from realTable;

任务在每个 tgrp 中

  • 从 tsubgrp = 0 开始(按 n)取 1..n0 个值,直到 sum exeeds 5,记住 n0,(s0 mod 5)

  • 从 tsubgrp = 5 取第一个 n0..n1 值直到 (s0 mod 5) + sum exeeds 5 记住 n1, (s1 mod 5)

  • 从 tsubgrp = 10 中获取第一个 n1..n2 值直到 (s1 mod 5) + sum exeeds 5

    获取 tgrp 的每个 tsubgrp 中所选值的总和

我想出了一个解决方案,但它钉钉子而且不是那么优雅。

基本上,您一次加载一个变量并检查总数是否超过 5。如果超过 5,则将时间增加 5 分钟并从计算中减去 5。

create table #t
(
 [Time]  time
 ,[Variable] varchar(10)
 ,    [Value] numeric(5,2)
 --,   [Cumulative_addition] as numeric(5,2)
 )
 insert into #t
 values
('0:00'    ,'Var1'        ,1.69)    --1.69)
,('0:00'    ,'Var2'        ,3.27)   -- 4.96
,('0:00'   ,'Var3'        ,4.80  )  --9.76
,('0:00'    ,'Var4'        ,2.14  )  --11.90
,('0:00'    ,'Var5'        ,0.70   ) --12.60
,('0:00'    ,'Var6'        ,2.14    )--14.74
,('0:05'    ,'Var1'        ,2.73)    --17.47
,('0:05'    ,'Var2'        ,2.73 )   --20.20
,('0:05'    ,'Var3'        ,1.60  )  --21.80
,('0:05'    ,'Var4'        ,1.20   ) --23.00


declare @var1 numeric(7,4)
declare @var2 numeric(7,4)
declare @var3 numeric(7,4)
declare @var4 numeric(7,4)
declare @var5 numeric(7,4)
declare @var6 numeric(7,4)
declare @total numeric(7,4) = 0
declare @calc numeric(7,4) = 0
declare @time time ='0:00'

select @var1 = [Value] from #t where time = @time and [Variable] = 'Var1'
set @calc=@calc+@var1
set @total = @total+@var1

if @calc>=5 
Begin
    set @time = dateadd(mi,5,@time)
    set @calc = @calc-5
End 

select @var2 = [Value] from #t where time = @time and [Variable] = 'Var2'
set @calc=@calc+@var2
set @total = @total+@var2
select 2,@calc

if @calc>=5.00 
Begin
    set @time = dateadd(mi,5,@time)
    set @calc = @calc-5
End 

select @var3 = [Value] from #t where time = @time and [Variable] = 'Var3'
set @calc=@calc+@var3
set @total = @total+@var3

if @calc>=5 
Begin
    set @time = dateadd(mi,5,@time)
    set @calc = @calc-5
End 


select @var4 = [Value] from #t where time = @time and [Variable] = 'Var4'
set @calc=@calc+@var4
set @total = @total+@var4

if @calc>=5 
Begin
    set @time = dateadd(mi,5,@time)
    set @calc = @calc-5
End 


select @var5 = [Value] from #t where time = @time and [Variable] = 'Var5'
set @calc=@calc+@var5
set @total = @total+@var5

if @calc>=5 
Begin
    set @time = dateadd(mi,5,@time)
    set @calc = @calc-5
End 


select @var6 = [Value] from #t where time = @time and [Variable] = 'Var6'
set @calc=@calc+@var6
set @total = @total+@var6

select var1=@var1,var2=@var2,var3=@var3,var4=@var4,var5=@var5,var6=@var6,total=@total
select * from #t
drop table #t

结果(由于数据有限):

var1    var2    var3    var4    var5    var6    total
1.6900  3.2700  4.8000  1.2000  NULL    NULL    NULL

这个答案为变量提供了一个循环并将它们加载到 table:

create table #t
(
 [Time]  time
 ,[Variable] varchar(10)
 ,    [Value] numeric(5,2)
 --,   [Cumulative_addition] as numeric(5,2)
 )
 insert into #t
 values
('0:00'    ,'Var1'        ,1.69)    --1.69)
,('0:00'    ,'Var2'        ,3.27)   -- 4.96
,('0:00'   ,'Var3'        ,4.80  )  --9.76
,('0:00'    ,'Var4'        ,2.14  )  --11.90
,('0:00'    ,'Var5'        ,0.70   ) --12.60
,('0:00'    ,'Var6'        ,2.14    )--14.74
,('0:05'    ,'Var1'        ,2.73)    --17.47
,('0:05'    ,'Var2'        ,2.73 )   --20.20
,('0:05'    ,'Var3'        ,1.60  )  --21.80
,('0:05'    ,'Var4'        ,1.20   ) --23.00

declare @v as numeric(7,4)
declare @total numeric(7,4) = 0
declare @calc numeric(7,4) = 0
declare @time time ='0:00'
declare @i int = 1

create table #answers (variable int, [Time] time, Value numeric(7,4))

while(@i<=6)
begin
    select @v=[Value] 
        from #t where time = @time and [Variable] = 'Var' + cast(@i as varchar(1))
    set @calc=@calc+@v
    set @total = @total+@v

    insert into #answers
    values(@i,@time,@v)

    if @calc>=5 
    Begin
        set @time = dateadd(mi,5,@time)
        set @calc = @calc-5
    End 
    set @i=@i+1
    set @v=null
end

select *
from #answers

drop table #t,#answers

结果:

variable    Time    Value
1   00:00:00.0000000    1.6900
2   00:00:00.0000000    3.2700
3   00:00:00.0000000    4.8000
4   00:05:00.0000000    1.2000
5   00:10:00.0000000    NULL
6   00:10:00.0000000    NULL

一个 set-based 蛇求和脚本,它可以 运行 比 loop-based 更快,因为蛇可以平行移动。玩得开心。

-- Mock table to visualize groups and subgroups
create table #t(
     [Time] time
    ,Var1 decimal(5,2)
    ,Var2 decimal(5,2)
    ,Var3 decimal(5,2)
    ,Var4 decimal(5,2)
    ,Var5 decimal(5,2)
    ,Var6 decimal(5,2)    
)
insert #t([Time], Var1, Var2, Var3, Var4, Var5, Var6)
values
     -- group 1    
     ('0:00', 1.69, 3.27, 4.80, 2.14, 0.70, 2.14)    
    ,('0:05', 2.73, 2.73, 1.60, 1.20, 0.46, 2.14)    
    ,('0:10', 5.45, 2.69, 4.62, 1.15, 1.03, 4.29)
     -- group 2           
    ,('0:15', 2.07, 4.74, 2.14, 1.50, 0.43, 2.37)    
    ,('0:20', 1.71, 4.62, 1.79, 1.29, 0.73, 2.37)    
    ,('0:25', 1.88, 3.60, 4.00, 2.09, 0.56, 2.25) 
     -- group 3           
    ,('0:30', 5.22, 8.57, 1.54, 2.20, 0.48, 1.13)    
    ,('0:35', 5.00, 5.63, 2.93, 1.32, 1.03, 2.05)    
    ,('0:40', 4.29, 5.29, 5.55, 1.14, 0.38, 1.48); -- this snake will hit the bottom.

-- Task parameters
declare @sumLimit decimal(5,2) = 5.0;
declare @grpStep int = 15; -- minutes
declare @subgrpStep int = 5; -- minutes
declare @nvars int = 6;

-- This is how the real table looks like
with realTable as(
    select [Time], n, val 
    from #t
    cross apply( values (1, Var1), (2, Var2), (3, Var3), (4, Var4), (5, Var5), (6, Var6)) a (n, val )
)
-- How data are grouped, 3 levels tgrp + tsubgrp + n
, grp as(
    select [Time], datediff(MINUTE, '00:00', [Time]) / @grpStep tgrp
         , datediff(MINUTE, '00:00', [Time]) % @grpStep tsubgrp
         , n, val
    from realTable
)
-- Snakes are moving
, snake as (
    select [Time], tgrp, tsubgrp, n, val
        , s = val % @sumLimit
        -- should the snake move down?
        , step = case when val > @sumLimit then @subgrpStep else 0 end
    from grp
    where tsubgrp = 0 and n = 1
    union all
    select grp.[Time], snake.tgrp, grp.tsubgrp, grp.n, grp.val
       , s = cast((s + grp.val) % @sumLimit as decimal(5,2))
       , step = case when s + grp.val > @sumLimit then @subgrpStep else 0 end
    from grp
    join snake on snake.tgrp = grp.tgrp        
       and grp.n = snake.n + 1 -- always move right
       and grp.tsubgrp = snake.tsubgrp + snake.step -- and down when needed
    where grp.n <= @nvars
       and case when s > @sumLimit then snake.tsubgrp + @subgrpStep else snake.tsubgrp end <= @grpStep     
)
-- select * from snake order by tgrp, tsubgrp, n; /*
select min([Time]) gstart, max([Time]) gend, sum(val) [sum]
from snake
group by tgrp
order by tgrp;
-- */