Performance issue with update query after adding index - mysql

I have added the index to my update query and by adding the same query start taking several hours to complete the process.While without index its completing in some minutes i have added the index to faster the process but it became very slow exactly opposite to my desire.
Below is sample code snippet of my code.
Cursor c_updt_stg_rsn is
select distinct substr(r.state_claim_id, 1, 12), ROWID
from nemis.stg_state_resp_rsn r
WHERE r.seq_resp_plan_id = v_seq_resp_plan_id
and r.submitted_claim_id is null
and r.filler_2 is null;
BEGIN
OPEN c_updt_stg_rsn;
LOOP
FETCH c_updt_stg_rsn BULK COLLECT
INTO v_state_claim_id, v_rowid LIMIT c_BULK_SIZE;
FORALL i IN 1 .. v_state_claim_id.COUNT()
UPDATE /*+ index(STG_STATE_RESP_RSN,IDX2_STG_STATE_RESP_RSN) */ nemis.stg_state_resp_rsn
SET (submitted_claim_id , filler_2) = (SELECT DISTINCT submitted_claim_id, sl_group_id FROM nemis.state_sub_Resp_dtl D WHERE
(d.state, d.type_of_claim) in (select distinct state, type_of_claim
from nemis.resp_match_state
where seq_resp_match_table_level_id in
(select seq_resp_match_table_level_id
from nemis.resp_match_table_level
where seq_resp_plan_id = v_seq_resp_plan_id))
AND resp_state_claim_id LIKE v_state_claim_id(i)||'%'
)
WHERE ROWID = v_rowid(i);
IF v_state_claim_id.COUNT() != 0 THEN
v_cnt_rsn := v_cnt_rsn + SQL%ROWCOUNT;
END IF;
COMMIT;
EXIT WHEN c_updt_stg_rsn%NOTFOUND;
END LOOP;
CLOSE c_updt_stg_rsn;

Related

MySQL function execution time increases everytime I SELECT it

Bit of a strange one. I have a function that runs perfectly fine at first, say 15ms execution and 10ms fetching when selecting the returned value plus some other columns. But if keep refreshing the same query, over and over, the execution of the query goes up. So first it's 15ms, then 17, then... I got it all the way to 900ms. It's mostly the fetching that goes up in time, but the execution too. So at the end it'll be 600ms for fetching and 300ms for execution. Any ideas what's going on?
Function. I experimented with just a simple IF/ELSEIF but it gives the same exact result in terms of performance.
create function get_table(var_account_id int unsigned) returns varchar(20)
reads sql data
BEGIN
RETURN IF(
(SELECT EXISTS(SELECT TRUE
FROM TableA
WHERE account_id = var_account_id
AND expiring_at > CURRENT_TIMESTAMP)), 'TableA',
IF((SELECT EXISTS(SELECT TRUE
FROM TableB
WHERE account_id = var_account_id
AND expiring_at > CURRENT_TIMESTAMP)), 'TableB',
IF((SELECT EXISTS(SELECT TRUE
FROM TableC
WHERE account_id = var_account_id
AND expiring_at > CURRENT_TIMESTAMP)), 'TableC',
IF((SELECT EXISTS(SELECT TRUE
FROM TableD
WHERE account_id = var_account_id
AND expiring_at > CURRENT_TIMESTAMP)), 'TableD',
NULL)
)));
END;
Explain of function after running it once with var_account_id = 1
9,SUBQUERY,TableD,,ref,"TableD_expiring_at_index,TableD_account_id_index",TableD_account_id_index,4,const,1,100,Using where
7,SUBQUERY,TableC,,ref,"TableC_account_id_index,TableC_expiring_at_index",TableC_account_id_index,4,const,1,5,Using where
5,SUBQUERY,TableB,,ref,"TableB_expiring_at_index,TableB_account_id_index",TableB_account_id_index,4,const,1,9.26,Using where
3,SUBQUERY,TableA,,ref,"TableA_expiring_at_index,TableA_account_id_index",TableA_account_id_index,4,const,1,100,Using where
Putting a compound index on account_id and expiring_at has no effect at all
And I run a simple query like
SELECT TableXYZ.*, get_table(TableXYZ.account_id) AS some_value FROM TableXYZ LIMIT 500;
I've run it on more complicated queries but the result is always the same, fast at first, slow after rerunning the same SELECT let's say 5 times a second for 30 secs straight. Even after I let MySQL cool off for a bit, come back, and the first run is still 900ms. And I'm sure it can keep going up. The only way to fix this is restarting the mysql service in windows.
Explain of the SELECT:
1,SIMPLE,TableXYZ,,ALL,,,,,695598,100,
I'm running these on Windows 10 if it matters, localy.
Sounds crazy. Maybe you can avoid the subqueries, which often cause performance problems
SELECT X.tabName
FROM (
SELECT 'TableA' as tabName, 1 as tabNr, account_id, expiring_at FROM TableA WHERE account_id = var_account_id AND expiring_at > CURRENT_TIMESTAMP
UNION
SELECT 'TableB' as tabName, 2 as tabNr, account_id, expiring_at FROM TableB WHERE account_id = var_account_id AND expiring_at > CURRENT_TIMESTAMP
UNION
...
) X ORDER BY tabNr LIMIT 1
If you want to avoid a union because you have very big tables, then why not use control flow in the function?
DECLARE tabName VARCHAR(50);
SET tabName := SELECT 'TableA' FROM TableA WHERE account_id = var_account_id AND expiring_at > CURRENT_TIMESTAMP;
IF (tabName IS NULL) THEN
SET tabName := SELECT 'TableB' FROM TableB WHERE account_id = var_account_id AND expiring_at > CURRENT_TIMESTAMP;
END IF;
and so on...
RETURN tabName;
RETURN COALESCE(
IF (EXISTS(...), "A", NULL),
IF (EXISTS(...), "B", NULL),
IF (EXISTS(...), "C", NULL),
IF (EXISTS(...), "D", NULL),
IF (EXISTS(...), "E", NULL)
)
where ... is, for example:
SELECT 1
FROM TableA
WHERE account_id = var_account_id
AND expiring_at > CURRENT_TIMESTAMP
Notes:
Be sure to have this index in table*: INDEX(account_id, expired_at) (in that order).
EXISTS() stops where a matching row is found. (One proposed solution failed to stop, hence took longer.)
COALESCE() does not need to evaluate all of its arguments. (UNION does.)
(No, I don't see why it would get slower and slower. Hopefully, my formulation will be consistently faster.)

From error 1109 to error 1242 MySQL

I'm using a procedure to calculate the length of user 'hiatus' (aka contingencies) from the program in our system. It runs after a procedure that determines user status depending on whether they are completing their daily treatment and to what extent.
The purpose of this procedure is to log the length of a user's contingency, by adding a row to a table with the following schema:
id_contingency int(11) NOT NULL AUTO_INCREMENT,
id_user int(11) DEFAULT NULL,
date_start date DEFAULT NULL,
program_day int(11) DEFAULT NULL,
date_end date DEFAULT NULL,
total_days int(11) DEFAULT NULL,
latest_tf_id archer(255) DEFAULT NULL
I considered adding this as a trigger on the update of the user_status table, but I can't risk an error preventing that table from updating. So, this procedure first closes contingencies that were previously open, when the user first entered the hiatus, but has now resumed the program, and it later opens new contingencies for users who have now started a hiatus in their treatment for the first time. It then remains open until they resume the program, and calculates how long they were on hiatus for.
This was my original procedure, and it returned error 1109 (unknown table tbl_user_status) :
DELIMITER $$
CREATE DEFINER=CURRENT_USER PROCEDURE `proc_cont_calc`
NO SQL
BEGIN
#CLOSE OPEN CONTINGENCIES FIRST or d0 > d1
CASE
WHEN tbl_user_status.d4 = 1 AND tbl_user_status.d2 > 0 AND tbl_user_status.user_status = 'seguimiento' THEN
UPDATE tbl_user_contingency, tbl_user_status SET
tbl_user_contingency.date_end = CURRENT_DATE,
tbl_user_contingency.total_days = DATEDIFF(tbl_user_contingency.date_start, tbl_user_contingency.date_end),
tbl_user_contingency.updated_by = 'proc_cont.close'
WHERE tbl_user_contingency.date_end = '' AND tbl_user_contingency.id_smoker = tbl_user_status.id_smoker LIMIT 1;
#OPEN NEW CONTINGENCIES
WHEN tbl_user_status.d5 = 1 AND tbl_user_status.d4 = 0 AND tbl_user_status.user_status = 'contingencia' THEN
INSERT INTO tbl_user_contingency (id_smoker, roadmap_day, date_start, latest_tf_id, updated_by) SELECT
id_smoker, roadmap_day, CURRENT_DATE, latest_tf_id, 'proc_cont.open' FROM tbl_user_status;
END CASE;
END$$
DELIMITER;
So I tried this (amongst other things):
CASE
WHEN (SELECT d4 FROM tbl_user_status) = 1 AND (SELECT d2 FROM tbl_user_status) > 0 AND (SELECT user_status FROM tbl_user_status) = 'seguimiento' THEN
UPDATE tbl_user_contingency, tbl_user_status SET
tbl_user_contingency.date_end = CURRENT_DATE,
tbl_user_contingency.total_days = DATEDIFF(tbl_user_contingency.date_start, tbl_user_contingency.date_end),
tbl_user_contingency.updated_by = 'proc_cont.close'
WHERE tbl_user_contingency.id_smoker = tbl_user_status.id_smoker LIMIT 1;
#OPEN NEW CONTINGENCIES
WHEN (SELECT d5 FROM tbl_user_status) = 1 AND (SELECT d4 FROM tbl_user_status) = 0 AND (SELECT user_status FROM tbl_user_status) = 'contingencia' THEN
INSERT INTO tbl_user_contingency (id_smoker, roadmap_day, date_start, latest_tf_id, updated_by) SELECT
id_smoker, roadmap_day, CURRENT_DATE, latest_tf_id, 'proc_cont.open' FROM tbl_user_status;
END CASE;
And now I'm getting error 1242 returning multiple rows.
How can I get this procedure to run properly? Thanks!
UPDATE - I tried #P.Salmon's suggestion to simply update the rows, but not all the fields were filling out, or the update overruns previous contingencies.
Thanks!
The case statement seems unnecessary here just move the conditions to where clauses for example
UPDATE tbl_user_contingency join tbl_user_status on tbl_user_contingency.id_smoker = tbl_user_status.id_smoker
SET
tbl_user_contingency.date_end = CURRENT_DATE,
tbl_user_contingency.total_days = DATEDIFF(tbl_user_contingency.date_start, tbl_user_contingency.date_end),
tbl_user_contingency.updated_by = 'proc_cont.close'
WHERE tbl_user_contingency.date_end = '' AND
tbl_user_status.d4 = 1 AND tbl_user_status.d2 > 0 AND tbl_user_status.user_status = 'seguimiento'
;
INSERT INTO tbl_user_contingency (id_smoker, roadmap_day, date_start, latest_tf_id, updated_by)
SELECT
id_smoker, roadmap_day, CURRENT_DATE, latest_tf_id, 'proc_cont.open'
FROM tbl_user_status
where tbl_user_status.d5 = 1 AND tbl_user_status.d4 = 0 AND tbl_user_status.user_status = 'contingencia'
;
You could improve your question and get thereby a better response if you describe what it is you are trying to do instead of having us guess by reverse engineering two non working code segments, by adding your table definitions, sample data and expected output as text to your question. BTW I hope you have a mechanism that will stop this thing doing stuff more than once.

MySQL user-defined function returns incorrect value when used in a SELECT statement

I met a problem when calling a user-defined function in MySQL. The computation is very simple but can't grasp where it went wrong and why it went wrong. Here's the thing.
So I created this function:
DELIMITER //
CREATE FUNCTION fn_computeLoanAmortization (_empId INT, _typeId INT)
RETURNS DECIMAL(17, 2)
BEGIN
SET #loanDeduction = 0.00;
SELECT TotalAmount, PeriodicDeduction, TotalInstallments, DeductionFlag
INTO #totalAmount, #periodicDeduction, #totalInstallments, #deductionFlag
FROM loans_table
WHERE TypeId = _typeId AND EmpId = _empId;
IF (#deductionFlag = 1) THEN
SET #remaining = #totalAmount - #totalInstallments;
IF(#remaining < #periodicDeduction) THEN
SET #loanDeduction = #remaining;
ELSE
SET #loanDeduction = #periodicDeduction;
END IF;
END IF;
RETURN #loanDeduction;
END;//
DELIMITER ;
If I call it like this, it works fine:
SELECT fn_computeLoanAmortization(3, 4)
But if I call it inside a SELECT statement, the result becomes erroneous:
SELECT Id, fn_computeLoanAmortization(Id, 4) AS Amort FROM emp_table
There's only one entry in the loans_table and the above statement should only result with one row having value in the Amort column but there are lots of random rows with the same Amort value as the one with the matching entry, which should not be the case.
Have anyone met this kind of weird dilemma? Or I might have done something wrong from my end. Kindly enlighten me.
Thank you very much.
EDIT:
By erroneous, I meant it like this:
loans_table has one record
EmpId = 1
TypeId = 2
PeriodicDeduction = 100
TotalAmount = 1000
TotalInstallments = 200
DeductionFlag = 1
emp_table has several rows
EmpId = 1
Name = Paolo
EmpId = 2
Name = Nikko
...
EmpId = 5
Name = Ariel
when I query the following statements, I get the correct value:
SELECT fn_computeLoanAmortization(1, 2)
SELECT Id, fn_computeLoanAmortization(Id, 2) AS Amort FROM emp_table WHERE EmpId = 1
But when I query this statement, I get incorrect values:
SELECT Id, fn_computeLoanAmortization(Id, 2) AS Amort FROM emp_table
Resultset would be:
EmpId | Amort
--------------------
1 | 100
2 | 100 (this should be 0, but the query returns 100)
3 | 100 (same error here)
...
5 | 100 (same error here up to the last record)
Inside your function, the variables you use to retrieve the values from the loans_table table are not local variables local to the function but session variables. When the select inside the function does not find any row, those variables still have the same values as from the previous execution of the function.
Use real local variables instead. In order to do that, use the variables names without # as a prefix and declare the variables at the beginning of the function. See this answer for more details.
I suspect the problem is that the variables in the INTO are not re-set when there is no matching row.
Just set them before the INTO:
BEGIN
SET #loanDeduction = 0.00;
SET #totalAmount = 0;
SET #periodicDeduction = 0;
SET #totalInstallments = 0;
SET #deductionFlag = 0;
SELECT TotalAmount, PeriodicDeduction, TotalInstallments, DeductionFlag
. . .
You might just want to set them to NULL.
Or, switch your logic to use local variables:
SET v_loanDeduction = 0.00;
SET v_totalAmount = 0;
SET v_periodicDeduction = 0;
SET v_totalInstallments = 0;
SET v_deductionFlag = 0;
And so on.

Calling stored procedure in MySQL takes forever to execute

I have a stored procedure which I'm trying to call, and it takes forever to execute. I have no idea what's wrong. A similar stored procedure in another database executes perfectly. I'm not well-versed with MySQL Workbench, so I don't know if the database settings are different or something.
Following is my stored procedure:
CREATE
DEFINER = `admin`#`%`
PROCEDURE `calculate_daily_coil_moved_by_crane_data`()
BEGIN
set #curr_date = curdate();
set #pre_date = date_add(curdate(), interval -1 day);
set #a_shift_start_ts = concat(#pre_date, ' 06:00:00');
set #a_shift_end_ts = concat(#pre_date, ' 13:59:59');
set #b_shift_start_ts = concat(#pre_date, ' 14:00:00');
set #b_shift_end_ts = concat(#pre_date, ' 21:59:59');
set #c_shift_start_ts = concat(#pre_date, ' 22:00:00');
set #c_shift_end_ts = concat(#curr_date, ' 05:59:59');
SELECT #curr_date,
#pre_date,
#a_shift_start_ts,
#a_shift_end_ts,
#b_shift_start_ts,
#b_shift_end_ts,
#c_shift_start_ts,
#c_shift_end_ts;
#SET DATA
insert into daily_coil_move_by_crane_data_for_report (crane_id, crane_name, date, a_shift, b_shift, c_shift)
select cr.id, cr.name, #pre_date, 0, 0, 0
from yms_phase3.crane cr
where active = 1
order by cr.name;
#----------------------------------------------------------------------------------------------------
#--> COILS MOVED BY CRANE A Shift <--
#----------------------------------------------------------------------------------------------------
SET #shift = 'A';
#FETCH ROW DATA
update daily_coil_move_by_crane_data_for_report
set a_shift = ifnull((select COUNT(*)
FROM yms_phase3.workorder_history in_data
where in_data.crane_id = daily_coil_move_by_crane_data_for_report.crane_id
and current_execution_status IN (6 , 7)
and in_data.pick_ts between #a_shift_start_ts and #a_shift_end_ts
group by in_data.crane_name), 0)
where (a_shift is null or a_shift = 0);
#----------------------------------------------------------------------------------------------------
#--> COILS MOVED BY CRANE B Shift <--
#----------------------------------------------------------------------------------------------------
SET #shift = 'B';
#FETCH ROW DATA
update daily_coil_move_by_crane_data_for_report
set b_shift = ifnull((select COUNT(*)
FROM yms_phase3.workorder_history in_data
where in_data.crane_id = daily_coil_move_by_crane_data_for_report.crane_id
and current_execution_status IN (6 , 7)
and in_data.pick_ts between #b_shift_start_ts and #b_shift_end_ts
group by in_data.crane_name), 0)
where (b_shift is null or b_shift = 0);
#----------------------------------------------------------------------------------------------------
#--> COILS MOVED BY CRANE C Shift <--
#----------------------------------------------------------------------------------------------------
SET #shift = 'C';
#FETCH ROW DATA
update daily_coil_move_by_crane_data_for_report
set c_shift = ifnull((select COUNT(*)
FROM yms_phase3.workorder_history in_data
where in_data.crane_id = daily_coil_move_by_crane_data_for_report.crane_id
and current_execution_status IN (6 , 7)
and in_data.pick_ts between #c_shift_start_ts and #c_shift_end_ts
group by in_data.crane_name), 0)
where (c_shift is null or c_shift = 0);
#----------------------------------------------------------------------------------------------------
#INSERT ALL CRANE ENTRY
insert into daily_coil_move_by_crane_data_for_report (crane_id, crane_name, date, a_shift, b_shift, c_shift)
select -1, 'ALL', #pre_date, SUM(a_shift), sum(b_shift), sum(c_shift)
from daily_coil_move_by_crane_data_for_report
where date = #pre_date
group by date;
#UPDATE TOTAL
update daily_coil_move_by_crane_data_for_report
set total_coils_moved = (a_shift + b_shift + c_shift)
where date = #pre_date;
END
Also tried to execute the query from Java using the following:
jdbcTemplate.execute("CALL calculate_daily_coil_moved_by_crane_data;");
But it gives me the following Exception:
java.sql.SQLException: Lock wait timeout exceeded
Any workaround I can do to solve this?
Please try and edit the configuration file, also search for the same here on stack. There are certain possibilities while checking this out,
Check and edit the config file on Hard drive for MySQL increase the cache capacity and default values as the default values are in KB's the memory allocated is very less and to execute such a big procedure it should at least be some MB.
Increase the connection String timeout, that is by setting up right time in seconds. by default it is 60 seconds, which is very less for executing such a procedure, I think in c# at least we set it to '0' seconds which means that it shall not get timed-out till the query is executed.
If Any left Joins/ inner query please try and check whether the same output is produced in inner joins ? as inner joins are faster than left or right joins.
Add Indexes, have foreign key references properly mapped for faster execution of query.
Hope it works.

Need to use a nested cursor and getting "invalid object name"

First, let me state right off that I'm well aware that cursors are generally evil and shouldn't be used - I'm all about using sets, but just couldn't come up with a set-based solution to this particular problem. If you tell me to go do some set-based operations, well, I'm all for it, if you can tell me how you'd code this particular problem.
Basically, I've got a number of stock items for which I need to make purchases. I want to make purchases based upon the cheapest available price, where I know the suppliers' prices and their stock levels. There's also a pack-size issue in here, wherein I want to buy by pack-size if possible.
I've already pulled a list of the things I need to purchase into #needorders, and suppliers' stock levels and prices into #orderedprices. Below I'm iterating through cursor CUR_NEEDED and creating a secondary cursor CUR_AVAILABLE:
DECLARE CUR_NEEDED CURSOR LOCAL SCROLL_LOCKS
FOR
SELECT GoodID
, ConditionID
, QuantityToShip
, OrderStatusID
, RetailerID
, PackSize
FROM #needorders
ORDER BY GoodID
, ConditionID
, PurchaseDate DESC
FOR UPDATE
OPEN CUR_NEEDED
FETCH NEXT FROM CUR_NEEDED INTO #GoodID, #ConditionID, #QuantityToShip, #OrderStatusID, #RetailerID, #PackSize
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE CUR_AVAILABLE CURSOR LOCAL SCROLL_LOCKS
FOR
SELECT SupplierStocklistItemID
, SupplierID
, StockLevel
, SupplierCurrencyID
, CostPrice
FROM #orderedprices
WHERE #orderedprices.GoodID = #GoodID
AND #orderedprices.ConditionID = #ConditionID
AND #orderedprices.StockLevel > 0
ORDER BY #orderedprices.PriceRank
FOR UPDATE
OPEN CUR_AVAILABLE
FETCH NEXT FROM CUR_AVAILABLE INTO #SupplierStocklistItemID, #SupplierID, #StockLevel, #SupplierCurrencyID, #CostPrice
WHILE ##FETCH_STATUS = 0
BEGIN
/*
Buy as many #PackSize as we need to cover how many we require, unless the supplier
only has a certain number, in which case buy that number.
E.g., need 14, pack size 5, 2 suppliers
Supplier A has 11
Supplier B has 40
Buy 9 from Supplier A, with our remaining need being 3.
Buy 5 from supplier B, with our remaining need being -2
*/
--feed rows into #supplierpurchasesbase while #StockLevel > 0
--Figure out how many we need to buy, based upon PackSize
IF #QuantityToShip % #PackSize > 0
BEGIN
SET #Buy = #QuantityToShip - #QuantityToShip % #PackSize + #PackSize
END
ELSE
BEGIN
SET #Buy = #QuantityToShip
END
IF #StockLevel < #Buy
BEGIN
--PRINT 'Supplier only has ' + CAST(#StockLevel AS VARCHAR) + ' for us to buy.'
SET #Buy = #StockLevel
END
INSERT INTO #supplierpurchasesbase (
GoodID
, ConditionID
, SupplierStocklistItemID
, Quantity
, SupplierID
, SupplierCurrencyID
, CostPrice
, RetailerID )
SELECT #GoodID
, #ConditionID
, #SupplierStocklistItemID
, #Buy
, #SupplierID
, #SupplierCurrencyID
, #CostPrice
, #RetailerID
--update #QuantityToShip & the row in CUR_AVAILABLE
IF #StockLevel <= #Buy
BEGIN
UPDATE CUR_AVAILABLE
SET StockLevel = #StockLevel - #Buy
WHERE CURRENT OF CUR_AVAILABLE
SET #QuantityToShip = 0
END
ELSE
BEGIN
UPDATE CUR_AVAILABLE
SET StockLevel = 0
WHERE CURRENT OF CUR_AVAILABLE
SET #QuantityToShip = #QuantityToShip - #Buy
END
--update the stocklevel so we don't see the thing again if we've used it up.
IF #QuantityToShip = 0 --Don't need any more
BEGIN
UPDATE CUR_NEEDED
SET OrderStatusID = #StatusPendingPO
WHERE CURRENT OF CUR_NEEDED
BREAK
END
ELSE --Need more, move next, if we can
FETCH NEXT FROM CUR_AVAILABLE INTO #SupplierStocklistItemID, #SupplierID, #StockLevel, #SupplierCurrencyID, #CostPrice
END
CLOSE CUR_AVAILABLE
DEALLOCATE CUR_AVAILABLE
FETCH NEXT FROM CUR_NEEDED INTO #GoodID, #ConditionID, #QuantityToShip, #OrderStatusID, #RetailerID, #PackSize
END
CLOSE CUR_NEEDED
DEALLOCATE CUR_NEEDED
The problem I'm running into is that I get I'm getting the error
Invalid object name 'CUR_AVAILABLE'.
when I'm attempting to update CURRENT OF CUR_AVAILABLE.
I've tried defining the CUR_AVAILABLE cursor as #CUR_AVAILABLE but get a different error. I've tried defining the CUR_AVAILABLE cursor outside of the WHILE loop of CUR_NEEDED, I've tried not closing / deallocating the cursor, etc. None of this seems to work.
Any ideas where I'm going wrong, here (other than not using sets, unless you've got a set-based solution)?
The following query uses a recursive CTE and, therefore, can't be considered a truly set-based solution. Nevertheless, I would still expect it to perform better than your two cursors (or to be worth trying, at the very least):
WITH buys (
GoodID,
ConditionID,
SupplierStocklistItemID,
Quantity,
SupplierID,
SupplierCurrencyID,
CostPrice,
RetailerID,
PriceRank,
RemainingNeed,
PackSize
)
AS (
SELECT
GoodID,
ConditionID,
SupplierStocklistItemID = 0,
Quantity = 0,
SupplierID = 0,
SupplierCurrencyID = 0,
CostPrice = CAST(0.00 AS decimal(10,2)),
RetailerID,
PriceRank = 0,
RemainingNeed = QuantityToShip,
PackSize
FROM #needorders
UNION ALL
SELECT
p.GoodID,
p.ConditionID,
p.SupplierStockListItemID,
Quantity = y.CurrentBuy,
p.SupplierID,
p.SupplierCurrencyID,
p.CostPrice,
b.RetailerID,
p.PriceRank,
RemainingNeed = b.RemainingNeed - y.CurrentBuy,
b.PackSize
FROM #orderedprices p
INNER JOIN buys b ON p.GoodID = b.GoodID
AND p.ConditionID = b.ConditionID
AND p.PriceRank = b.PriceRank + 1
CROSS APPLY (
SELECT RemainingNeedAdjusted =
(b.RemainingNeed + b.PackSize - 1) / b.PackSize * b.PackSize
) x
CROSS APPLY (
SELECT CurrentBuy = CASE
WHEN x.RemainingNeedAdjusted > p.StockLevel
THEN p.StockLevel
ELSE x.RemainingNeedAdjusted
END
) y
WHERE p.StockLevel > 0
AND b.RemainingNeed > 0
)
SELECT
GoodID,
ConditionID,
SupplierStocklistItemID,
Quantity,
SupplierID,
SupplierCurrencyID,
CostPrice,
RetailerID
FROM buys
WHERE PriceRank > 0
ORDER BY
GoodID,
ConditionID,
PriceRank
Basically, the CTE forms the rows almost identical to those your query is inserting into #supplierpurchasesbase, except it additionally features auxiliary columns serving as kind of internal variables. (They are not pulled by the final SELECT, though.)
The anchor part forms a set of 0-quantity records based on the #needordered table, together with the initial values for the auxiliary columns. The recursive part contains all the logic: calculates the quantity to buy, updates the "remaining need" quantity for the next iteration, checks whether the next iteration is needed.
Certain assumptions have been made, and I hope you'll be able find your way around them if they do not match your real situation. For instance, quantities, pack sizes are assumed to be integer, and part of the logic relies on that, because it uses integral division. It is also assumed that PriceRank is a sequence of integers starting from 1, unique per (GoodID, ConditionID).
This script, as well as a minimal test setup, can be found, tested, modified, and tested on SQL Fiddle.
The problem was twofold: The update syntax should not be:
UPDATE CUR_AVAILABLE
SET StockLevel = #StockLevel - #Buy
WHERE CURRENT OF CUR_AVAILABLE
Rather, the syntax should be:
UPDATE #orderedprices
SET StockLevel = #StockLevel - #Buy
WHERE CURRENT OF CUR_AVAILABLE
Also, in order to be updatable, the temp table needed to have a primary key:
ALTER TABLE #orderedprices ADD CONSTRAINT PRIMARY KEY CLUSTERED (RowCtr)
Lesson learned, I guess, but it certainly took me a fair bit of grief to find the solution!