根据组和最大总和分配组
Posted
技术标签:
【中文标题】根据组和最大总和分配组【英文标题】:Assign groups based on a group and a maximum sum 【发布时间】:2019-10-31 16:02:52 【问题描述】:我需要按某些列和运行总和对行进行分组,直到达到阈值。我得到的最接近的是基于this answer 的查询,但这个解决方案并不像我需要的那样精确,因为总和必须在达到阈值时重置并重新启动。
这是我对一些样本数据和阈值 100 的尝试:
drop table #table
create table #table (
Id int not null,
GroupId int not null,
Code nvarchar(14) not null,
Total int not null
)
insert into #table values
( 1, 1, '1111', 20),( 2, 1, '1111', 75),( 3, 1, '1111', 40),( 4, 1, '1111', 20),
( 5, 1, '1111', 20),( 6, 1, '1111', 25),( 7, 1, '2222', 20),( 8, 1, '2222', 20),
( 9, 1, '2222', 20),(10, 1, '2222', 20),(11, 2, '3333', 10),(12, 2, '3333', 90),
(13, 2, '3333', 90),(14, 2, '3333', 90),(15, 2, '3333', 90),(16, 2, '3333', 10),
(17, 2, '3333', 10),(18, 2, '3333', 10),(19, 2, '3333', 10),(20, 2, '3333', 10),
(21, 2, '3333', 10),(22, 2, '3333', 10),(23, 2, '3333', 10),(24, 2, '3333', 10),
(25, 2, '3333', 10),(26, 2, '3333', 10),(27, 2, '3333', 10),(28, 2, '3333', 10),
(29, 2, '3333', 10),(30, 2, '3333', 10),(31, 2, '3333', 10),(32, 2, '3333', 10),
(33, 2, '3333', 10),(34, 2, '3333', 10),(35, 2, '3333', 10)
;with cte as (
select
Id, GroupId, Code, Total,
cast(sum(Total) OVER (ORDER BY Code, id) as int) / 100 AS Limit
from #table
)
select
*,
dense_rank() over(ORDER BY Code, Limit) as Groups
from cte order by id
这就是我得到的,我手动添加了一个“GroupsExpected”列来显示我实际需要的组。
| Id | GroupId | Code | Total | Limit | Groups | GroupsExpected |
|----|---------|------|-------|-------|--------|----------------|
| 1 | 1 | 1111 | 20 | 0 | 1 | 1 |
| 2 | 1 | 1111 | 75 | 0 | 1 | 1 |
| 3 | 1 | 1111 | 40 | 1 | 2 | 2 |
| 4 | 1 | 1111 | 20 | 1 | 2 | 2 |
| 5 | 1 | 1111 | 20 | 1 | 2 | 2 |
| 6 | 1 | 1111 | 25 | 2 | 3 | 3 |
| 7 | 1 | 2222 | 20 | 2 | 4 | 4 |
| 8 | 1 | 2222 | 20 | 2 | 4 | 4 |
| 9 | 1 | 2222 | 20 | 2 | 4 | 4 |
| 10 | 1 | 2222 | 20 | 2 | 4 | 4 |
| 11 | 2 | 3333 | 10 | 2 | 5 | 5 |
| 12 | 2 | 3333 | 90 | 3 | 6 | 6 |
| 13 | 2 | 3333 | 90 | 4 | 7 | 7 |
| 14 | 2 | 3333 | 90 | 5 | 8 | 8 |
| 15 | 2 | 3333 | 90 | 6 | 9 | 9 |
| 16 | 2 | 3333 | 10 | 6 | 9 | 10 |
| 17 | 2 | 3333 | 10 | 6 | 9 | 10 |
| 18 | 2 | 3333 | 10 | 6 | 9 | 10 |
| 19 | 2 | 3333 | 10 | 6 | 9 | 10 |
| 20 | 2 | 3333 | 10 | 7 | 10 | 10 |
| 21 | 2 | 3333 | 10 | 7 | 10 | 10 |
| 22 | 2 | 3333 | 10 | 7 | 10 | 10 |
| 23 | 2 | 3333 | 10 | 7 | 10 | 10 |
| 24 | 2 | 3333 | 10 | 7 | 10 | 10 |
| 25 | 2 | 3333 | 10 | 7 | 10 | 11 |
| 26 | 2 | 3333 | 10 | 7 | 10 | 11 |
| 27 | 2 | 3333 | 10 | 7 | 10 | 11 |
| 28 | 2 | 3333 | 10 | 7 | 10 | 11 |
| 29 | 2 | 3333 | 10 | 7 | 10 | 11 |
| 30 | 2 | 3333 | 10 | 8 | 11 | 11 |
| 31 | 2 | 3333 | 10 | 8 | 11 | 11 |
| 32 | 2 | 3333 | 10 | 8 | 11 | 11 |
| 33 | 2 | 3333 | 10 | 8 | 11 | 11 |
| 34 | 2 | 3333 | 10 | 8 | 11 | 12 |
| 35 | 2 | 3333 | 10 | 8 | 11 | 12 |
每个组的Total
列的总和不能达到 100,而组“9”和“10”达到了这个数量(它们的总和分别为 130 和 100)。
我还尝试使用来自 this answer 的递归 CTE,但在这种情况下,我无法按 Code
列分组。
我可以以编程方式执行此操作,但我觉得这可以通过单个查询更轻松地实现。
我正在使用 MSSQL 2016。
【问题讨论】:
也许是递归 cte?我不知道。我倾向于认为光标(yuk)。 您说“阈值 100”和“不能超过 100”。您指出第 10 组是不可接受的,因为它等于 100 - 但等于 100 不超过 100。由于您在预期结果中指出 100 的总数是不可接受的,我认为这意味着“运行总数必须小于 100",这意味着正好 100 是不可接受的。 您也没有说过如果个人总数为 100 或更多该怎么办。 【参考方案1】:您可以使用递归 CTE 来做到这一点:
with tt as (
select t.*, row_number() over (order by id) as seqnum
from t
),
cte as (
select id, groupid, code, total, total as totaltotal, 1 as grp, tt.seqnum
from tt
where seqnum = 1
union all
select tt.id, tt.groupid, tt.code, tt.total,
(case when cte.totaltotal + tt.total > 100 or cte.groupid <> tt.groupid or cte.code <> tt.code
then tt.total else totaltotal + tt.total
end),
(case when cte.totaltotal + tt.total > 100 or cte.groupid <> tt.groupid or cte.code <> tt.code
then cte.grp + 1 else cte.grp
end),
tt.seqnum
from cte join
tt
on tt.seqnum = cte.seqnum + 1
)
select *
from cte
order by id;
Here 是一个 dbfiddle。
不幸的是,没有“基于集合”的方法。
如果您接受重新启动每个 groupid
/code
组合的号码,则可以加快速度。
【讨论】:
【参考方案2】:这是一个递归 CTE。它要求 ID 是增量的,没有间隙,并且按照您想要的顺序。这在您的示例数据中是正确的。但是,如果不能保证在您的实际数据中是这样的,那么您必须使用带有 row_number 的子查询来按您想要的顺序获取序列号。
declare @table table (
Id int not null,
GroupId int not null,
Code nvarchar(14) not null,
Total int not null
)
insert into @table values
( 1, 1, '1111', 20),( 2, 1, '1111', 75),( 3, 1, '1111', 40),( 4, 1, '1111', 20),
( 5, 1, '1111', 20),( 6, 1, '1111', 25),( 7, 1, '2222', 20),( 8, 1, '2222', 20),
( 9, 1, '2222', 20),(10, 1, '2222', 20),(11, 2, '3333', 10),(12, 2, '3333', 90),
(13, 2, '3333', 90),(14, 2, '3333', 90),(15, 2, '3333', 90),(16, 2, '3333', 10),
(17, 2, '3333', 10),(18, 2, '3333', 10),(19, 2, '3333', 10),(20, 2, '3333', 10),
(21, 2, '3333', 10),(22, 2, '3333', 10),(23, 2, '3333', 10),(24, 2, '3333', 10),
(25, 2, '3333', 10),(26, 2, '3333', 10),(27, 2, '3333', 10),(28, 2, '3333', 10),
(29, 2, '3333', 10),(30, 2, '3333', 10),(31, 2, '3333', 10),(32, 2, '3333', 10),
(33, 2, '3333', 10),(34, 2, '3333', 10),(35, 2, '3333', 10)
;with rcte as (
select id, groupid, code, total, total as runtotal, 1 as groups
from @table
where id=1
union all
select t.id, t.groupid, t.code, t.total,
case when r.runtotal + t.total >= 100 or r.code <> t.code
then t.total
else r.runtotal + t.total
end as runtotal,
case when r.runtotal + t.total >= 100 or r.code <> t.code
then groups + 1
else groups
end as groups
from rcte r
inner join @table t on t.id = r.id + 1
)
select id, groupid, code, total, groups
from rcte
order by id
【讨论】:
【参考方案3】:这里有一个光标。
declare @table table (
Id int not null,
GroupId int not null,
Code nvarchar(14) not null,
Total int not null
)
insert into @table values
( 1, 1, '1111', 20),( 2, 1, '1111', 75),( 3, 1, '1111', 40),( 4, 1, '1111', 20),
( 5, 1, '1111', 20),( 6, 1, '1111', 25),( 7, 1, '2222', 20),( 8, 1, '2222', 20),
( 9, 1, '2222', 20),(10, 1, '2222', 20),(11, 2, '3333', 10),(12, 2, '3333', 90),
(13, 2, '3333', 90),(14, 2, '3333', 90),(15, 2, '3333', 90),(16, 2, '3333', 10),
(17, 2, '3333', 10),(18, 2, '3333', 10),(19, 2, '3333', 10),(20, 2, '3333', 10),
(21, 2, '3333', 10),(22, 2, '3333', 10),(23, 2, '3333', 10),(24, 2, '3333', 10),
(25, 2, '3333', 10),(26, 2, '3333', 10),(27, 2, '3333', 10),(28, 2, '3333', 10),
(29, 2, '3333', 10),(30, 2, '3333', 10),(31, 2, '3333', 10),(32, 2, '3333', 10),
(33, 2, '3333', 10),(34, 2, '3333', 10),(35, 2, '3333', 10)
select *
from @table
order by code,id
declare @runtotal int = 0
declare @groups int = 0
declare @code nvarchar(14)
declare @currentcode nvarchar(14) = ''
declare @total int
declare @id int
declare @output table (
Id int not null,
Groups int not null
)
declare cursor_table cursor
for select id, code, total
from @table
order by code,id
open cursor_table
fetch next from cursor_table into @id, @code, @total
while @@fetch_status = 0
begin
set @runtotal += @total
if @runtotal >= 100 or @code <> @currentcode
begin
set @runtotal = @total
set @groups += 1
set @currentcode = @code
end
insert into @output
select @id,@groups
fetch next from cursor_table into @id, @code, @total
end
select t.*,groups
from @table t
inner join @output o on o.id=t.id
close cursor_table
deallocate cursor_table
【讨论】:
以上是关于根据组和最大总和分配组的主要内容,如果未能解决你的问题,请参考以下文章