如何使用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;
-- */
我正在尝试对数据集做出临时决策。让我用一个简单的 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;
-- */