I have changed this stored procedure many times and still keep getting the same error. I know I am getting rows back because I tested the cursor select separately. I have read other similar issues on this site but I don't seem to have the same fix.
DECLARE iDone INTEGER(10) DEFAULT 0;
DECLARE userID INTEGER(10);
DECLARE creditRemaining INTEGER(10);
DECLARE column_cur CURSOR FOR
SELECT `userID`, `creditRemaining` FROM `access`
WHERE (`dateExpire`>=now() OR `isRenewed`=1) and `descriptionShort`='Subscription';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET iDone=1;
SET userID = 0;
SET creditRemaining = 0;
OPEN column_cur;
LOOP1: LOOP
FETCH column_cur INTO userID, creditRemaining;
IF iDone = 1
THEN
LEAVE LOOP1;
END IF;
-- SELECT userID, creditRemaining;
UPDATE `users` SET `saveCount`=creditRemaining, `searchCount`=6000 WHERE `ID`=userID;
END LOOP LOOP1;
CLOSE column_cur;
I think this is a duplicate of more, except for the fact you already have a HANDLER declared. In another Error 1329 question, this answer from #DanJGer pointed out a note in the docs:
Before MySQL 5.6.3, if a statement that generates a warning or error causes a condition handler to be invoked, the handler may not clear the diagnostic area. This might lead to the appearance that the handler was not invoked. The following discussion demonstrates the issue and provides a workaround.
That's likely the problem you're encountering.
Related
I have read the documentation on Exit Handlers and have found useful code ideas in relevant SO questions here and here amongst other places.
Nevertheless, calling the stored procedure below appears to complete OK and returns a TRUE value for the parameter success. when I know it is definitely not completing OK, not least because there was a syntax error in the body SQL (it was referring to a field that did not exist).
So the exit handler should have kicked in and returned FALSE for parameter success.
Can anyone help me to understand why a transaction that fails does not return the correct value for the parameter? (I suspect it has something to do with where I set success to true)
The actual SQL inside the transaction is not important to this question so I haven't shown it. Just assume that it might or might not successfully complete the transaction. It is the success or failure of that transaction that I want to detect through the parameter
DELIMITER $$
CREATE PROCEDURE do_thing (OUT success BOOLEAN)
DETERMINISTIC
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN SET success := FALSE; ROLLBACK; END; # rollback on any error
DECLARE EXIT HANDLER FOR SQLWARNING BEGIN SET success := FALSE; ROLLBACK; END; # rollback on any warning
START TRANSACTION;
< SQL that might cause an error >
< in my case it was referring to a field that didn't exist>
COMMIT;
SET success := TRUE;
END$$
DELIMITER ;
SEE DBFIDDLE
The first part is a copy of your code, it throws an error....
The second part is corrected, both DECLARE EXIT are moved within the block.
The third part is an example where #success will be set to false.
I have a mysql code that will iterate through the list and changes the total salary field. However, what I don't understand is when dose the value of 'done' change for the loop to stop?. Becaunse UNTIL DONE depends on the value to change. This is an example from a book. Anyway, here is the code:
CREATE PROCEDURE updateSalary() BEGIN
DECLARE done INT DEFAULT 0;
DECLARE current_dnum INT;
DECLARE dnumcur CURSOR FOR SELECT dnumber FROM deptsal;
DECLARE continue HANDLER FOR NOT FOUND SET DONE = 1;
OPEN dnumcur;
REPEAT
FETCH dnumcur INTO current_dnum;
UPDATE deptsal SET totalSalary = (SELECT SUM(salary) FROM employee
WHERE dno=current_dnum) WHERE dnumber=current_dnum;
UNTIL done
END REPEAT;
CLOSE dnumcur;
END$$
delimiter ;
Any help would be appreciated!
Thanks.
The value of done will be changed when the cursor does not find any more data in the data set.
This is controlled by the handler in your code:
DECLARE continue HANDLER FOR NOT FOUND SET done = 1;
This is basically saying, when there is no more data in the data set for this cursor, set the value of done to 1.
There is some extra information on this at: https://dev.mysql.com/doc/refman/5.7/en/declare-handler.html - that reads "NOT FOUND: Shorthand for the class of SQLSTATE values that begin with '02'. This is relevant within the context of cursors and is used to control what happens when a cursor reaches the end of a data set. If no more rows are available, a No Data condition occurs with SQLSTATE value '02000'. To detect this condition, you can set up a handler for it or for a NOT FOUND condition."
Hope that helps :)
I am trying to use a cursor in MySQL to call a stored procedure many times. I want to call it as many times as a value for my_id exists in some temporary table, and iterate through those ids and concatenate the results.
Anyway, I'm having trouble with this part of the process:
DECLARE curs CURSOR FOR
SELECT something FROM somewhere;
I don't want to select something from somewhere. I want something like
DECLARE curs CURSOR FOR
CALL storedproc(#an_id);
Can the DECLARE statement be used to call a stored procedure? Or does it have to be associated with a SELECT only? Googling around, I'm afraid that the latter is the case.
Using a cursor requires some standard boilerplate code to surround it.
Using a cursor to call a stored procedure for each set of values from the table requires essentially the same boilerplate. You SELECT the values you want to pass, from wherever you're getting them (which could be a temporary table, base table, or view, and can include calls to stored functions) and then call the procedure with those values.
I've written an syntactically valid example of that boilerplate code, below, with comments to explain what each component is doing. There are few things I dislike more than being asked to just do something "just because" -- so everything is (hopefully) explained.
You mentioned calling the procedure with multiple values, so this example uses 2.
Note that there events that happen her are in a specific order for a reason. Variables have to be declared first, cursors have to be declared before their continue handlers, and loops have to follow all of those things. This gives an impression that there's some fairly extreme inflexibility, here, but that's not really the case. You can reset the ordering by nesting additional code inside BEGIN ... END blocks within the procedure body; for example, if you needed a second cursor inside the loop, you'd just declare it inside the loop, inside another BEGIN ... END.
DELIMITER $$
DROP PROCEDURE IF EXISTS `my_proc` $$
CREATE PROCEDURE `my_proc`(arg1 INT) -- 1 input argument; you might not need one
BEGIN
-- from http://stackoverflow.com/questions/35858541/call-a-stored-procedure-from-the-declare-statement-when-using-cursors-in-mysql
-- declare the program variables where we'll hold the values we're sending into the procedure;
-- declare as many of them as there are input arguments to the second procedure,
-- with appropriate data types.
DECLARE val1 INT DEFAULT NULL;
DECLARE val2 INT DEFAULT NULL;
-- we need a boolean variable to tell us when the cursor is out of data
DECLARE done TINYINT DEFAULT FALSE;
-- declare a cursor to select the desired columns from the desired source table1
-- the input argument (which you might or might not need) is used in this example for row selection
DECLARE cursor1 -- cursor1 is an arbitrary label, an identifier for the cursor
CURSOR FOR
SELECT t1.c1,
t1.c2
FROM table1 t1
WHERE c3 = arg1;
-- this fancy spacing is of course not required; all of this could go on the same line.
-- a cursor that runs out of data throws an exception; we need to catch this.
-- when the NOT FOUND condition fires, "done" -- which defaults to FALSE -- will be set to true,
-- and since this is a CONTINUE handler, execution continues with the next statement.
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
-- open the cursor
OPEN cursor1;
my_loop: -- loops have to have an arbitrary label; it's used to leave the loop
LOOP
-- read the values from the next row that is available in the cursor
FETCH NEXT FROM cursor1 INTO val1, val2;
IF done THEN -- this will be true when we are out of rows to read, so we go to the statement after END LOOP.
LEAVE my_loop;
ELSE -- val1 and val2 will be the next values from c1 and c2 in table t1,
-- so now we call the procedure with them for this "row"
CALL the_other_procedure(val1,val2);
-- maybe do more stuff here
END IF;
END LOOP;
-- execution continues here when LEAVE my_loop is encountered;
-- you might have more things you want to do here
END $$
DELIMITER ;
Can the DECLARE statement be used to call a stored proc?
Not possible and documentation is pretty clear on that
Cursor DECLARE Syntax
This statement declares a cursor and associates it with a SELECT statement that retrieves the rows to be traversed by the cursor. To fetch the rows later, use a FETCH statement. The number of columns retrieved by the SELECT statement must match the number of output variables specified in the FETCH statement.
I have a mysql stored procedure made it recursively, it always returned 499 rows max.
my stored procedure is moving in a tree (not a binary tree) and check the nodes if they have children and so on until it reached the leaves.
I don't know how can I convert my code into non-recursive way, I just want to ask for tow points:
how can I make an infinite recursive in mysql(mysql server version is 5.5)?
if that can't happened, how can I change my cod into non-recursive way?
CREATE PROCEDURE `get_citations`(in _pub_id int(10),in _lvl int,citation_count int)
BEGIN
DECLARE done INT DEFAULT FALSE;
declare p_id,c_count int;
declare _counter int default 1;
DECLARE cur1 CURSOR FOR SELECT pat_publn_id,cited_count from temp.a_citations
where pub_parent=_pub_id ;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
insert into a_citations
(pat_publn_id ,
publn_nr ,
publn_kind,
publn_auth,
publn_date,
cited_pat_publn_id,
cited_count,
pub_lvl,
pub_parent)
(select p.pat_publn_id,p.publn_nr,p.publn_kind,p.publn_auth,p.publn_date,c.cited_pat_publn_id,
(select count(*) as cnt FROM patstat1304.tls212_citation c2 where c2.cited_pat_publn_id=c.pat_publn_id) as cited_count,_lvl as pub_lvl,_pub_id as pub_parent
from patstat1304.tls212_citation c,patstat1304.tls211_pat_publn p
where c.pat_publn_id=p.pat_publn_id and c.cited_pat_publn_id=_pub_id);
commit;
OPEN cur1;
read_loop: LOOP
fetch cur1 into p_id,c_count;
IF (c_count !=0) then
call get_citations( p_id,_lvl+1,c_count);
commit;
END if;
IF done THEN
LEAVE read_loop;
END IF;
set _counter=_counter+1;
if(_counter=citation_count) then
LEAVE read_loop;
end if;
end loop;
CLOSE cur1;
END
MySQL can not execute stored procedures with a very deep nesting.
Very soon, error ER_STACK_OVERRUN_NEED_MORE will appear.
Increasing the thread stack to go further will not work either.
To change the recursive call to a non recursive one, consider something like this:
1) Create a table named publications_to_process, with the publication and search level.
2) To start the search, insert the original publication in this table, with level 1.
3) In a loop, fetch one publication, examine the citations, and add the publications listed in the publications_to_process, incrementing the level.
4) As a bonus, for cases like:
Pub_1 --> Pub_2 --> Pub_3,
Pub_1 --> Pub_3
there is no need to add Pub_3 again to the search if it has been processed already.
In other words, the publications are more likely to be a directed graph that a tree.
5) Either make the table temporary, or consider adding a PROCESSLIST_ID column, so that different sessions (having a different CONNECTION_ID()) do not step on each other, when executing this search in parallel.
I have a sp with two nested cursors. The outer cursor is customers, and the inner cursor is periods.
when an error occurs in the inner cursor I want to rollback what has been done for the specific customer and proceed with the processing of the next customer. However when the inner cursor is executed for the next customer (after an exception has occurred) I get an "cursor is already open message"
The code looks like this:
DECLARE customers CURSOR FOR
select * from customers_table;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET no_more_customers = 1;
OPEN customers;
customers_cursor:
REPEAT
FETCH customers
INTO ....
IF no_more_customers = 1
THEN
close customers;
LEAVE customers_cursor;
END IF;
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
rollback;
END;
....... //do some stuff
BEGIN
DECLARE no_more_periods INT(1) DEFAULT 0;
DECLARE periods CURSOR FOR
SELECT ...
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET no_more_periods = 1;
OPEN periods;
periods_cursor:
REPEAT
FETCH periods INTO...
IF no_more_periods = 1
THEN
close periods;
LEAVE periods_cursor;
END IF;
..... //do some stuff point 1
UNTIL no_more_periods = 1
END REPEAT periods_cursor;
end;
END;
UNTIL no_more_customers = 1
END REPEAT customers_cursor;
END;
Customer 1 runs and and exception occurs in //do some stuff point 1. Then customer 2 runs until the open periods statement is reached and that's when I get the "Cursor is already open".
Thanks a lot for your help.
I do not see any transactions or commits in your code. I would change the code to include the following:
Start a new transaction for each customer
Commit this transaction before starting on the next customer.
Close the periods cursor before performing a rollback.
thanks a lot for your help #JodyT .I am initiating a new block for each loop of the customer cursor (begin after the "if no_more_customer=1" block". Also i am doing "commit" before next customer. Unfortunately i cannot close periods cursor in the exception handler of this block (cursor periods not defined in this scope-compile time error).What i did to overcome the issue was to define an exception handler inside the inner loop (exactly after the continue handler of the periods cursor).In this handler i close periods and then raise exception to perform rollback in the outter exception handler