MySQL concat - within custom function - not working - mysql

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;

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.

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