Назначить группы на основе группы и максимальной суммы - PullRequest
0 голосов
/ 31 октября 2019

Мне нужно сгруппировать строки по некоторым столбцам и по промежуточной сумме, пока она не достигнет порогового значения. Ближе всего я получил запрос, основанный на этом ответе , но это решение не так точно, как мне нужно, потому что сумма должна быть сброшена и перезапущена, когда она достигнет порога.

Вот моя попытка с некоторыми примерами данных и порогом 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 соответственно).

Я также пытался использовать рекурсивный CTE из этого ответа , но в этом случае я не смог сгруппировать по столбцу Code.

Я мог бы сделать это программно, но у меня есть ощущение, что это можно сделать проще с помощью одного запроса.

Я использую MSSQL 2016.

Ответы [ 3 ]

1 голос
/ 31 октября 2019

Вот рекурсивный CTE. Требуется, чтобы идентификатор был инкрементным, без пробелов и в нужном вам порядке. Это верно в ваших данных образца. Тем не менее, если это не гарантируется в ваших реальных данных, вам придется использовать подзапрос с 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
1 голос
/ 31 октября 2019

Вы можете сделать это с помощью рекурсивного 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;

Здесь - это db <> скрипка.

К сожалению, нет "set-основанный на этом подход.

Вы могли бы ускорить его, если бы приняли перезапуск чисел для каждой комбинации groupid / code.

0 голосов
/ 31 октября 2019

Вот он с курсором.

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...