I need to group a set of rows based on the Category
column, and also limit the combined rows based on the SUM(Number)
column to be less than or equal to the @Limit
value.
For each distinct Category
column I need to identify "buckets" that are <=@limit
. If the SUM(Number
) of all the rows for a Category
column are <=@Limit
then there will be only 1 bucket for that Category
value (like 'CCCC' in the sample data). However if the SUM(Number
)>@limit
, then there will be multiple bucket rows for that Category
value (like 'AAAA' in the sample data), and each bucket must be <=@Limit
. There can be as many buckets as necessary. Also, look at Category
value 'DDDD', its one row is greater than @Limit
all by itself, and gets split into two rows in the result set.
Given this simplified data:
DECLARE @Detail table (DetailID int primary key, Category char(4), Number int)
SET NOCOUNT ON
INSERT @Detail VALUES ( 1, 'AAAA',100)
INSERT @Detail VALUES ( 2, 'AAAA', 50)
INSERT @Detail VALUES ( 3, 'AAAA',300)
INSERT @Detail VALUES ( 4, 'AAAA',200)
INSERT @Detail VALUES ( 5, 'BBBB',500)
INSERT @Detail VALUES ( 6, 'CCCC',200)
INSERT @Detail VALUES ( 7, 'CCCC',100)
INSERT @Detail VALUES ( 8, 'CCCC', 50)
INSERT @Detail VALUES ( 9, 'DDDD',800)
INSERT @Detail VALUES (10, 'EEEE',100)
INSERT @Detail VALUES (11, 'AAAA',200) --EDIT added
INSERT @Detail VALUES (12, 'AAAA',200) --EDIT added
INSERT @Detail VALUES (13, 'AAAA',200) --EDIT added
INSERT @Detail VALUES (14, 'AAAA',200) --EDIT added
SET NOCOUNT OFF
DECLARE @Limit int
SET @Limit=500
I need one of these result set:
DetailID Bucket | DetailID Category Bucket
-------- ------ | -------- -------- ------
1 1 | 1 'AAAA' 1
2 1 | 2 'AAAA' 1
3 1 | 3 'AAAA' 1
4 2 | 4 'AAAA' 2
11 2 | 11 'AAAA' 2 --EDIT added
12 3 | 12 'AAAA' 3 --EDIT added
13 3 | 13 'AAAA' 3 --EDIT added
14 4 | 14 'AAAA' 4 --EDIT added
5 5 OR 5 'BBBB' 1
6 6 | 6 'CCCC' 1
7 6 | 7 'CCCC' 1
8 6 | 8 'CCCC' 1
9 7 | 9 'DDDD' 1
9 8 | 9 'DDDD' 2
10 9 | 10 'EEEE' 1
EDIT after trying out all the answers
With all attempts at a set based solution not working as needed, I'm going with a modification to @GalacticJello Answer, modification noted in the code below. I basically find all the rows where the entire category fits into the bucket and INSERT them using a single INSERT-SELECT, and then loop over the remaining data using @GalacticJello cursor free loop. This will work fine in my situation since there will hardly ever be any rows processed by the loop.
DECLARE @DetailTemp table (PID INT IDENTITY(1,1), DetailID int primary key, Category char(4), Number int)
DECLARE @DetailFinal table (DetailID int, Category char(4), Bucket int) ---<<<renamed column to Bucket
DECLARE @DetailCount int
SET @DetailCount = 0;
--------<<<optimization added starts here
;WITH AllSingleBuckets AS (
SELECT
Category
FROM @Detail
GROUP BY Category
HAVING SUM(Number)<=@Limit
)
INSERT INTO @DetailFinal
(DetailID, Category, Bucket)
SELECT
d.DetailID,d.Category,1
FROM @Detail d
INNER JOIN AllSingleBuckets s ON d.Category=s.Category
--------<<<optimization added ends here
INSERT @DetailTemp
--------<<<changed for optimization, added WHERE clause
SELECT d.DetailId, d.Category, d.Number FROM @Detail d WHERE NOT EXISTS (SELECT 1 FROM @DetailFinal f WHERE d.Category=f.Category) ORDER BY Category, DetailId
SELECT @DetailCount = @@ROWCOUNT
DECLARE @CurrentPid int
SET @CurrentPid = 1
DECLARE @ThisId int
DECLARE @ThisCategory char(4)
DECLARE @ThisNumber int
DECLARE @CurrentCategory char(4)
DECLARE @CurrentSum INT
DECLARE @CurrentBucket INT
WHILE @CurrentPid <= @DetailCount
BEGIN
SELECT @ThisId = DetailId, @ThisCategory = Category, @ThisNumber = Number
FROM @DetailTemp
WHERE PID = @CurrentPid
IF @ThisCategory = @CurrentCategory
BEGIN
IF @CurrentSum + @ThisNumber > @Limit
BEGIN
SET @CurrentBucket = @CurrentBucket + 1
SET @CurrentSum = @ThisNumber
END
ELSE
BEGIN
SET @CurrentSum = @CurrentSum + @ThisNumber
END
END
ELSE
BEGIN
SET @CurrentBucket = 1
SET @CurrentCategory = @ThisCategory
SET @CurrentSum = @ThisNumber
END
WHILE @CurrentSum > @Limit
BEGIN
INSERT @DetailFinal SELECT @ThisId, @CurrentCategory, @CurrentBucket
SET @CurrentBucket = @CurrentBucket + 1
SET @CurrentSum = @CurrentSum - @Limit
END
INSERT @DetailFinal SELECT @ThisId, @CurrentCategory, @CurrentBucket
SET @CurrentPid = @CurrentPid + 1
END
SELECT * from @DetailFinal ORDER BY Category --------<<<added order by
OUTPUT:
DetailID Category Bucket
----------- -------- -----------
1 AAAA 1
2 AAAA 1
3 AAAA 1
4 AAAA 2
11 AAAA 2
12 AAAA 3
13 AAAA 3
14 AAAA 4
5 BBBB 1
6 CCCC 1
7 CCCC 1
8 CCCC 1
9 DDDD 1
9 DDDD 2
10 EEEE 1
(15 row(s) affected)