Как разбить записи на группы по сумме?
Есть таблица, отсортированная по полю kolich
в обратном порядке:
id | kolich |
---|---|
7863 | 5392 |
3230 | 2321 |
3821 | 1914 |
10595 | 1524 |
11397 | 1395 |
10602 | 952 |
10612 | 829 |
10611 | 828 |
10081 | 670 |
Как получить такую таблицу:
id | kolich | grupa |
---|---|---|
7863 | 5392 | 1 |
3230 | 2321 | 2 |
3821 | 1914 | 3 |
10595 | 1524 | 4 |
11397 | 1395 | 5 |
10602 | 952 | 6 |
10612 | 829 | 6 |
10611 | 828 | 7 |
10081 | 670 | 7 |
чтобы уникальная grupa
присваивалась идущим подряд записям,сумма(kolich)
которых меньше 2000?
То есть подряд идущие записи должны в сумме давать <2000, а если следующая запись превышает этот лимит, то ей присваивается новая группа на примере выше это группы (grupa
) 6 и 7
Полагаю это можно реализовать оконной функцией с промежуточными итогами. Пока хожу возле:
SUM(count(*)) OVER (
PARTITION BY case when count(*) < 2000 then id else count(*) end
ORDER BY kolich rows between unbounded preceding and current row
)
AS rns
Ответы (2 шт):
Предлагаю решение на c#:
using System;
using System.Linq;
using System.Collections.Generic;
class IdKolich {
public int id, kolich, grupa;
public IdKolich(int id, int kolich)
{ this.id = id; this.kolich = kolich; grupa = 0; }
public override string ToString()
{ return string.Format("{0,10}{1,10}{2,10}", id, kolich, grupa); }
}
static class Program {
static void Main(string[] args) {
// тестовые данные
var iks = new IdKolich[] { new IdKolich(7863, 5392),
new IdKolich(3230, 2321),
new IdKolich(3821, 1914),
new IdKolich(10595, 1524),
new IdKolich(11397, 1395),
new IdKolich(10602, 952),
new IdKolich(10612, 829),
new IdKolich(10611, 828),
new IdKolich(10081, 670) };
// решение
int i = iks[0].kolich >= 2000 ? 0 : 1;
iks.Aggregate(0, (stot, cur) => {
if ((stot += cur.kolich) >= 2000) { i++; stot = cur.kolich; }
cur.grupa = i; return stot;
} );
// вавод результата
foreach (var ik in iks) Console.WriteLine(ik);
}
}
-->
7863 5392 1
3230 2321 2
3821 1914 3
10595 1524 4
11397 1395 5
10602 952 6
10612 829 6
10611 828 7
10081 670 7
Такая задача довольно часто встречается - набрать группы строк не больше определенного размера. Например, для заполнения грузов в контейнер или грузовик.
Как уже комментировал @Akina, нужен рекурсивный запрос.
Перебираем строки по одной в цикле (рекурсии).
Запрос достаточно простой, можете адаптировать под Oracle.
Посмотрите вариант решения (для Postgresql)
with recursive
t as(select *,row_number()over(order by kolich desc) rn from test)
,r as(
select *
,kolich ts
,1 grn
from t
where rn=1
union all
select t.*
,case when (r.ts+t.kolich)>2000 then t.kolich
else r.ts+t.kolich
end ts
,case when (r.ts+t.kolich)>2000 then r.grn+1
else r.grn
end grn
from r inner join t on (t.rn=r.rn+1)
)
select * from r order by rn;
Результат
id | kolich | rn | ts | grn |
---|---|---|---|---|
7863 | 5392 | 1 | 5392 | 1 |
3230 | 2321 | 2 | 2321 | 2 |
3821 | 1914 | 3 | 1914 | 3 |
10595 | 1524 | 4 | 1524 | 4 |
11397 | 1395 | 5 | 1395 | 5 |
10602 | 952 | 6 | 952 | 6 |
10612 | 829 | 7 | 1781 | 6 |
10611 | 828 | 8 | 828 | 7 |
10081 | 670 | 9 | 1498 | 7 |