MySQL Stored Procedures : cursor declaration - mysql

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;
...

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.

Can I use subquery in a user-defined function

I try to use subquery in mysql custom user-defined function I get an error so could u help me with one example.
Here is my code:
CREATE DEFINER=`root`#`localhost` FUNCTION `findsubName`(counts INT)
RETURNS varchar(255) CHARSET utf8
BEGIN
DECLARE result VARCHAR(500) DEFAULT NULL;
DECLARE v_name VARCHAR(200);
DECLARE finished INT(1) DEFAULT 0;
DECLARE my_cursor CURSOR FOR
SELECT id, (SELECT t_name FROM ajctb_titles b WHERE a.jt_id=b.t_id)
as tableName FROM ajctb_vacancies a limit counts;
DECLARE CONTINUE HANDLER
FOR NOT FOUND
SET finished = 1;
OPEN my_cursor;
calc_change: LOOP
FETCH my_cursor INTO v_name;
IF finished THEN
LEAVE calc_change;
END IF;
IF result<>'' THEN
SET result = CONCAT_WS(',',result,v_name);
ELSE
SET result = v_name;
END IF;
END LOOP calc_change;
CLOSE my_cursor;
RETURN result;
END
Error message:
Error Code: 1328. Incorrect number of FETCH variables
Error message: Error Code: 1328. Incorrect number of FETCH variables
Error messages attempt to tell you what the problem is. It is in the FETCH. Looking at the documentation:
13.6.6.3 Cursor FETCH Syntax
FETCH [[NEXT] FROM] cursor_name INTO var_name [, var_name] ...
This statement fetches the next row for the
SELECT statement associated with the specified cursor (which must be
open), and advances the cursor pointer. If a row exists, the fetched
columns are stored in the named variables. The number of columns
retrieved by the SELECT statement must match the number of output
variables specified in the FETCH statement.
https://dev.mysql.com/doc/refman/8.0/en/fetch.html
2 columns in your query:
SELECT
id
, (
SELECT
t_name
FROM ajctb_titles b
WHERE a.jt_id = b.t_id
)
AS tableName
means 2 variables are needed the FETCH
It hasn't even attempted the subquery yet.
Regarding that correlated subquery it could be a problem. When you use a subquery in the select clause like this it MUST return no more then one value. So you should use limit 1 if you continue with that subquery.
That subquery can be replaced with a join. e.g.
SELECT
id
, b.t_name AS tableName
FROM ajctb_vacancies a
LEFT JOIN ajctb_titles b ON a.jt_id = b.t_id
You may want to use an INNER JOIN if you must always have a non-null tablename returned.

Convert postgresql trigger to mysql trigger

I'm trying to transpose a postgres trigger to a mysql trigger. It automatically adds fields to the row according to the date added
CREATE FUNCTION convert_date ()
RETURNS trigger
AS $$
declare
date_min DATE;
date_max DATE;
temp_year INTEGER;
begin
SELECT SUBSTRING(NEW."dc_date_label",0,5)::integer
INTO temp_year;
SELECT date(temp_year || '-01-10')
INTO date_min;
SELECT date(temp_year +1 || '-09-30')
INTO date_max;
NEW."dc_date_start" = date_min;
NEW."dc_date_end" = date_max;
RETURN new;
end;
CREATE TRIGGER trig_b_i_compute_date()
BEFORE INSERT
ON campaigns
FOR EACH ROW
EXECUTE PROCEDURE convert_date();
This is what i've done on mysql :
DELIMITER //
CREATE TRIGGER trig_b_i_compute_date
BEFORE INSERT ON campaigns
FOR EACH ROW
BEGIN
DECLARE date_min DATE;
DECLARE date_max DATE;
DECLARE temp_year INTEGER;
SET temp_year = SELECT CONVERT( SUBSTRING(NEW.dc_date_label,1,5), UNSIGNED INTEGER) ;
SET date_min = SELECT CONVERT( CONCAT(temp_year,'-01-10'), DATE);
SET date_max = SELECT CONVERT( CONCAT(temp_year + 1, '09-30'), DATE);
SET NEW.dc_date_start = date_min;
SET NEW.dc_date_end = date_max;
END;
//
DELIMITER ;
However I get an error :
MySQL server version for the right syntax to use near 'SELECT CONVERT( SUBSTRING(NEW.dc_date_label,1,5), UNSIGNED INTEGER) ;
What is wrong with the procedure ?
If you use SELECT in a SET statement, you need to put it in parentheses:
SET temp_year = (SELECT ...);
But in your case you don't need a SELECT and you can just skip it:
SET temp_year = CONVERT(...);
You can also use the SELECT INTO syntax in MySQL:
SELECT CONVERT(...) INTO temp_year;
And there is no need to declare date_min and date_max. Also no need to cast everything explicitly. Your trigger body could be:
DECLARE temp_year INTEGER;
SET temp_year = CONVERT( SUBSTRING(NEW.dc_date_label,1,5), UNSIGNED);
SET NEW.dc_date_start = CONCAT(temp_year, '-01-10');
SET NEW.dc_date_end = CONCAT(temp_year + 1, '-09-30');
I don't know how dc_date_label looks like, and why the year should be 5 characters long. So I kept the year extraction as it is. But if it's a DATE, DATETIME or TIMESTAMP, you can just use the YEAR function:
SET temp_year = YEAR(NEW.dc_date_label);
And since it's much shorter, you could also use it inline and skip the temp_year variable:
SET NEW.dc_date_start = CONCAT(YEAR(NEW.dc_date_label), '-01-10');
SET NEW.dc_date_end = CONCAT(YEAR(NEW.dc_date_label) + 1, '-09-30');
And last one: Remove the semicolon after END. It might work, but it doesn't belong there.

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;

MySQL concat - within custom function - not working

I try to do this
CREATE FUNCTION getOneCentOrderIds (s text) RETURNS text
BEGIN
DECLARE no_more_orders, ent_id INT default 0;
DECLARE ids text;
DECLARE orders_cur CURSOR FOR SELECT entity_id FROM sales_flat_order WHERE total_due = 0.01;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_more_orders = 1;
OPEN orders_cur;
FETCH NEXT FROM orders_cur INTO ent_id;
REPEAT
SET ids = CONCAT(ids, ', ', ent_id);
FETCH orders_cur INTO ent_id;
UNTIL no_more_orders END REPEAT;
CLOSE orders_cur;
RETURN ids;
END$
but I get null when I execute the function.
If I simply remove concat and leave SET ids = ent_id I get the last id in cursor, as expected.
How should I do the concatenation ?
The concat() function returns NULL if any of its arguments are NULL. Try
DECLARE ids text DEFAULT '';
which will make sure the first call to CONCAT has no NULL arguments.
Instead of creating a function, the above can simple be done in a query as
SELECT group_concat(entity_id) FROM sales_flat_order WHERE total_due = 0.01;