基于组和最大和分配组

问题描述 投票:0回答:3

我需要按行和连续的总和对行进行分组,直到达到阈值为止。我得到的最接近的是基于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。

sql sql-server
3个回答
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是db <>小提琴。

不幸的是,对此没有“基于集合的”方法。

如果您接受重新启动每个groupid / code组合的数字,则可以加快速度。


1
投票

这里是递归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

0
投票

这里是光标。

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
© www.soinside.com 2019 - 2024. All rights reserved.