MySQL Cursor Fetch not working - mysql

I have the following stored procedure that is meant to implement Dijkstra's shortest path algorithm:
CREATE PROCEDURE `Dijkstras`(IN `pids` VARCHAR(512), IN `startP` VARCHAR(8), IN `endP` VARCHAR(8), OUT `dist` DECIMAL(20,10), OUT `eset` VARCHAR(1024))
BEGIN
DECLARE currentP VARCHAR(4);
DECLARE finished INT DEFAULT 0;
DECLARE pt_from, pt_to int;
DECLARE pt_dist decimal(20,10);
DECLARE done INT DEFAULT 0;
DECLARE cur2 CURSOR FOR
select F.id as `from`, T.id as `to`, dist(F.lat, F.lng, T.lat, T.lng)
as dist
from sampledata F, sampledata T
where F.id < T.id and
find_in_set(convert(F.id, char(10)), pids) and
find_in_set(convert(T.id, char(10)), pids)
order by dist;
DECLARE CONTINUE HANDLER FOR not found SET done = 1;
SET currentP= startP;
SET eset = '';
SET dist = 0;
SET done=0;
OPEN cur2; -- this finds pariwise distances in miles.
REPEAT
FETCH cur2 INTO pt_from, pt_to, pt_dist;
SET dist= dist+pt_dist;
SET eset= CONCAT(eset, ',');
IF(currentP=pt_from OR currentP=pt_to) AND
(IN_SET(pt_from,pids) AND IN_SET(pt_to,pids)) THEN
BEGIN
SET dist= dist+ pt_dist;
SET pids= REMOVE_MEMBER(currentP, pids);
SET eset = concat(eset, ',', concat(pt_from, ':', pt_to));
IF left(eset, 1) = ',' then
SET eset = substring(eset, 2); -- remove extra comma.
END IF;
IF currentP=pt_from THEN
SET currentP=pt_to;
ELSE
SET currentP=pt_from;
END IF;
IF currentP= endP THEN
SET finished= 1;
END IF;
END;
END IF;
UNTIL done
END REPEAT;
CLOSE cur2;
END
My issue is that the cursor isn't working properly. When I fetch the current row into pt_from, pt_to, and pt_dist all I get are NULL values. The sampledata table is properly stored in the database and all the point ids in pids are also in the sampledata table. Plus this EXACT code works for another procedure, but reusing it here isn't working.
Anybody know what I'm doing wrong?

The error was that I passed in the point ids like this '12, 15, 18' with spaces in between. MySQL counts the whitespace when it parses the strings, and the id's in the table were listed without spaces. The correct way to pass in the string set is '12,15,18'.

Related

how does mysql user defined function know a selected row was found?

a MYSQL user defined function selects a row from a table. How does the UDF code determine if the selected row was found in the table?
CREATE FUNCTION snippetFolder_folderPath(folder_id int)
RETURNS varchar(512)
BEGIN
declare vFolder_id int;
declare vParent_id int;
declare vPath varchar(512) default '';
declare vFolderName varchar(256) default '';
set vFolder_id = folder_id;
build_path:
while (vFolder_id > 0) do
/* -------- how to know this select statement returns a row?? ---------- */
select a.parent_id, a.folderName
into vParent_id, vFolderName
from SnippetFolder a
where a.folder_id = vFolder_id;
if vPath = ' ' then
set vPath = vFolderName;
else
set vPath = concat_ws( '/', vFolderName, vPath );
end if ;
set vFolder_id = vParent_id;
end while ;
return vPath;
END
https://dev.mysql.com/doc/refman/8.0/en/select-into.html says:
If the query returns no rows, a warning with error code 1329 occurs (No data), and the variable values remain unchanged.
So you could declare a continue handler on warnings, something like the example from the documentation:
BEGIN
DECLARE i INT DEFAULT 3;
DECLARE done INT DEFAULT FALSE;
retry:
REPEAT
BEGIN
DECLARE CONTINUE HANDLER FOR SQLWARNING
BEGIN
SET done = TRUE;
END;
IF done OR i < 0 THEN
LEAVE retry;
END IF;
SET i = i - 1;
END;
UNTIL FALSE END REPEAT;
END
I'll leave it to you to read the documentation and adapt that example to your table and your loop.
Alternatively, if you're using MySQL 8.0 you can use recursive common table expression:
CREATE FUNCTION snippetFolder_folderPath(vFolder_id int)
RETURNS varchar(512)
BEGIN
DECLARE vPath varchar(512) DEFAULT '';
WITH RECURSIVE cte AS (
SELECT folderName, parent_id, 0 AS height
FROM SnippetFolder WHERE folder_id = vFolder_id
UNION
SELECT f.folderName, f.parent_id, cte.height+1
FROM SnippetFolder AS f JOIN cte ON cte.parent_id = f.folder_id
)
SELECT GROUP_CONCAT(folderName ORDER BY height DESC SEPARATOR '/')
INTO vPath
FROM cte;
RETURN vPath;
END
The recursive CTE result is all the ancestors of the row matching vFolder_id, and then one can use GROUP_CONCAT() to concatenate them together as one string.

MySQL If Statement and Increment

I am having issues with a MySQL If statement that creates a group rank. here is the MySQL Statement:
SELECT EnCode, EnName, QuScore,
#scorerank := IF(#currathlete = EnCode, #scorerank + 1, 1),
#currathlete := EnCode
FROM ranking ORDER BY EnCode, QuScore DESC
It currently gives the following output
'1004277','Ashe','1628','1','1004277'
'1004277','Ashe','1309','1','1004277'
'1004277','Ashe','1263','1','1004277'
'1004277','Ashe','648','1','1004277'
'1004277','Ashe','645','1','1004277'
'1004277','Ashe','1628','1','1004277'
'1015934', 'Sabina', '544', '1', '1015934'
'1015934', 'Sabina', '455', '1', '1015934'
'1015934', 'Sabina', '276', '1', '1015934'
'1015934', 'Sabina', '216', '1', '1015934'
What it should be doing is incrementing each of the '1' numbers by one for each row that has the same code, and then starting from 1 again when it sees a different code number (1004277, then 1015934 in this case)
Any help is appreciated as i have followed a number of examples online using the above method but seem to hit the same issue a this point.
Try this way in stored Procedure:
drop PROCEDURE if EXISTS INCREMENTME;
create PROCEDURE INCREMENTME()
BEGIN
DECLARE OldEnNamevar VARCHAR(10) DEFAULT NULL;
DECLARE done INT DEFAULT FALSE;
DECLARE Encodevar VARCHAR(10);
DECLARE EnNamevar VARCHAR(10);
DECLARE QuScorevar VARCHAR(10);
DECLARE scorerankvar VARCHAR(10);
DECLARE currathalthletevar VARCHAR(10);
DECLARE countcode int(29) DEFAULT(1);
DECLARE counter int(20) default 0;
DECLARE get_cur CURSOR FOR select `Encode`,`EnName`,`QuScore`,`scorerank`,`currathalthlete` from tbl_ranking;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;
drop table if exists temp_temptable;
create TEMPORARY table temp_temptable(Encodevar VARCHAR(50) NULL,EnNamevar VARCHAR(50) NULL,QuScorevar VARCHAR(50) NULL,scorerankvar VARCHAR(50) NULL,currathalthletevar VARCHAR(50) NULL,recordCount int(10) null);
OPEN get_cur;
REPEAT
set counter = counter + 1;
FETCH get_cur INTO Encodevar,EnNamevar,QuScorevar,scorerankvar,currathalthletevar;
if (OldEnNamevar = EnNamevar) THEN
set countcode = countcode +1;
ELSE
if(counter=1) then
set countcode = 1;
ELSE
set countcode = 0;
end if;
end if;
if (OldEnNamevar != EnNamevar) THEN
set countcode = 1;
end if;
if(OldEnNamevar=NULL) then
set countcode = 1;
end if;
insert into temp_temptable (Encodevar,EnNamevar,QuScorevar,scorerankvar,currathalthletevar,recordCount) values(Encodevar,EnNamevar,QuScorevar,scorerankvar,currathalthletevar,countcode);
set OldEnNamevar = EnNamevar;
UNTIL done END REPEAT;
select * from temp_temptable;
drop temporary table if exists temp_temptable;
CLOSE get_cur;
END
call the procedure like this:
call INCREMENTME();
Here's the result:
You have to initialize your variables, otherwise they are null (at least at the beginning of the session, probably not anymore if you run it twice), and your query will give strange results. Try
SELECT EnCode, EnName, QuScore,
#scorerank := IF(#currathlete = EnCode, #scorerank + 1, 1),
#currathlete := EnCode
FROM ranking, (select #currathlete := '', #scorerank := 0) init
ORDER BY EnCode, QuScore DESC

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.

Nested Cursor call is calling inner cursor only once in MYSQL

There are 2 procedures. When tested separately they execute fine.
When first SP calls 2nd SP, this is not called after the first call.
Please help resolve the issue.
First Cursor:
BEGIN
DECLARE vAttendEmpid,vNoOfDays, vempid INT DEFAULT 0;
DECLARE processed BOOLEAN DEFAULT FALSE;
DECLARE curEmp CURSOR FOR Select distinct empid as d1 from rawattendance where DATE_FORMAT( indatetime,'%m') = process_month and DATE_FORMAT( indatetime,'%Y') = process_year order by empid;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET processed = TRUE ;
OPEN curEmp;
loopemp: LOOP
FETCH FROM curEmp INTO vEmpid;
IF processed THEN
CLOSE curEmp;
LEAVE loopemp;
END IF;
--select vEmpid;
CALL sp_attendance(vEmpid,process_month,process_year);
END LOOP loopemp;
END
2nd Cursor.. Nested cursor
BEGIN
DECLARE vInDateTime, vOutDateTime,vTempInDateTime, vTempOutDateTime DATETIME ;
DECLARE vAttendEmpid ,vDiffHr INT DEFAULT 0;
DECLARE eprocessed BOOLEAN DEFAULT FALSE;
DECLARE curAttendance CURSOR FOR Select empid, indatetime ,outdatetime from rawattendance where empid=vEmpid and DATE_FORMAT( indatetime, '%m' ) = process_month and DATE_FORMAT( indatetime, '%Y' ) = process_year
order by indatetime;
OPEN curAttendance;
att_loop:LOOP
FETCH curAttendance INTO vAttendEmpid, vInDateTime,vOutDateTime;
select concat ('In Time 0 ==',vInDateTime, ' out ==', vOutDateTime, ' Empid=',vAttendEmpid);
select 'looping';
IF eprocessed THEN
select 'loop end';
select concat ('In Time 4 ==',vTempInDateTime, ' out ==', vTempOutDateTime, ' Empid=',vAttendEmpid);
SET vDiffHr =TIMESTAMPDIFF(HOUR,vTempInDateTime,vTempOutDateTime);
insert into emp_attendance_processed(empid,in_date_time, out_date_time, workedhr)
values(vAttendEmpid,vTempInDateTime,vTempOutDateTime, vDiffHr);
SET vTempOutDateTime=vOutDateTime;
CLOSE curAttendance;
END IF;
END LOOP att_loop;
END
Try to reset your continue handler variable processed after the inner procedure is called.
SET processed = FALSE;
The Problem is that the HANDLER FOR NOT FOUND is not only executed if the cursor in your first procedure returns no rows, but also if a nested SELECT returns no rows. And as your second procedure contains SELECT statements, I believe one of them is returning an empty set at least one time. If that's not intended, it may have to do with faulty parameters assigned.
OPEN curEmp;
loopemp: LOOP
FETCH FROM curEmp INTO vEmpid;
IF processed THEN
CLOSE curEmp;
LEAVE loopemp;
END IF;
--select vEmpid;
CALL sp_attendance(vEmpid,process_month,process_year);
SET processed = FALSE;
END LOOP loopemp;

Last row of inner cursor fetched twice

I'm new to MySQL, and I have a slight problem.
I have a stored procedure which has 2 cursors, one inside the other.
Problem is that the last row of the inner cursor is always fetched twice. This happens everytime the last row comes in the inner cursor, for each iteration of the outer cursor.
Here is the complete Stored Procedure:
CREATE PROCEDURE MAP_TITLES_TO_SRC_CATEGORIES()
BEGIN
Block1:BEGIN
DECLARE matched_titles_category_id INTEGER DEFAULT 0;
DECLARE tmp_genre_category_id INTEGER DEFAULT 0;
DECLARE index_wanted INT Default 0;
DECLARE genre_string VARCHAR(255);
SET matched_titles_category_id = (SELECT category_id FROM oc_category_description WHERE name='matched_titles' LIMIT 1);
Block2:BEGIN
DECLARE src_cursor_finished INTEGER DEFAULT 0;
DECLARE src_cursor_src_code_value varchar(9) DEFAULT "";
DECLARE src_cursor_genres_value varchar(100) DEFAULT "";
DECLARE src_cursor CURSOR FOR SELECT it.src_id, it.Genres FROM src_table it order by it.Title asc;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET src_cursor_finished = 1;
OPEN src_cursor;
REPEAT
FETCH src_cursor INTO src_cursor_src_code_value, src_cursor_genres_value;
INSERT INTO src_log (log_entry) VALUES (CONCAT('Cursor #1 populated with :: src_cursor_src_code_value: ',src_cursor_src_code_value,' & src_cursor_genres_value: ',src_cursor_genres_value));
Block3:BEGIN
DECLARE products_cursor_finished INTEGER DEFAULT 0;
DECLARE products_cursor_id_value INTEGER DEFAULT 0;
DECLARE products_cursor_isbn_value varchar(9) DEFAULT "";
DECLARE products_cursor CURSOR FOR SELECT prod.product_id, prod.isbn FROM oc_product prod where prod.isbn !='' and prod.sku='1';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET products_cursor_finished = 1;
OPEN products_cursor;
REPEAT
FETCH products_cursor INTO products_cursor_id_value, products_cursor_isbn_value;
INSERT INTO src_log (log_entry) VALUES (CONCAT('Cursor #2 populated with :: products_cursor_id_value: ',products_cursor_id_value,' & products_cursor_isbn_value: ',products_cursor_isbn_value));
SET index_wanted = 0;
IF products_cursor_isbn_value = src_cursor_src_code_value THEN
INSERT INTO src_log (log_entry) VALUES (CONCAT('match entry for prod ',products_cursor_id_value,' in match cat id ',matched_titles_category_id,' BEGIN'));
INSERT INTO oc_product_to_category VALUES (products_cursor_id_value, matched_titles_category_id);
INSERT INTO src_log (log_entry) VALUES (CONCAT('match entry for prod ',products_cursor_id_value,' in match cat id ',matched_titles_category_id,' END'));
genres_loop:LOOP
SET index_wanted=index_wanted+1;
SET genre_string=SPLIT_STR(src_cursor_genres_value,',',index_wanted);
IF genre_string='' THEN
LEAVE genres_loop;
END IF;
SET tmp_genre_category_id = (SELECT category_id FROM oc_category_description WHERE name = genre_string LIMIT 1);
INSERT INTO src_log (log_entry) VALUES (CONCAT('genre entry for prod ',products_cursor_id_value,' and genre cat ID ',tmp_genre_category_id,' BEGIN'));
INSERT INTO oc_product_to_category VALUES (products_cursor_id_value, tmp_genre_category_id);
INSERT INTO src_log (log_entry) VALUES (CONCAT('genre entry for prod ',products_cursor_id_value,' and genre cat ID ',tmp_genre_category_id,' END'));
END LOOP genres_loop;
END IF;
Until products_cursor_finished END REPEAT;
CLOSE products_cursor;
END Block3;
UNTIL src_cursor_finished END REPEAT;
CLOSE src_cursor;
END Block2;
END Block1;
END;
As you can see, I'm logging the data received after every FETCH, and the result I see through this indicates my observed issue.
Any ideas on the bug ?
A test src_cursor_finished must be done immediately after the FETCH command.
But the code tries to fetch from the cursor, then perform many operation (without checking if the fetch was successful), then checks the condition at the end in the UNTIL statement:
DECLARE CONTINUE HANDLER FOR NOT FOUND SET src_cursor_finished = 1;
OPEN src_cursor;
REPEAT
FETCH products_cursor INTO products_cursor_id_value, products_cursor_isbn_value;
-- The condition must be tested HERE:
-- IF products_cursor_finished <> 1 THEN do something
-- or even better:
-- IF products_cursor_finished = 1 THEN LEAVE;
................
..............
..........
............
...................
Until products_cursor_finished END REPEAT;