简体   繁体   中英

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

I am trying to make temporal decisions over a data set. Let me explain the problem with a simple 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

What I am trying to do is to sum across all variables in the same time period until I reach 5 or greater than 5. Once I reach greater than 5, I substract 5 to get the remainder and I continue adding the remainder to the next variable in the next time period and so on. For the the result shown in the first total, the path will be the next one:

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

I am trying to find the path and the total for every time period.

My table is made vertically and not horizontally as shown in the example. So far I have gotten the cumulative_addition for all the variables using:

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

But I don't know if this should be the first step I should take. I have also thought about combining lead and lag functions in a query but then I have problems when I try to increment the time period:

 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

So in 9.76 I should do 9.76-5=4.76 and jump to 0:05 and in var4 to keep adding until I reach 5 again.

Do you think I can solve this problem by using window functions?

Thank you in advance for your help

Not an answer just test case as I understand it. @zhivab please comment.

-- 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;

The task is within every tgrp

  • take first (by n) 1..n0 vals from tsubgrp = 0 till sum exeeds 5, remember n0, (s0 mod 5)

  • take first n0..n1 vals from tsubgrp = 5 till (s0 mod 5) + sum exeeds 5 remember n1, (s1 mod 5)

  • take first n1..n2 vals from tsubgrp = 10 till (s1 mod 5) + sum exeeds 5

    get the sum of selected vals in every tsubgrp for the tgrp

This answer provides a loop for the variables and loads them into a 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

Results:

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

I came up with a solution but it hammering nails and not that elegant.

Basically, you are loading in variables one at a time and checking if the total is over 5. If it's over 5, then you increase the time 5 minutes and subtract 5 from the calc.

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

Results (due to limited data):

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

A set-based snake sum script, it could run faster then loop-based one as the snakes can move in parallel. Have fun.

-- 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;
-- */

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM