Pass a variable into CURSOR's SELECT Query (MySQL) - mysql

I'm trying the stored procedure below. However, when i pass the actual column name in 'DECLARE cur1' line, the SP returns correct value but when I pass variable name i.e. input parameter (colName), it returns 0. I've added comments in my code below. Is the code correct?
DROP PROCEDURE IF EXISTS test1.checkHardcodedField;
CREATE PROCEDURE test1.checkHardcodedField(IN textValue CHAR(10), colName CHAR(10), OUT counter VARCHAR(100))
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE temp CHAR(16);
DECLARE i INT DEFAULT 0;
-- Issue with this statement, returns incorrect value.
DECLARE cur1 CURSOR FOR SELECT colName FROM data1;
-- This statement works. MSH3 is actual column name
-- DECLARE cur1 CURSOR FOR SELECT MSH3 FROM data1;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur1;
read_loop: LOOP
FETCH cur1 INTO temp;
IF done THEN
LEAVE read_loop;
END IF;
IF temp = textValue THEN
SET i = i + 1;
END IF;
END LOOP;
SET counter = i;
CLOSE cur1;
END;

CREATE PROCEDURE procedure_name (..., fieldname VARCHAR, ...)
BEGIN
CREATE TEMPORARY TABLE tmp (field VARCHAR(10));
SET #sql := CONCAT('INSERT INTO tmp SELECT ', fieldname, ' FROM tablename;');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DROP PREPARE stmt;
BEGIN
DECLARE cur CURSOR FOR SELECT field FROM tmp;
...
END;
DROP TABLE tmp;
END

Related

Can't specify table name on mysql stored procedure

I am mass assigning new id numbers to things in the DB to make room for some stuff at the beginning of each table. I created a procedure that works, but when I try adding input parameters to allow scripting, it can't find the table
delimiter |
CREATE PROCEDURE changeID
( IN in_table_name varchar(64))
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE a,b INT DEFAULT 800000;
DECLARE cur1 CURSOR FOR SELECT id FROM in_table_name ORDER BY id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur1;
read_loop: LOOP
FETCH cur1 INTO b;
IF done THEN
LEAVE read_loop;
END IF;
UPDATE in_table_name SET id = a + 1 where id = b;
SET a = a+1;
END LOOP;
CLOSE cur1;
END;
|
delimiter ;
When I run this using call changeID('users'), I get the error:
[Err] 1146 - Table 'databaseName.in_table_name' doesn't exist
I was hoping to loop through using a simple list of commands like this so it could run unattended instead of manually changing the in_table_name between each execution:
call changeID('users');
call changeID('appointments');
call changeID('groups');
You can't dynamically pass a table name in a query, however, you can concatenate a string and then execute it as a statement. You of course want to be careful and ensure that this data has been sanitized etc. I wasn't able to test this, but something to this effect should get you going.
...
END IF;
SET #Query = CONCAT('UPDATE ',in_table_name,' SET `id` = ',a+1,' WHERE `id`=',b);
PREPARE stmt FROM #Query;
EXECUTE stmt;
...
https://dev.mysql.com/doc/refman/5.7/en/sql-syntax-prepared-statements.html
KChason got me started in the right direction, but I had to take it a little further to get the first part working from tips here: https://forums.mysql.com/read.php?98,138495,138908#msg-138908.
DROP PROCEDURE
IF EXISTS `workingversion`;
delimiter |
CREATE PROCEDURE `workingversion` (IN tableName VARCHAR(100))
BEGIN
DECLARE done INT DEFAULT 0 ;
DECLARE a,
b INT DEFAULT 800000 ;
DROP VIEW IF EXISTS v1;
SET #stmt_text = CONCAT("CREATE VIEW v1 AS SELECT id FROM ", tableName, " ORDER BY id") ;
PREPARE stmt
FROM
#stmt_text ; EXECUTE stmt ; DEALLOCATE PREPARE stmt ;
BEGIN
DECLARE cur1 CURSOR FOR SELECT * FROM v1 ;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000'
SET done = 1 ; OPEN cur1 ;
REPEAT
FETCH cur1 INTO b ;
IF NOT done THEN
SET #Query = CONCAT('UPDATE ',tableName,' SET `id` = ',a+1,' WHERE `id`=',b);
PREPARE stmt FROM #Query;
EXECUTE stmt;
SET a = a+1;
END
IF ; UNTIL done
END
REPEAT
; CLOSE cur1 ;
END ;
END

pass table name as parameter to cursor in mysql stored procedure

While implementing cursor I am unable to pass the table name as parameter to the select query declared in the cursor. How could I pass the table name as parameter to taht query ?
DELIMITER //
Drop PROCEDURE IF EXISTS GetRecord;
CREATE PROCEDURE GetRecord(IN hr int,IN TableName varchar(50))
BEGIN
declare done int DEFAULT 0;
declare ID int DEFAULT 0;
declare name varchar(25);
declare cur1 cursor for SELECT id ,name FROM TableName;
declare continue handler for not found set done=1;
SET #query = concat('update Student set no=no+? where id=?');
PREPARE stmt from #query;
open cur1;
igmLoop: loop
fetch cur1 into ID,name;
SELECT ID;
IF done = 1 THEN
LEAVE igmLoop;
END IF;
SET #id = ID;
SET #HR =hr;
EXECUTE stmt USING #HR,#id;
end loop igmLoop;
close cur1;
DEALLOCATE PREPARE stmt;
END;//
DELIMITER ;

MySQL Cursors fetching null value

Data fetched from cursor is NULL. But number of row my query select id from Projects returning is correct. I have put a select statement in my procedure loop to debug. It's returning null for id.
I have followed this [https://dev.mysql.com/doc/refman/5.5/en/cursors.html]
What's wrong with this code?
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `ReportDiffProDiffFinYear`()
BEGIN
DECLARE id INT;
DECLARE sqlstr varchar(10000) default "select financialYear as 'name' ";
DECLARE done INT DEFAULT FALSE;
DECLARE cur1 CURSOR FOR select id from Projects; /* My CURSOR QUERY*/
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur1;
read_loop: LOOP
FETCH cur1 INTO id;
select id;
IF done THEN
LEAVE read_loop;
END IF;
set sqlstr = concat(sqlstr, ", sum(if(projects_id=",id,",0,1)) ");
END LOOP;
CLOSE cur1;
set #sqlstr = concat(sqlstr," from (select * from WaterPoints where status='Waterpoint Complete') as wp, (select id,financialYear from ProjectDetails) as pd where pd.id=wp.projectDetails_id group by pd.financialYear");
prepare stmt from #sqlstr;
execute stmt;
deallocate prepare stmt;
END
I believe that your variable name id clashes with the query for the cursor, therefore the cursor query fetches the value of the id variable (null), not the value of the id from the projects table. Either change the name of the variable or use projects.id to reference the column in the query.

Serialising data in MySQL trigger

I tried to prototype some sort of generic serialise function in MySQL which I want to use in several triggers for storing the current state of a table row into another table.
The goal is a string with key value pairs of the data, e.g.
id: 3; name: doe; surname: john; age: 25;
whereas the keys are the names of the table columns.
So far, I came up with this:
delimiter //
DROP PROCEDURE IF EXISTS SELECTOR //
CREATE PROCEDURE SELECTOR(
col_name varchar(255),
tbl_name varchar(255),
id integer,
out result text
)
BEGIN
set #q = CONCAT('SELECT ', col_name, ' FROM ', tbl_name, ' WHERE id = ', id );
PREPARE stmt FROM #q;
EXECUTE stmt;
DEALLOCATE PREPARe stmt;
END; //
DROP FUNCTION IF EXISTS SERIALISE //
CREATE FUNCTION SERIALISE(tbl VARCHAR(255), id integer) RETURNS text
BEGIN
DECLARE num_rows INTEGER;
DECLARE i INTEGER;
DECLARE col_name varchar(255);
DECLARE result varchar(255) DEFAULT "";
DECLARE value text;
DECLARE col_names CURSOR FOR
SELECT column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = tbl
ORDER BY ordinal_position;
open col_names;
SELECT FOUND_ROWS() INTO num_rows;
SET i = 1;
the_loop: LOOP
IF i > num_rows THEN
CLOSE col_names;
LEAVE the_loop;
END IF;
FETCH col_names
INTO col_name;
call SELECTOR(col_name, tbl, id, value);
// here the value would be concated as well
SET result = CONCAT(result, col_name, ", ");
SET i = i + 1;
END LOOP the_loop;
return result;
END; //
Sadly, dynamic SQL is not allowed in functions or trigger, so the SELECTOR procedure does not work.
Does anybody have some hints how I could achieve my goal, or is it truly impossible?

Using prepared statements with cursor

I put the cursor declaration in the prepared statement and then executed it, then returns an error #1324 - Undefined CURSOR: getid.
How do I solve this problem?
delimiter ;;
drop procedure if exists test2;;
create procedure test2(table_id VARCHAR(25))
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE id INT;
DECLARE id_new INT;
DECLARE stmt1 VARCHAR(1024);
DECLARE stmt2 VARCHAR(1024);
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
SET #sqltext1 := CONCAT('DECLARE getid CURSOR FOR SELECT entryId FROM ',table_id,' ORDER BY entryId');
PREPARE stmt1 FROM #sqltext1;
EXECUTE stmt1;
SET #id_new = 1;
OPEN getid;
FETCH getid into id;
REPEAT
SET #sqltext2 := CONCAT('UPDATE ',table_id,' SET entryId = ? WHERE entryId = ?');
PREPARE stmt2 FROM #sqltext2;
EXECUTE stmt2 USING #new_id, id;
SET #id_new = #id_new + 1;
FETCH getid into id;
UNTIL done END REPEAT;
CLOSE getid;
END
;;
CALL test2('Test');
Some rules:
All declarations must be at one place in a sequence.
You can't use variable names in cursor declarations.
Handler declarations must be after cursor declarations.
You can't use local variable names (id) as bound parameters for
prepared statements. You can only use session variables (say #_id).
To overcome such problems, you can adopt following solution.
Define a temporary table using the input parameter to the SP.
Now declare the cursor on the same table and use it.
Drop the temporary table created.
Following example should work on your tables.
delimiter $$
drop procedure if exists test2$$
create procedure test2( table_id varchar(25) )
begin
set #temp_query = 'drop temporary table if exists temp_cursor_table';
prepare pst from #temp_query;
execute pst;
drop prepare pst; -- or
-- deallocate prepare pst;
set #temp_table_query='create temporary table temp_cursor_table ';
set #temp_table_query=concat( #temp_table_query, ' select entryId from ' );
set #temp_table_query=concat( #temp_table_query, table_id );
set #temp_table_query=concat( #temp_table_query, ' order by entryId' );
prepare pst from #temp_table_query;
execute pst;
drop prepare pst;
-- now write your actual cursor and update statements
-- in a separate block
begin
declare done int default false;
declare id int;
declare id_new int;
declare stmt1 varchar(1024);
declare stmt2 varchar(1024);
declare getid cursor for
select entryId from temp_cursor_table order by entryId;
declare continue handler for not found set done = 1;
set #id_new = 1;
open getid;
fetch getid into id;
repeat
set #sqltext2 := concat( 'update ', table_id );
set #sqltext2 := concat( #sqltext2, ' set entryId = ? where entryId = ?' );
set #_id = id;
prepare stmt2 from #sqltext2;
execute stmt2 using #new_id, #_id;
set #id_new = #id_new + 1;
fetch getid into id;
until done end repeat;
close getid;
end;
end;
$$
delimiter ;
Now call the procedure with table_id value.
call test2( 'Test' );