As I said in my comment, you can get your goal with a different approach, without loops , but you need a disjoint AllowanceChargeGroup , which means that one permission can be in one group.
Due to this unique relationship between premiums and charge groups, we can associate requests (calls) with premiums.
The idea is to list and weigh (order) each individual unit needed from calls, and list and weigh (order) each individual unit available from premiums, and finally bind them next to each other.
For example, suppose you have these Calls, Allowances and ChargeGroups:
ID ChargeGroupID Quantity QuantityFromAllowances FirstAllowanceUsedID 1 1 3 0 NULL 2 1 3 0 NULL 3 2 5 0 NULL ID Priority Quantity QuantityUsed 1 1 4 0 2 0 1 0 3 0 6 0 ID AllowanceID ChargeGroupID 1 1 1 2 2 1 4 3 2
Now blow each line in n lines depending on the number of lines (so we will have 3 lines for CallID 1 and CallID 2 and 5 lines for CallID 3). During the explosion, name the rows to identify them (add two different columns with the row number for the group and for the call / manual)
ChargeGroupID GroupRowN CallID CallRowN 1 1 1 1 1 2 1 2 1 3 1 3 1 4 2 1 1 5 2 2 1 6 2 3 2 1 3 1 2 2 3 2 2 3 3 3 2 4 3 4 2 5 3 5 ChargeGroupID GroupRowN AllowanceID AllowanceRowN 1 1 1 1 1 2 1 2 1 3 1 3 1 4 1 4 1 5 2 1 2 1 3 1 2 2 3 2 2 3 3 3 2 4 3 4 2 5 3 5 2 6 3 6
Now just attach these blasted sets to the group number (GroupRowN).
Here you can see the distribution of premiums on calls.
- CallID = 1 (first 3 lines) is completely covered by the first three lines AllowanceID = 1
- CallID = 2 (second 3 rows) partially covers the last row AllowanceID = 1 and the first (and unique) row AllowanceID = 2
- CallID = 3 (last 5 lines) is completely covered by the first 5 lines of AllowIDID = 3, which is not exhausted completely, since the last line is unsurpassed (not requested by any call)
(I added horizontal strokes to better show the distribution by CallID):
ChargeGroupID GroupRowN CallID CallRowN ChargeGroupID GroupRowN AllowanceID AllowanceRowN 1 1 1 1 1 1 1 1 1 2 1 2 1 2 1 2 1 3 1 3 1 3 1 3 ----------------------------------------------------------------------------------------------------- 1 4 2 1 1 4 1 4 1 5 2 2 1 5 2 1 1 6 2 3 NULL NULL NULL NULL ----------------------------------------------------------------------------------------------------- 2 1 3 1 2 1 3 1 2 2 3 2 2 2 3 2 2 3 3 3 2 3 3 3 2 4 3 4 2 4 3 4 2 5 3 5 2 5 3 5 NULL NULL NULL NULL 2 6 3 6
Now let's aggregate this result to get some results:
CallID Max(CallN) AllowanceID Max(AllowanceN) 1 3 1 3 2 1 1 4 2 2 2 1 3 5 3 5
Finally, from the last output, we can obtain information for updating calls in the table of assumptions:
CallID QtUsed FirstUsed 1 3 1 2 2 1 3 5 3 AllowanceID QtUsed 1 4 2 1 3 5
Ok
it was a theory, now you can see some code (using the above data).
Pay attention to FN_NUMBERS (n), this is a function that returns only one column with numbers from 1 to n, you need it in your database, there are many ways to do this, just google for the โtable tablesโ or see here .
I am using the following:
CREATE FUNCTION FN_NUMBERS( @MAX INT ) RETURNS @N TABLE (N INT NOT NULL PRIMARY KEY) BEGIN WITH Pass0 as (select '1' as C union all select '1'), --2 rows Pass1 as (select '1' as C from Pass0 as A, Pass0 as B),--4 rows Pass2 as (select '1' as C from Pass1 as A, Pass1 as B),--16 rows Pass3 as (select '1' as C from Pass2 as A, Pass2 as B),--256 rows Pass4 as (select TOP (@MAX) '1' as C from Pass3 as A, Pass3 as B) --65536 rows ,Tally as (select TOP (@MAX) '1' as C from Pass4 as A, Pass2 as B, Pass1 as C) --4194304 rows --,Tally as (select TOP (@MAX) '1' as C from Pass4 as A, Pass3 as B) --16777216 rows --,Tally as (select TOP (@MAX) '1' as C from Pass4 as A, Pass4 as B) --4294836225 rows INSERT INTO @N SELECT TOP (@MAX) ROW_NUMBER() OVER(ORDER BY C) AS N FROM Tally RETURN END
Return to sql ..
declare @res as table (id int identity primary key, CallID int, CallN int , AllowanceID int, AllowanceN int, unique (callId, id), unique (allowanceID, id)) ;with cx as ( select c.ID, c.Quantity, c.ChargeGroupID, n, ROW_NUMBER() over (partition by ChargeGroupID order by id,n) rn from [call] c join FN_NUMBERS(1000) n on nN<=(c.Quantity-c.QuantityFromAllowances) ), ax as ( select a.ID, a.Quantity, ChargeGroupID, N, ROW_NUMBER() over (partition by g.ChargeGroupID order by [priority] desc, a.id,n) rn from Allowance a join AllowanceChargeGroup g on g.AllowanceID = a.ID join FN_NUMBERS(1000) n on nN <= (a.Quantity-a.QuantityUsed) ), j as ( select cx.ID CallID, cx.Quantity CallQt, cx.N CallN, cx.rn CallRn, ax.ID AllowanceID, ax.Quantity AllowanceQt, ax.N AllowanceN, ax.rn AllowanceRn from cx join ax on cx.rn = ax.rn and (cx.ChargeGroupID = ax.ChargeGroupID) ) insert into @res select CallID, MAX(CallN) CallN, AllowanceID, MAX(AllowanceN) AllowanceN from j group by CallID,AllowanceID
This will populate the @res table with the final summary data that will be used for updates.
Therefore, we only need to perform the actual updates:
-- updates Allowance table ;with ar as ( select AllowanceID, MAX(AllowanceN) QtUsed from @res group by AllowanceID ) update a set a.QuantityUsed = a.QuantityUsed + ar.QtUsed select ar.* from Allowance a join ar on a.ID = ar.AllowanceID -- updates Call table ;with fu as ( select CallID id, min(calln) FirstUsed from @res group by CallID ), cr as ( select CallID, MAX(CallN) QtUsed, MIN(AllowanceID) FirstUsed from @res r1 left join fu r2 on r1.CallID=r2.id and r1.CallN = r2.FirstUsed group by CallID ) update c set QuantityFromAllowances = c.QuantityFromAllowances + QtUsed, FirstAllowanceUsedID = ISNULL(FirstAllowanceUsedID, FirstUsed) select cr.* from [call] c join cr on c.ID = cr.CallID
That's all, one insert into the temporary table and two updates, no loops, no cursors.