MySQL cursor fetching same result twice - mysql

I've got a Stored Procedure that checks rows from one table to insert its details into another. I'm using a cursor but I have a big problem: the cursor loops 2 times over the same row. So I get 2 repeated inserts .
Here is the sp code:
IF (SELECT 1 FROM NOVEDADES WHERE LEGAJO_ID = pLEGAJO_ID AND FECHA >= pFECHA AND CONCEPTO_ID != 11 AND CONCEPTO_ID != 13 AND CONCEPTO_ID != 12 LIMIT 1) = 1
THEN
BEGIN
DECLARE vCONCEPTO_ID INT;
DECLARE vMONTO DECIMAL(12,2);
DECLARE vID INT;
DECLARE vDONE INT DEFAULT 0;
DECLARE CURSOR_NOVEDADES CURSOR FOR
SELECT ID
FROM NOVEDADES
WHERE LEGAJO_ID = pLEGAJO_ID
AND FECHA >= pFECHA
AND CONCEPTO_ID != 11
AND CONCEPTO_ID != 13
AND CONCEPTO_ID != 12;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDONE=1;
OPEN CURSOR_NOVEDADES;
SET vDONE = 0;
REPEAT
FETCH CURSOR_NOVEDADES INTO vID;
SELECT CONCEPTO_ID, MONTO INTO vCONCEPTO_ID, vMONTO
FROM NOVEDADES WHERE ID = vID;
INSERT INTO LIQUIDACIONES_DETALLE (LIQUIDACION_ID, CONCEPTO_ID, MONTO)
VALUES(pLIQUIDACION_ID, vCONCEPTO_ID, vMONTO);
UNTIL vDONE END REPEAT;
CLOSE CURSOR_NOVEDADES;
END;
END IF;
variables beggining with "p" are IN parameters, with "v" are common variables.
I must say that the query of the cursor returns only 1 value.
I've tried with LOOP also, but same result.
I've tried "debugging" the procedure inserting some SELECTS and I see the repeated.
Thanks a lot.

On the last iteration through the loop, the fetch is failing. When it does so, you are re-inserting the previous values. Here is one way to fix this:
REPEAT
FETCH CURSOR_NOVEDADES INTO vID;
if ! vdone then
SELECT CONCEPTO_ID, MONTO INTO vCONCEPTO_ID, vMONTO
FROM NOVEDADES WHERE ID = vID;
INSERT INTO LIQUIDACIONES_DETALLE (LIQUIDACION_ID, CONCEPTO_ID, MONTO)
VALUES(pLIQUIDACION_ID, vCONCEPTO_ID, vMONTO);
end
UNTIL vDONE END REPEAT;

Related

mysql. Stored Procedure Error 1328

This is really baffling me. I am converting a simple procedure from informix into mysql. What it basically does is tell me what the next event is from an event table and a calendar table. In informix the procedure is simple.
FOREACH
SELECT date,weekno,event
INTO l_date,l_week,l_event
FROM event,calendar
WHERE dayno = dayno
AND date = l_today
AND start >= l_now
UNION
SELECT date,weekno,event
FROM event,calendar
WHERE dayno = dayno
AND date > l_today
UNION
SELECT TODAY,9999,9999
FROM event,calendar
WHERE dayno = dayno
AND event = (SELECT MAX(event) FROM event)
ORDER BY 3
if l_event = 9999 then <error> end if;
EXIT FOREACH
END FOREACH
So basically the query finds the next event and returns it. l_today and l_event are parameters that are passed. So on to the mysql version.
looper: BEGIN
DECLARE curs1 CURSOR FOR
SELECT CONCAT("SELECT date, weekno, event FROM event INNER JOIN calendar ON dayno = dayno",
" WHERE date = '", lv_today ,"' AND start >= '", lv_time ,"'",
" UNION SELECT date, weekno, event FROM event INNER JOIN calendar ON dayno = dayno WHERE date > '", lv_today ,"'",
" UNION SELECT DATE(NOW()) AS date, 9999 AS weekno, 9999 AS event FROM event INNER JOIN calendar ON dayno = dayno",
" WHERE (SELECT MAX(event) FROM event) ORDER BY event ");
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done := TRUE;
OPEN curs1;
loop1: LOOP
FETCH curs1 INTO ldate, lweek, levent;
SELECT ldate, lweek, levent;
LEAVE looper;
END LOOP loop1;
END;
I haven't checked that the rest of the methodology works because I get this error:
Incorrect number of FETCH variables.
Does this mean that I have declare a different variable for each of the query returns? I am new to mysql. If this is the case what would be the best way to solve this conundrum? I have also changed to column and table names.
Many thanks
looper: BEGIN
DECLARE curs1 CURSOR FOR
SELECT eve_date, dia_weekno, eve_event
FROM game_event
INNER JOIN stan_calendar ON eve_abs_dayno = dia_abs_dayno
WHERE eve_date = lv_today
AND eve_start >= lv_time
UNION
SELECT eve_date, dia_weekno, eve_event
FROM game_event
INNER JOIN stan_calendar ON eve_abs_dayno = dia_abs_dayno
WHERE eve_date > lv_today
UNION SELECT DATE(NOW()) AS eve_date, 9999 AS dia_weekno, 9999 AS eve_event
FROM game_event
INNER JOIN stan_calendar ON eve_abs_dayno = dia_abs_dayno
WHERE (SELECT MAX(eve_event) FROM game_event) ORDER BY eve_event;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done := TRUE;
OPEN curs1;
curs_loop: LOOP
FETCH curs1 INTO lv_date, lv_week, lv_event;
SELECT lv_date, lv_week, lv_event;
LEAVE looper;
CLOSE curs1;
END LOOP curs_loop;
Thank you for answering my question, I have put it back to how I thought it was... and it now works. Here is the loop in full.
https://dev.mysql.com/doc/refman/5.7/en/fetch.html The number of columns retrieved by the SELECT statement must match the number of output variables specified in the FETCH statement so yes but in fact you are only selecting 1 concatenated string - I think your first problem is with the select syntax which doesn't need the concat, the brackets or the quotes (unless you are trying to create a prepared statement for some reason - and even if you were I doubt if the code would be correct)
Simple Cursor
DROP PROCEDURE IF EXISTS EC;
DELIMITER $$
CREATE PROCEDURE `EC`(
IN `inemp_no` varchar(255)
)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
LOOPER:begin
DECLARE done INT DEFAULT FALSE;
declare ename varchar(20);
declare esalary int default 0;
declare emp_cursor CURSOR FOR
SELECT last_name,salary FROM employees where emp_no= inemp_no ;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
open emp_cursor;
read_loop: loop
fetch emp_cursor into ename,esalary;
if done then leave read_loop; end if;
insert into debug_table (msg) values(concat('employee:',ename,' earns:',esalary));
end loop;
close emp_cursor;
end $$
DELIMITER ;
MariaDB [sandbox]> truncate table debug_table;
Query OK, 0 rows affected (0.22 sec)
MariaDB [sandbox]> call ec(2);
Query OK, 0 rows affected (0.03 sec)
MariaDB [sandbox]> select * from debug_table;
+----+--------------------------+------+
| id | msg | MSG2 |
+----+--------------------------+------+
| 1 | employee:BBB earns:39500 | NULL |
+----+--------------------------+------+
1 row in set (0.00 sec)

Delete duplicate records according to difference of seconds

I have records example:
Orden| date_record
-----|-------------------
2334 | 2017-05-17 05:00:30
2334 | 2017-05-17 05:00:50
2334 | 2017-05-17 05:10:30
3421 | 2017-05-17 07:09:40
I need to delete records that have duplicate ids only where the difference in date_record is less than 30 seconds.
Thanks
I create a table for your data Table1 and include a litle more data.
I create a temporal table newTable
include a row_number
include a field to mark for deletion
Create a function to debug you dont need it
Create a function process_time to check for deletion
Create a cursor to loop for the table and mark each row when second diff
is < 30
If a row is already marked <> 0 doesn't count for the next row calculations
The final step is delete from original table using the newTable marked rows
SQL DEMO
CREATE PROCEDURE process_time()
BEGIN
DECLARE int_id INTEGER DEFAULT 0;
DECLARE int_prev_id INTEGER DEFAULT 0;
DECLARE dtt_date datetime;
DECLARE v_finished INTEGER DEFAULT 0;
DECLARE newTable_cursor CURSOR FOR
SELECT `ID`, `date_record`
FROM newTable
ORDER BY `ID`, `date_record`;
-- declare NOT FOUND handler
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET v_finished = 1;
OPEN newTable_cursor;
get_dates: LOOP
FETCH newTable_cursor INTO int_id, dtt_date;
IF v_finished = 1 THEN
LEAVE get_dates;
END IF;
IF int_prev_id = int_id THEN
SELECT #dd := TIMESTAMPDIFF(SECOND, MAX(`date_record`), dtt_date) as d -- MAX(`date_record`)
FROM newTable
WHERE `ID` = int_id
AND `date_record` < dtt_date
AND delete_mark = 0;
ELSE
SET int_prev_id := int_id;
SET #dd := null;
END IF;
IF #dd < 30 THEN
UPDATE newTable
SET `delete_mark` = #dd
WHERE `ID` = int_id
and `date_record` = dtt_date;
END IF;
END LOOP get_dates;
CLOSE newTable_cursor;
END;
OUTPUT

1172 - Result consisted of more than one row in mysql

How can I solve this problem (Result consisted of more than one row in mysql)
DROP PROCEDURE IF EXISTS `doMarksApplication`;
CREATE PROCEDURE `doMarksApplication`(
in kuser varchar(20),
out idpro int(11))
SP:BEGIN
declare no_more_rows int default FALSE;
declare total_marks decimal(10,2) default 0;
declare idfor int(11) default 0;
declare sskod int(5) default getCurSession();
declare bdata int(5) default 0;
declare nopmh varchar(20);
# Data PB [Permohonan Baru] DM [Proses Pemarkahan]
declare cur1 cursor for
select ind_nopmh from pinduk
left join pprses on pro_nopmh = ind_nopmh
where ind_sskod = sskod and
concat(pro_stats,pro_statp) in ('PB','DM') and
not exists (select mar_idnum from pmrkah where mar_nopmh = ind_nopmh)
order by ind_nopmh;
declare continue handler for not found set no_more_rows = TRUE;
begin
select count(ind_nopmh) into bdata
from pinduk
left join pprses on pro_nopmh = ind_nopmh
where ind_sskod = sskod and
concat(pro_stats,pro_statp) in ('PB','DM') and
not exists (select mar_idnum from pmrkah where mar_nopmh = ind_nopmh);
end;
begin
select count(for_idnum) into idfor from xkod_markah_00_formula
where for_stats = 'A' and
curdate() between for_tkhdr and for_tkhhg;
end;
if idfor = 1 and sskod <> 0 then
begin
select for_idnum into idfor from xkod_markah_00_formula
where for_stats = 'A' and
curdate() between for_tkhdr and for_tkhhg;
end;
begin
insert into pprmar
(pma_tkmla,pma_msmla,pma_puser,pma_sskod,pma_idfor,pma_bdata)
values
(curdate(),curtime(),kuser,sskod,idfor,bdata);
end;
begin
select last_insert_id() into idpro;
end;
open cur1;
LOOP1:loop
fetch cur1 into nopmh;
if no_more_rows then
close cur1;
leave LOOP1;
end if;
begin
call getMarksAnakPerak(nopmh,#total_perak);
call getMarksAkademik(nopmh,#total_akdmk);
call getMarksSosioekonomi(nopmh,#total_sosio);
end;
set total_marks = #total_perak + #total_akdmk + #total_sosio;
begin
insert into pmrkah
(mar_idpro,mar_nopmh,mar_idfor,mar_perak,mar_akdmk,mar_sosio,mar_total)
values
(idpro,nopmh,idfor,#total_perak,#total_akdmk,#total_sosio,total_marks);
end;
begin
update pprses
set pro_stats = 'D',
pro_statp = 'M',
pro_tkmsk = curdate(),
pro_msmsk = curtime(),
pro_kuser = kuser
where pro_nopmh = nopmh;
end;
end loop;
begin
update pprmar
set pma_tktmt = curdate(),
pma_mstmt = curtime()
where pma_idnum = idpro;
end;
end if;
END;
i have been programming in mysql for 15 years and this is easily the most confusing stored procedure i have ever seen.
None the less, one possible place for your issue is here
select for_idnum into idfor from xkod_markah_00_formula
where for_stats = 'A' and
curdate() between for_tkhdr and for_tkhhg;
I know it does not seem to be the reason but without knowing the content of the other three stored procedures you are calling this is the only candidate. You should add a limit 1 to it, and to every select into statement that reads from a table (i.e. not a sum() or a count() etc...) as that would always have the potential to cause the error you are seeing.
select for_idnum into idfor from xkod_markah_00_formula
where for_stats = 'A' and
curdate() between for_tkhdr and for_tkhhg limit 1;
In addition, you should comment out the three stored procedure calls and see if the error goes away. My guess is that the issue is one of those stored procedures due to a select into similar to above has more than one row in the result set but does not use limit 1 and does not filter properly.

MYSQL Stored Procedure Issues

I am writing a MySQL Stored Procedure for the first time, and I am running into an issue - I think with the Handler Code. Basically, I want this code to update all rows in the pps_users table, but for some reason I am hitting the 'finished condition' for the handler after only two rows are fetched.
I tried the same thing with the REPEAT syntax and got the same result. If I just run the cursor query I correctly get the 10,000 records I expect, but when I run the whole thing as is, I hit the finished code after only 1 or 2 records.
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `changeNFLFavTeams`()
BEGIN
DECLARE favNFLTeam varchar(100) DEFAULT "";
DECLARE favNCAATeam varchar(100) DEFAULT "";
DECLARE v_finished INTEGER DEFAULT 0;
DECLARE user_id bigint(20);
DECLARE fullNameOfTeam varchar(100) DEFAULT "";
DECLARE update_favs CURSOR FOR select id, favorite_nfl_team from pps_users WHERE favorite_nfl_team is not null;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_finished = 1;
OPEN update_favs;
updaterecord: LOOP
FETCH update_favs INTO user_id, favNFLTeam;
select user_id, favNFLTeam as "Test";
if v_finished = 1
then
select "finished" as "finished";
LEAVE updaterecord;
end if;
select full_name into fullNameOfTeam
from teams t
inner join display_names dt on dt.entity_id = t.id
and dt.entity_type = 'teams'
and dt.first_name = favNFLTeam
and team_key like 'l.nfl.com%' LIMIT 1;
select user_id, fullNameOfTeam AS "BeforeUpdate";
IF fullNameOfTeam != ''
THEN
-- here for whatever_transformation_may_be_desired
-- Find the Full name for the record they chose
UPDATE pps_users p
SET favorite_nfl_team = fullNameOfTeam
WHERE user_id = p.id;
ELSE
SELECT 'A' AS 'A'; -- no op
END IF;
end loop updaterecord;
CLOSE update_favs;
END
This is because if your SELECT full_name into fullNameOfTeam... query returns no rows, then it will set v_finished to 1. That, apparently, happens early on, and forces an exit from the main loop.
The key is to realize that the CONTINUE HANDLER for NOT FOUND does not apply to the cursor alone.
You should either put the secondary query into its own BEGIN..END block with its own CONTINUE handler, or (easier) just set v_finished = 0 after the SELECT full_name into fullNameOfTeam... statement.

MySQL Stored Procedures : cursor declaration

Sorry for the vague title, here is my problem. I have stored procedures for DB2 that i try to convert for MySQL. I'd like to know if i can write the SELECT statement in the cursor declaration as a string variable. For example with DB2 i have this :
(...)
-- Declare cursors
DECLARE c_very_init CURSOR WITH RETURN FOR s_very_init;
DECLARE c_date CURSOR WITH RETURN FOR s_date;
DECLARE CONTINUE HANDLER FOR not_found
SET at_end = 1;
-- In case the_date is 0, retrieve the first date
IF the_date = 0 THEN
SET sql_end_date = '
SELECT DATE
FROM ACCOUNTS
WHERE REF = ''' || the_ref || '''
ORDER BY ID ASC FETCH FIRST 1 ROWS ONLY';
PREPARE s_date FROM sql_end_date;
OPEN c_date;
FETCH FROM c_date INTO data_ins;
SET the_last_date = data_ins;
CLOSE c_date;
ELSE
SET the_last_date = the_date;
END IF;
-- Get the 'very' initial value
SET sql_very_init = '
SELECT in, out
FROM MOVEMENTS
WHERE REF = ''' || the_ref || '''
AND DATE < ' || the_last_date;
PREPARE s_very_init FROM sql_very_init;
OPEN c_very_init;
FETCH FROM c_very_init INTO dare, avere;
-- Loop through the results
(...)
I declare a c_very_init cursor, but at the time of the cursor declaration in the SP i still don't know the full select statement because i need to fetch (if necessary) the the_last_date value. It seems i can't do this :
DECLARE c_very_init CURSOR WITH RETURN FOR s_very_init;
with MySQL, the syntax being with the statement directly in the declaration :
DECLARE c_very_init CURSOR FOR SELECT blaablaa...;
Am i wrong?
Thank you.
fabien.
No, you cannot declare cursors in this way. But if 'the_ref' is a variable, you could do it like this -
...
DECLARE the_ref INT DEFAULT 10;
DECLARE cur1 CURSOR FOR SELECT column1 FROM table1 WHERE column1 = the_ref;
...