-- SQL 2017 構文に収まるように、@kirk-roybal が提供した上記の回答を編集しました。
DROP TABLE IF EXISTS dbo.Numbers;
DECLARE @UpperBound INT = 1000000;
;WITH cteN(Number) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY s1.[object_id]) - 1
FROM sys.all_columns AS s1
CROSS JOIN sys.all_columns AS s2
)
SELECT [Number] INTO dbo.Numbers
FROM cteN WHERE [Number] <= @UpperBound;
CREATE CLUSTERED INDEX IX_dboNumber ON dbo.Numbers([Number])
DROP TABLE IF EXISTS #buckets;
CREATE TABLE #buckets (
bucket_id int identity(1,1) primary key,
capacity integer);
-- something to put in the buckets
DROP TABLE IF EXISTS #transactions;
CREATE TABLE #transactions (
transaction_id int identity(1,1) primary key,
quantity integer);
-- create 2 buckets with different capacities
INSERT INTO #buckets (capacity)
VALUES (100),(50);
-- create some traffic to put in the buckets
INSERT INTO #transactions (quantity)
VALUES (50),(60),(20),(40);
select * from #buckets
select * from #transactions;
WITH buckets AS (
-- expand buckets (create a row per bucket capacity)
SELECT bucket_row_id = row_number() OVER (Order By bucket_id) , *
FROM (
-- slot = a unit of capacity
SELECT b.*, N.Number slot
FROM #buckets b
CROSS JOIN dbo.Numbers N
WHERE N.Number >0 AND N.Number <= b.capacity
) useless_alias
), xact AS (
-- expand transactions, creating an id per unit of quantity
SELECT unit_row_id = row_number() OVER (Order by transaction_id) , *
FROM (
-- an item per transaction quantity
SELECT t.*, N.Number unit
FROM #transactions t
CROSS JOIN dbo.Numbers N
WHERE N.Number > 0 AND N.Number <= t.quantity
) useless_alias
), filled AS (
-- join buckets to transactions on slots=units
-- slots with no units = wasted bucket capacity
-- units with no slots = overage
SELECT b.*, x.*
FROM xact x
FULL JOIN buckets b ON b.bucket_row_id = x.unit_row_id
)
-- finally, do the do
SELECT transaction_id
, bucket_id = CASE WHEN bucket_id IS NULL THEN 'overage' ELSE CAST(bucket_id as varchar(200)) END
, count(unit_row_id) quantity_bucketed
FROM filled
GROUP BY transaction_id, CASE WHEN bucket_id IS NULL THEN 'overage' ELSE CAST(bucket_id as varchar(200)) END
ORDER BY 1,2