mySQL (stored procedure) cursor infinite loop on last row - mysql

I have following code: Problem is on the last row of creature_y loop doesn't end and inserts same values (with increased guid) infinitely.
I tried few ways with changing continue handler but seems it is not that.
DELIMITER $$
DROP PROCEDURE IF EXISTS creature_copy
$$
CREATE PROCEDURE creature_copy ()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE new_guid INT(10);
-- Creature definition
DECLARE y_guid INT(10);
DECLARE y_id mediumint(8);
DECLARE y_map int(5);
DECLARE y_modelid mediumint(8);
DECLARE y_position_x float(10);
DECLARE y_position_y float(10);
DECLARE y_position_z float(10);
DECLARE y_orientation float(10);
DECLARE y_spawntimesecs INT(10);
DECLARE y_curhealth INT(10);
DECLARE y_curmana INT(10);
DECLARE y_MovementType tinyint(3);
-- waypoints definition
DECLARE w_id INT(10);
DECLARE w_point mediumint(8);
DECLARE w_position_x float(10);
DECLARE w_position_y float(10);
DECLARE w_position_z float(10);
DECLARE w_orientation float(10);
-- Generate creatures map
DECLARE creature_sel CURSOR FOR
SELECT guid,id, map, modelid, position_x, position_y, position_z, orientation, spawntimesecs, curhealth, curmana, MovementType
FROM creature_y;
-- Generate waypoints map
DECLARE waypoint_sel CURSOR FOR
SELECT id, point, position_x, position_y, position_z, orientation
FROM creature_movement
WHERE creature_movement.id = y_guid;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN creature_sel;
-- Cleanup tables before re-running
TRUNCATE creature_t;
TRUNCATE waypoint_data;
-- Set starting guid before loop
SET new_guid = 504115;
creature_loop:LOOP
if done = 1 THEN
-- set done = 0;
CLOSE creature_sel;
LEAVE creature_loop;
end if;
SET new_guid = new_guid + 1;
FETCH creature_sel INTO y_guid, y_id, y_map, y_modelid, y_position_x, y_position_y, y_position_z, y_orientation, y_spawntimesecs, y_curhealth, y_curmana, y_MovementType;
INSERT INTO creature_t(guid, id, map, modelid, position_x, position_y, position_z, orientation, spawntimesecs, curhealth, curmana, MovementType) VALUES(new_guid, y_id, y_map, y_modelid, y_position_x, y_position_y, y_position_z, y_orientation, y_spawntimesecs, y_curhealth, y_curmana, y_MovementType);
OPEN waypoint_sel;
waypoint_loop:LOOP
FETCH waypoint_sel INTO w_id, w_point, w_position_x, w_position_y, w_position_z, w_orientation;
IF done = 1 THEN
SET done = 0;
LEAVE waypoint_loop ;
END IF;
INSERT INTO waypoint_data(id, point, position_x, position_y, position_z, orientation) VALUES (new_guid, w_point, w_position_x, w_position_y, w_position_z, w_orientation);
END LOOP waypoint_loop;
CLOSE waypoint_sel;
END LOOP creature_loop;
CLOSE creature_sel;
END;

I have found solution to this.
To use 2 different continue handlers (to see whether cursor is at end) you need to split code into blocks
BLOCK1: BEGIN
DECLARE done INT DEFAULT 0;
-- First cursor
DECLARE sel CURSOR FOR
SELECT column
FROM table;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN sel;
sel_loop:LOOP
if done = 1 THEN
set done = 0;
CLOSE sel;
LEAVE sel_loop;
end if;
FETCH sel INTO variable1;
-- Do something in loop
-- Start second code-block
BLOCK2: BEGIN
DECLARE done2 INT DEFAULT 0;
-- define second cursor
DECLARE sel2 CURSOR FOR
SELECT column
FROM table
WHERE column = condition;
-- define second handler
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done2 = 1;
-- now you can make second cursor loop and it will finish properly.
OPEN sel2;
sel2_loop:LOOP
FETCH sel2 INTO variable2;
IF done2 = 1 THEN
SET done2 = 0;
LEAVE sel2_loop ;
END IF;
-- do something in second loop
END LOOP waypoint_loop;
CLOSE waypoint_sel;
-- End your code blocks and close first loop / cursor
END BLOCK2;
END LOOP sel_loop;
CLOSE sel;
END BLOCK1

Related

MySQL cursor fetch NULL

Why both my variables output NULL? SELECT part of the cursor is working properly.
CREATE PROCEDURE p2()
BEGIN
# Account table
DECLARE accountid INT;
DECLARE accountname VARCHAR(1000);
# 1. cursor finished/done variable comes first
DECLARE done INT DEFAULT 0;
# 2. the curser declaration and select
DECLARE c_account_id_name CURSOR FOR SELECT
accountid,
accountname
FROM temp.test;
# 3. the continue handler is defined last
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = TRUE;
OPEN c_account_id_name;
SET accountid = 0;
SET accountname = '';
read_loop: LOOP
FETCH c_account_id_name
INTO accountid, accountname;
IF done
THEN
LEAVE read_loop;
END IF;
SELECT accountname;
END LOOP;
END;
Variable and select attribute in cursor can't be the same...it's a MySQL bug.
This will work
DROP PROCEDURE IF EXISTS p2;
DELIMITER $$
CREATE PROCEDURE p2()
BEGIN
# Account table
DECLARE v_accountidsome INT; #pay attention
DECLARE v_accountnameelst VARCHAR(1000); #pay attention
# 1. cursor finished/done variable comes first
DECLARE v_done INT DEFAULT FALSE;
# 2. the cursor declaration and select
DECLARE c_account_id_name CURSOR FOR SELECT
accountid, #pay attention
accountname #pay attention
FROM temp.test;
# 3. the continue handler is defined last
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET v_done = TRUE;
OPEN c_account_id_name;
read_loop: LOOP
FETCH c_account_id_name
INTO v_accountidsome, v_accountnameelst;
IF v_done
THEN
LEAVE read_loop;
END IF;
SELECT v_accountidsome;
SELECT v_accountnameelst;
END LOOP;
CLOSE c_account_id_name;
END $$
DELIMITER ;
CALL p2();
Find more here

populating table in stored procedure, mysql

Can anyone shed light on why this will not loop through the function and populate the temp table? Ive tried a number of things and at best can only get the first value to populate. I'm trying to take a value and a ":" separated string (queried in this procedure) to populate a table so I can reference it in a larger query. The function SPLIT_STR works great on its own but I cant seem to increment value "a" so that it separates ALL values per field per value.
BEGIN
DECLARE platform_val VARCHAR(255);
DECLARE productName_val VARCHAR(255);
DECLARE no_more_rows BOOLEAN;
DECLARE num_rows INT DEFAULT 0;
DECLARE loop_cntr INT DEFAULT 0;
DECLARE str VARCHAR(255);
DECLARE a INT DEFAULT 1;
DECLARE cur1 CURSOR FOR SELECT
ProductName,
ProductPlatforms
FROM Feed
limit 10;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_more_rows = TRUE;
OPEN cur1;
select FOUND_ROWS() into num_rows;
the_loop: LOOP
FETCH cur1
INTO productName_val,
platform_val;
SET str = platform_val;
WHILE a< num_rows DO
SET str=SPLIT_STR(str,":",a);
insert into temp values (productName_val, str);
SET a=a+1;
END WHILE;
END LOOP the_loop;
END
This is the idea I came up with
CREATE PROCEDURE `col2lines`()
BEGIN
declare v_platform,v_productName,v_platformAll varchar(255) default null;
declare v_finished int default 0;
declare curs cursor for select ProductName,ProductPlatforms from Feed limit 10;
declare continue handler for not found set v_finished = 1;
drop temporary table if exists temp_table;
create temporary table temp_table (
productName varchar(255),
platform varchar(255)
);
open curs;
parent: LOOP
fetch curs into v_productName, v_platformAll;
if (v_finished = 1) then LEAVE parent; end if;
child: LOOP
set v_platform = substring_index(v_platformAll, '::', 1);
set #strlen = char_length(v_platform);
if (#strlen > 0) then
insert into temp_table values (v_productName, v_platform);
set v_platformAll = substr(v_platformAll, #strlen + 3);
iterate child;
end if;
LEAVE child;
END LOOP child;
iterate parent;
END LOOP parent;
close curs;
select * from temp_table;
END

Can an inner loop in a mysql procedure use the result of the outer loop

I have tried to create the following procedure in mysql
PROCEDURE fix()
BEGIN
DECLARE event_id_ INT;
DECLARE gate_number INT;
DECLARE l_done INT DEFAULT 0;
DECLARE curs_event_id CURSOR FOR SELECT DISTINCT event_id FROM history_15min;
DECLARE curs_gate_number CURSOR FOR SELECT DISTINCT gate_number
FROM history_15min WHERE event_id =event_id_;
( -- HERE IS THE PROBLEM - event_id_ IS BLANK THERE FOR THE INNER LOOP RETURNS NO RESULTS ....)
DECLARE CONTINUE HANDLER FOR NOT FOUND SET l_done=1;
OPEN curs_event_id;
event_loop : LOOP
FETCH curs_event_id INTO event_id_;
IF l_done=1 THEN LEAVE event_loop;
END IF;
OPEN curs_gate_number;
gate_loop : LOOP
FETCH curs_gate_number INTO gate_number;
IF l_done=1 THEN LEAVE gate_loop;
END IF;
insert into t value ('1');
END LOOP gate_loop;
CLOSE curs_gate_number;
SET l_done=0;
END LOOP event_loop;
CLOSE curs_event_id;
END
Is there a way i can get the result from the first loop to be a variable in the second loop ??
Thank CHill60 , your link had the answer i was looking for
here is the correct way to perform the nested loop :
PROCEDURE fix()
BLOCK1: BEGIN
DECLARE colA_ INT;
DECLARE l_done INT DEFAULT 0;
DECLARE curs_colA CURSOR FOR SELECT DISTINCT colA FROM tableA;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET l_done=1;
OPEN curs_colA;
first_loop : LOOP
FETCH curs_colA INTO colA_;
IF l_done=1 THEN LEAVE first_loop;
END IF;
BLOCK2: BEGIN
DECLARE calB_ INT;
DECLARE l_done INT DEFAULT 0;
DECLARE curs_calB CURSOR FOR SELECT DISTINCT calB FROM tableB WHERE colA = colA_;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET l_done=1;
OPEN curs_calB;
second_loop : LOOP
FETCH curs_calB INTO calB_;
IF l_done=1 THEN LEAVE second_loop;
END IF;
insert into tmp value ('1');
END LOOP second_loop;
CLOSE curs_calB;
SET l_done=0;
END BLOCK2;
END LOOP first_loop;
CLOSE curs_colA;
END BLOCK1

How to use many cursors in a single store procedure in MySQL

I want to create a store procedure that has ability to do multi-tasks. Then it got error message below
Error Code: 1338 Cursor declaration after handler declaration
Please look my store procedure
CREATE PROCEDURE `spTest`(OUT v1 VARCHAR(500), OUT v2 VARCHAR(500))
BEGIN
DECLARE _cur_1 CURSOR FOR
SELECT id
FROM tbl_1;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET _cur1Done = 1;
DECLARE _cur_2 CURSOR FOR
SELECT id
FROM tbl_2;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET _cur2Done = 1;
.......
You may only have one continue handler active in a block. Multiple continue handler's get the Duplicate handler declared in same block issue, even if you get the order of declarations correct ( all cursors first, then the handlers later).
You can work around this by creating a block devoted to a cursor's access. You can place these chunks of code in the same parent block, or in nested loops, or wherever.
DELIMITER $$
CREATE PROCEDURE `spTest`(OUT v1 VARCHAR(500), OUT v2 VARCHAR(500))
BEGIN
declare tbl_1_id int;
declare tbl_2_id int;
declare _cur1Done boolean default false;
declare _cur2Done boolean default false;
DECLARE _cur_1 CURSOR FOR SELECT id FROM tbl_1;
DECLARE _cur_2 CURSOR FOR SELECT id FROM tbl_2;
begin -- dedicated block to fetch from cursor 1 and update its flag
declare continue handler for not found set _cur1Done = TRUE;
fetch _cur_1 into tbl_1_id;
end;
begin -- dedicated block to fetch from cursor 2 and update its flag
declare continue handler for not found set _cur2Done = TRUE;
fetch _cur_2 into tbl_2_id;
end;
END $$
DELIMITER ;
In the manual it says
Cursor declarations must appear before handler declarations and after variable and condition declarations.
Also you can not have multiple continue handlers (how should MySQL know which continue handler is related to which cursor? Unfortunately you can't specify that), unless you nest them, for example like this:
DELIMITER $$
CREATE PROCEDURE `spTest`(OUT v1 VARCHAR(500), OUT v2 VARCHAR(500))
BEGIN
BLOCK1:BEGIN
DECLARE variable1 INT;
DECLARE _cur_1 CURSOR FOR SELECT id FROM tbl_1;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET _cur1Done = 1;
LOOP1: LOOP
FETCH _cur_1 INTO variable1;
IF _cur1Done THEN
CLOSE _cur_1;
LEAVE LOOP1;
END IF;
BLOCK2:BEGIN
DECLARE variable2 INT;
DECLARE _cur_2 CURSOR FOR SELECT id FROM tbl_2;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET _cur2Done = 1;
OPEN _cur_2;
LOOP2: LOOP
FETCH _cur_2 INTO variable2;
IF _cur2Done THEN
CLOSE _cur_2;
LEAVE LOOP2;
END IF;
END LOOP LOOP2;
END BLOCK2;
END LOOP LOOP1;
END BLOCK1;
END $$
DELIMITER ;

Multiple cursors in nested loops in MySQL

I wish to do something which appear a bit complicated in MySQL.
In fact, I wish to open a cursor, do a loop, and in this loop, open a second cursor using the data from the previous fetch to be executed, and re-loop on the results.
DECLARE idind INT;
DECLARE idcrit INT;
DECLARE idindid INT;
DECLARE done INT DEFAULT 0;
DECLARE done2 INT DEFAULT 0;
DECLARE curIndicateur CURSOR FOR SELECT id_indicateur FROM indicateur;
DECLARE curCritereIndicateur CURSOR FOR SELECT C.id_critere FROM critere C where C.id_indicateur=idind;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
set idindid=54;
OPEN curIndicateur;
REPEAT
FETCH curIndicateur INTO idind;
open curCritereIndicateur;
REPEAT
FETCH curIndicateur INTO idcrit;
INSERT INTO SLA_DEMANDE_STATUS (iddemande,idindicateur,indicateur_status,progression) values('0009',idcrit,'OK',10.0);
UNTIL done END REPEAT;
close curCritereIndicateur;
UNTIL done END REPEAT;
CLOSE curIndicateur;
In fact, how to do 'Until done' differently for the two cursors, because you can only declare one handler for SQLSTATE?
If the first ends, the second ends too.
You need to define a new BLOCK inside your 1st cursor loop and use different Declares in that block.
Something like:
BLOCK1: begin
declare v_col1 int;
declare no_more_rows boolean1 := FALSE;
declare cursor1 cursor for
select col1
from MyTable;
declare continue handler for not found
set no_more_rows1 := TRUE;
open cursor1;
LOOP1: loop
fetch cursor1
into v_col1;
if no_more_rows1 then
close cursor1;
leave LOOP1;
end if;
BLOCK2: begin
declare v_col2 int;
declare no_more_rows2 boolean := FALSE;
declare cursor2 cursor for
select col2
from MyOtherTable
where ref_id = v_col1;
declare continue handler for not found
set no_more_rows2 := TRUE;
open cursor2;
LOOP2: loop
fetch cursor2
into v_col2;
if no_more_rows then
close cursor2;
leave LOOP2;
end if;
end loop LOOP2;
end BLOCK2;
end loop LOOP1;
end BLOCK1;
Correct me if I'm wrong, but it looks like what you are trying to do is a bulk insert of records into your "SLA_DEMANDE_STATUS" table. Include every criteria for every indicator found and default it with the values of '0009', 'OK' and 10.0 per each indicator's Criteria ID.
This can all be done in a single SQL-Insert. INSERT INTO ... from a SQL-Select...
Now, if you want to only include a single "id_indicateur" entry, you can add that to the WHERE clause of the select statement.
Notice that my SQL-Select has forced values to correspond with the columns you want to have populated. They will all be inserted to the destination table by the same name. The nice thing about this, you can just run the SQL-SELECT portion just to see the data you would EXPECT to have inserted... if its incorrect, you can adjust it to fit whatever restrictions you want.
insert into SLA_DEMANDE_STATUS
( iddemande,
idindicateur,
indicateur_status,
progression )
SELECT
'0009' iddemande,
c.id_criterere idindicateur,
'OK' indicateur_status,
10.0 progression
FROM
indicateur i;
JOIN critere c
ON i.id_indicateur = c.id_indicateur
you could use loop,and reset the value of the handle,like this:
get_something:loop
open cur;
fetch cur into temp_key;
if no_more_record=1 then
set no_more_record=0;
close cur;
leave get_something;
else
//do your job;
end if;
end loop;
Or redefine the CONTINUE HANDLE:
//...
LOOP1: LOOP
fetch cursor1
into v_col1;
if no_more_rows1 then
close cursor1;
leave LOOP1;
end if;
//...
SET no_more_rows1=false;//That's new
END LOOP LOOP1;
It seems that all select statements inside a loop execute the CONTINUE HANDLE
DECLARE _idp INT;
DECLARE _cant INT;
DECLARE _rec INT;
DECLARE done INT DEFAULT 0;
-- DefiniciĆ³n de la consulta
DECLARE primera CURSOR FOR SELECT dp.id_prod, SUM(dp.cantidad) AS cantidad, pp.receta FROM tm_detalle_pedido AS dp INNER JOIN tm_producto_pres AS pp
DECLARE segunda CURSOR FOR SELECT id_ins, cant FROM tm_producto_ingr WHERE id_pres = _idp;
-- DeclaraciĆ³n de un manejador de error tipo NOT FOUND
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
-- Abrimos el primer cursor
OPEN primera;
REPEAT
FETCH primera INTO _idp, _cant, _rec;
IF NOT done THEN
OPEN segunda;
block2: BEGIN
DECLARE doneLangLat INT DEFAULT 0;
DECLARE _ii INT;
DECLARE i FLOAT;
DECLARE _canti FLOAT;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET doneLangLat = 1;
REPEAT
FETCH segunda INTO _ii,_canti;
IF NOT doneLangLat THEN
IF _rec = 1 THEN
SET i = _canti * _cant;
-- Insertamos
INSERT INTO tm_inventario (id_ins,id_tipo_ope,id_cv,cant,fecha_r)
VALUES (_ii, 2, #id, i, _fecha);
END IF;
END IF;
UNTIL doneLangLat END REPEAT;
END block2;
CLOSE segunda;
END IF;
UNTIL done END REPEAT;
CLOSE primera;