MySQL Stored Procedure - Nested loop at fault? - mysql

I have a medium sized stored procedure going on here below. My problem is that it doesn't do anything and I have no idea why.
1.) First of all, the code:
DROP PROCEDURE IF EXISTS deleteabundant_fixshared_shiftResources;
DELIMITER //
CREATE PROCEDURE deleteabundant_fixshared_shiftResources ()
BEGIN
DECLARE finish_flag BOOLEAN DEFAULT FALSE;
DECLARE id INT(11);
DECLARE startTime DATETIME;
DECLARE endTime DATETIME;
DECLARE shid INT(11);
DECLARE resid INT(11);
DECLARE id_inner INT(11);
DECLARE startTime_inner DATETIME;
DECLARE endTime_inner DATETIME;
DECLARE shid_inner INT(11);
DECLARE resid_inner INT(11);
DECLARE cr130 CURSOR FOR SELECT shift_resource_id, start_date, end_date, shift_id, resource_id FROM temp_shift_resource;
DECLARE cr131 CURSOR FOR SELECT shift_resource_id, start_date, end_date, shift_id, resource_id FROM temp_shift_resource;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET finish_flag = TRUE;
START TRANSACTION;
OPEN cr130;
OPEN cr131;
OUTERLOOP: LOOP
FETCH cr130 into id, startTime, endTime, shid, resid;
IF finish_flag THEN LEAVE OUTERLOOP; END IF;
INNERLOOP: LOOP
FETCH cr131 INTO id_inner, startTime_inner, endTime_inner, shid_inner, resid_inner;
IF finish_flag THEN LEAVE INNERLOOP; END IF;
IF (id!=id_inner) THEN
IF (resid=resid_inner AND shid_inner!=9) THEN
-- logic to determine if the dates are wrong:
IF (startTime<=startTime_inner AND endTime>=endTime_inner) THEN
INSERT INTO repairchange ( shift_resource_id, changetype, shift_id, resource_id, start_date, end_date )
VALUES ( id_inner, "FD", shid_inner, resid_inner, startTime_inner, endTime_inner );
DELETE FROM temp_shift_resource WHERE shift_resource_id = id_inner;
ELSEIF (endTime>=endTime_inner AND startTime<=endTime_inner) THEN
INSERT INTO repairchange ( shift_resource_id, changetype, shift_id, resource_id, start_date, end_date )
VALUES ( id_inner, "FU", shid_inner, resid_inner, startTime_inner, endTime_inner );
UPDATE temp_shift_resource set endTime_inner=(startTime - INTERVAL 1 DAY) where shift_resource_id = id_inner;
ELSEIF (startTime<=startTime_inner AND endTime>=startTime_inner) THEN
INSERT INTO repairchange ( shift_resource_id, changetype, shift_id, resource_id, start_date, end_date )
VALUES ( id_inner, "FU", shid_inner, resid_inner, startTime_inner, endTime_inner );
UPDATE temp_shift_resource set startTime_inner=(endTime + INTERVAL 1 DAY) where shift_resource_id = id_inner;
END IF;
END IF;
END IF;
END LOOP INNERLOOP;
SET finish_flag = FALSE;
END LOOP OUTERLOOP;
CLOSE cr130;
CLOSE cr131;
COMMIT;
END //
DELIMITER ;
call deleteabundant_fixshared_shiftResources();
2.) Description of what I want to do:
Basically, I have a table full of workshifts. Due to code bugs, some of these shifts have a wrong date assigned to them, and I have to fix the database.
I have to run through the whole table, and compare the rows that are assigned to the same resource_id, which represents a person. So if a person has two shifts that look like (2016-05-10 to 2016-05-20) and (2016-05-15 to 2016-05-23) for example, I have to fix it so that one of them will be trimmed to (2016-05-10 to 2016-05-14) and (2016-05-15 to 2016-05-23).
A shift that is a nightshift, marked as shift_id=9, must not be modified at all.
I insert rows into the repairchange table if a change or a deletion has been made
3.) The procedure runs, but does nothing. I have examples in the database for wrong rows, one example is the one I wrote above. I suspect it is the nested loop, because I want to loop and fetch through the same table, but I haven't found anything on that.
I got the message
0 row(s) affected, 1 warning(s): 1329 No data - zero rows fetched, selected, or processed
but I have seen this before and my stored procedures have worked even though they output this warning.
Any ideas or tips are welcome. Thank you for your time!

I figured it out, after quite some debugging:
I opened the cursors before both of the loops. This meant that after the first walk-through of the inner loop, the cursor was standing at +1 of the LAST row of the table, and when the new outer loop iteration started the second inner loop iteration, the cursor was still at the end position.
Thus it did not run. I replaced the inner-cursor opening and closing into the outer loop, and now it works properly.

Related

error in stored procedure ...into keyword

Two tables Borrower(rollno,name,bookissue_date) and Fine(rollno,name,amount)
delimiter //
create procedure student( in roll_no int,in Nameofbook varchar(40))
begin
declare Dateofiss1 date;
Declare cur cursor for
select Dateofiss from Borrower where Roll_no = roll into Dateofiss1;
OPEN cur;
fetch cur into Dateofiss1
if(datediff(sysdate(),Dateofiss1)<15) then varchar(20))
update Borrower set status='R'where Roll_no=roll_no
elseif(datediff(sysdate(),Dateofiss1)>=15)and datediff (sysdate(),Dateofiss1<30)
SET FINEAMOUNT=5*(datediff(sysdate(),Dateofiss1)-15)
insert into Fine(Roll_no,Date,amount)values(rollno,sysdate,fineamount);
update.borrower set status='R' where Roll_no='rollno';
elseif (datediff(sysdate(),Dateofiss1)>30)
SET FINEAMOUNT=50*(datediff(sysdate(),Dateofiss1)-15)
insert into Fine(Roll_no,Date,amount)values(rollno,sysdate,fineamount);
update.borrower set status='R' where Roll_no='rollno';
close cur;
end if
select * from Borrower;
elect * from Fine;
end
You have a number of syntax errors.
You have an extraneous varchar(20)) in the first if statement.
You're missing THEN in the ELSEIF statements.
You wrote update.borrower instead of update borrower.
You have roll_no in quotes in some of your update statements.
The roll_no parameter is the same as a table column, since column names are case-insensitive. The condition where Roll_no = roll_no will match every row because of this. Give the parameter a different name.
In a SELECT, the INTO clause goes after FROM, not at the end.
There's no need to use a cursor if you're using SELECT INTO. Just execute the query and it will set the variable.
You can also simplify the code by putting the date difference in a variable, so you don't have to repeatedly calculate it. And in the ELSEIF you don't need to test >= 15, since you'll only get there if the < 15 test failed.
The UPDATE statement is the same in all conditions, so it doesn't need to be in the IF at all.
delimiter //
create procedure student( in p_roll_no int,in Nameofbook varchar(40))
begin
declare Dateofiss1 date;
declare diff INT;
select Dateofiss from Borrower into Dateofiss1 where Roll_no = p_roll_no;
OPEN cur;
SET diff = datediff(sysdate(),Dateofiss1)
IF diff BETWEEN 15 AND 29 THEN
SET FINEAMOUNT= 5 * (diff - 15)
insert into Fine(Roll_no,Date,amount)values(rollno,sysdate,fineamount);
else
SET FINEAMOUNT= 50 * (diff - 15)
insert into Fine(Roll_no,Date,amount)values(rollno,sysdate,fineamount);
end if
update Borrower set status='R'where Roll_no=p_roll_no
select * from Borrower;
select * from Fine;
end

How to add salary from two tables in stored procedure

I want to add the salary from two tables in stored procedure on the basis of id column:
DDl:
create table salary1 (id varchar(20), salary varchar(20));
create table salary2 (id varchar(20), salary varchar(20));
DML:
insert into salary1 values('1', '100');
insert into salary1 values('2', '200');
insert into salary2 values('1', '10');
insert into salary2 values('2', '10');
Database: mysql
Output should like this:
id total_sal
1 110
2 210
My stored procedure look like:
CREATE PROCEDURE totalSal()
BEGIN
DECLARE tbl1_id varchar(30);
DECLARE tbl1_sal varchar(30);
DECLARE tbl2_id varchar(30);
DECLARE tbl2_sal varchar(30);
DECLARE total_sal varchar(30);
DECLARE c1 CURSOR FOR SELECT * FROM salary1;
DECLARE c2 CURSOR FOR SELECT * FROM salary2;
-- Open first cursor
OPEN c1;
LOOP
FETCH c1 INTO tbl1_id, tbl1_sal;
-- Open second cursor
OPEN c2;
LOOP
FETCH c2 INTO tbl2_id, tbl2_sal;
IF tbl1_id = tbl2_id THEN
set total_sal := tbl1_sal + tbl2_sal;
ELSE
set total_sal := tbl_sal;
END IF;
END LOOP;
CLOSE c2;
END LOOP;
CLOSE c1;
end $$
It got's successfully compiled, but when i am running the procedure i am getting the below error:
ERROR 1329 (02000): No data - zero rows fetched, selected, or processed
I have also used the DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; in my procedure. but still my problem is unresolved.
If someone can solve this problem in oracle, that would also help me.
Note : I cannot perform join operation on these tables. Because of a few performance issues.
Thanks in advance !!!
Solution 1:
Using collection and only one iteration of 2 loop
You should consider to fix your performance issue on join. Performing loop is slower than a set base approach in most case.
If I follow your logic, what you realy want is to loop trough all the salary2 table for each salary1 row in order to found the right ID => millions of loop.
You can consider doing 2 separated loop and store data inside and indexed array. ( the key will be the tlb1_id).
If the key exist : sum the salary values, if not exist insert it inside the array.
At the end of the procedure, just select the array as table.
Solution 2:
Using a join on integer indexed columns
you can add a new integer column on each table
Populate this column with the casted value of the ID column
Add an index on these columns on each tables
After that you will be able to perform a join
Have a look at this fiddle http://sqlfiddle.com/#!9/c445de/1 , it can be time consuming to perform theses step and disk space consumuming to add a new columns and indexes but the join operation may be faster than before.
You can do something like this... I have moved the second cursor inside the loop so that it only goes over the id's from table 1. This should help the logic for the procedure but still I would recommend trying to figure out how to fix the join to get the results as that seems like an easier way and should be much faster if done correctly.
CREATE PROCEDURE totalSal()
BEGIN
DECLARE tbl1_id varchar(30);
DECLARE tbl1_sal varchar(30);
DECLARE tbl2_id varchar(30);
DECLARE tbl2_sal varchar(30);
DECLARE total_sal varchar(30);
DECLARE c1 CURSOR FOR SELECT * FROM salary1;
-- Open first cursor
OPEN c1;
LOOP
FETCH c1 INTO tbl1_id, tbl1_sal;
SELECT COUNT(*) INTO v_rowcount FROM salary2 WHERE id = tbl1_id;
IF v_rowcount > 0 THEN
Begin
DECLARE c2 CURSOR FOR SELECT * FROM salary2 WHERE id = tbl1_id;
-- Open second cursor
OPEN c2;
LOOP
FETCH c2 INTO tbl2_id, tbl2_sal;
IF tbl1_id = tbl2_id THEN
set total_sal := tbl1_sal + tbl2_sal;
ELSE
set total_sal := tbl_sal;
END IF;
END LOOP;
CLOSE c2;
END IF;
END
END LOOP;
CLOSE c1;
end $$
Well you asked for an answer without JOIN, but that seemed arbitrary, so here's an answer with JOIN.
SELECT
sums1.id
, S1Sum + S2Sum AS SalarySum
FROM (SELECT id, SUM(CAST(salary AS int)) AS S1Sum
FROM salary1
GROUP BY id) sums1
JOIN (SELECT id, SUM(CAST(salary AS int)) AS S2Sum
FROM salary2
GROUP BY id) sums2
ON sums1.id = sums2.id
I am guessing your performance is bad because all of your columns are varchar when they should be int or numeric. But we don't have much to go on so hopefully this helps you come to a solid solution.
Also the post was edited to add both MySQL and Oracle tags so it's difficult to determine what the syntax should be...

MySQL Stored Procedure Updating Status of Accounts Error Code: 1329

I have a situation in which I would like to iterate through a "schedPayments" table that stores a schedule of payments corresponding with a client in the "client" table. The client table also contains a "status" column at the moment holds a 0 for "Past Due" and a 1 for "Current". When the balance from the client table is greater than the supposed balance from the schedPayments table AND today's date is later than the date the payment was scheduled for, the status column in the clients table should be set to 0.
I may be completely off the wall with my solution, but I keep getting Error Code: 1329. No Data - zero rows fetched, selected or processed. The MySQL Workbench lacks some major debugging capabilities that I wish it had. The documentation also doesn't quite cover what I need either in this situation.
CREATE PROCEDURE `project`.`status_update` ()
BEGIN
DECLARE balance DECIMAL(20) DEFAULT 0;
DECLARE cID INT(10) DEFAULT 0;
DECLARE currentID INT(10) DEFAULT 0;
DECLARE supposedBal DECIMAL(20) DEFAULT 0;
DECLARE payDate DATE;
DECLARE cur1 CURSOR FOR SELECT ClientID,SupposedBalance,Date FROM project.schedpayments;
OPEN cur1;
status_loop: LOOP
FETCH cur1 INTO cID, supposedBal, payDate;
BLOCK2: BEGIN
DECLARE cur2 CURSOR FOR SELECT balance FROM project.client WHERE ID=cID;
OPEN cur2;
FETCH cur2 INTO balance;
IF currentID > cID THEN
SET currentID = cID;
IF (CURDATE() > payDate) AND (supposedBal < balance) THEN
UPDATE feeagree SET Status=0 WHERE ID=cID;
END IF;
CLOSE Cur2;
END IF;
END BLOCK2;
END LOOP;
CLOSE cur1;
END $$
You can see the remnants of how I had enclosed the entire procedure in a block and that only resulted in the compiler thinking the first block ended with END BLOCK2; and that resulted in an Error Code 1325. Cursor is already open.
I am definitely making this more complicated than necessary, so any help would be much appreciated. The only way I learn this stuff is trial by fire, and it is super hot today.
It seems that you don't need all those cursors and you can achieve your goal with one UPDATE statement.
It's hard to be precise without seeing your tables structures and sample data, but a more succinct version of your SP might look like this
CREATE PROCEDURE status_update()
UPDATE feeagree
SET Status = 0
WHERE ID IN
(
SELECT p.cID
FROM schedpayments p JOIN client c
ON p.cID = p.ID
WHERE p.Date < CURDATE()
AND p.SupposedBalance < c.balance
GROUP BY p.cID
);
...
DECLARE done TINYINT DEFAULT FALSE;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET DONE = true;
OPEN cur1;
status_loop: LOOP
FETCH cur1 INTO cID, supposedBal, payDate;
IF DONE = true THEN LEAVE status_loop; END IF;
...
SET DONE = false;
END LOOP;
The SET DONE = false at the end resets DONE in case anything in the inner block results in it getting set to TRUE;

Stored Procedure taking ages to execute?

DELIMITER $$
CREATE PROCEDURE Load_Fact_List()
BEGIN
DECLARE Project_Number_Temp INT;
DECLARE Panel_Id_Temp INT;
DECLARE Employee_Id_Temp INT;
DECLARE Zip_Temp VARCHAR(255);
DECLARE Created_Date_Temp DATE;
DECLARE Country_Temp VARCHAR(255);
DECLARE no_more_rows BOOLEAN;
DECLARE loop_cntr INT DEFAULT 0;
DECLARE num_rows INT DEFAULT 0;
DECLARE load_cur CURSOR FOR
SELECT Project_Id, Panel_Id, Employee_Id, Zip, Created_Date
FROM Fact_List;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET no_more_rows = TRUE;
OPEN load_cur;
select FOUND_ROWS() into num_rows;
the_loop: LOOP
FETCH load_cur
INTO Project_Number_Temp, Panel_Id_Temp, Employee_Id_Temp, Zip_Temp, Created_Date_Temp;
IF no_more_rows THEN
CLOSE load_cur;
LEAVE the_loop;
END IF;
SET Country_Temp= (select Country from Zip where Zip= Zip_Temp);
INSERT INTO Test_Fact
(
Project_Key,
Campaign_Key,
Respondents_Key,
Event_Key,
Employee_Key,
Geography_Key,
Date_Key
)
SELECT (SELECT Project_Key from Project_Dim where Project_Id= Project_Number_Temp AND Quota_Country= Country_Temp),0,(SELECT MAX(Respondents_Key) from Respondents_Dim WHERE Panel_Id= Panel_Id_Temp),1,(select MAX(Employee_Key) from Employee_Dim WHERE Employee_Id= Employee_Id_Temp),(Select Geography_Key from Geography_Dim where Zip= Zip_Temp), (Select Date_Key from Date_Dim where Full_Date= Created_Date_Temp);
SET loop_cntr = loop_cntr + 1;
END LOOP the_loop;
select num_rows, loop_cntr;
END $$
The above code is properly working but it is damn slow. For every 1 hour it is loading 1000 records. I got lacks of records to load into fact table. can anyone suggest me any optimization?
Requirement is to load fact table by looping through other table and gathering required key values from dimension tables.
The usual procedure is actually like this.
You have your dimensions built and you just gathered the data you want to insert into your fact table in a temporary table. Then you insert this data in another temporary table like this:
INSERT INTO tmp_fact_table
(
fact_key,
dim1_key,
dim2_key,
...
fact1,
fact2
...
)
SELECT
ISNULL (f.fact_key, 0),
ISNULL (d1.sid, 0) as whatever,
ISNULL (d2.sid, 0) as whatever2,
...
ISNULL (tt.fact1, 0),
ISNULL (tt.fact2, 0)
FROM
yourTempTable tt
LEFT JOIN Dim1 d1 ON tt.identifying_column = d1.identifying_column
...
LEFT JOIN fact_table f ON
f.dim1_key = d1.sid
AND f.dim2_key = d2.sid
where
fact_key is the identifying column in your fact table
dim1_key is the foreign key in your fact table to the dimensions
fact1 and so on are the facts you want in your fact table, clear
the ISNULL() function returns 0 when no entry is found. 0 is the id of your dummy row in each dimension for unknown data
Then you will have a table where you have the IDs of your dimensions linked to the data you want to import into your fact table with 0 as fact key when the entry in the fact table does not already exist and the ID of the fact table entry otherwise.
Then you update the fact table where tmp_fact_table.fact_key != 0
Then you insert into the fact table where tmp_fact_table.fact_key = 0
That's it.
I'm doing this with millions of rows and it takes about half an hour. 300,000 rows is peanuts.

Mysql FETCH CURSOR result ununderstood

I've been Googleing around for a while and I am sure that the problem is that I don't understand clearly how CURSORs in MySQL work.
A short explanation of the problem: I'm writing such function (simplified):
CREATE DEFINER=`me`#`localhost` FUNCTION `product_move`(prID INT, tr_type VARCHAR(2), clID INT, am INT, dnID INT, usrID INT, price FLOAT(10,2), ti DATETIME, barc TINYTEXT, cmt TINYTEXT, lnID INT)
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE cur_id INT;
DECLARE net_pr FLOAT(10,2);
DECLARE cur_r INT;
DECLARE remaind INT DEFAULT 0;
DECLARE avg_price FLOAT(10,2) DEFAULT 0;
DECLARE curs CURSOR FOR SELECT `products_transactionsID`,
`price`,
`remains`
FROM `products_transactions`
WHERE `productID`=prID AND `remains`>0 AND `type`='V'
ORDER BY `products_transactionsID` ASC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN curs;
SET remaind=am;
read_loop:LOOP
FETCH curs INTO cur_id,net_pr,cur_r;
IF done THEN
LEAVE read_loop;
END IF;
IF (cur_r>=remaind) THEN
SET avg_price = avg_price + (net_pr * remaind);
UPDATE `products_transactions` SET `remains`=`remains`-remaind WHERE products_transactionsID=cur_id;
LEAVE read_loop;
ELSE
SET avg_price = avg_price + (net_pr * cur_r);
SET remaind=remaind-cur_r;
UPDATE `products_transactions` SET `remains`=0 WHERE products_transactionsID=cur_id;
END IF;
END LOOP;
CLOSE curs;
SET avg_price=avg_price/am;
INSERT INTO products_transactions
(`products_transactionsID`,`clientID`,`date_created`,`delivery_notesID`,`type`,`productID`,`amountIN`,`amountOUT`,`barcodes`,`in_stock`,`out_stock`,`out_repair`,`out_loss`,`booked`,`ordered`,`userID`,`price`,`comments`,`fifo_buy_price`)
SELECT NULL, clID, ti, dnID , tr_type, prID, 0, am, barc, products_transactions.in_stock-am, products_transactions.out_stock,
products_transactions.out_repair, products_transactions.out_loss, products_transactions.booked, products_transactions.ordered,usrID,price,cmt,avg_price
FROM
products_transactions WHERE productID=prID ORDER BY products_transactionsID DESC LIMIT 1;
So, we insert a new row in this table, based upon some calculations from the previously selected rows and updating these rows meanwhile.
The problem is with the avg_price variable, which should be calculated based on the net_pr variable which is FETCH'ed from the cursor. But somehow, instead of being FETCH'ed from the SELECT, the net_pr variable takes the value of the price input parameter of my function! How is that possible?
My guesses have been so far:
a variable name conflict? Searched through the code but I can't find any.
updating the table within the LOOP could make the CURSOR loose its position? It would make sense, but that wouldn't result in this, either...
I'd apreciate any ideas.
Two things that I can see:
1) Don't update the table that you're using in the cursor. MySQL says the cursor is read only but I wouldn't trust this. Set your value, exit the cursor, and then update the table.
2) Using the same name for a variable in the proc definition and a column in a select gives a conflict: http://dev.mysql.com/doc/refman/5.0/en/local-variable-scope.html
"A local variable should not have the same name as a table column. If an SQL statement, such as a SELECT ... INTO statement, contains a reference to a column and a declared local variable with the same name, MySQL currently interprets the reference as the name of a variable. "