Use variable when declaring cursor - mysql

I want to pass the parameter to the procedure and use it for the table name on declaring cursor. The following code returns an error message: #1146 - Table 'db.table_id' doesn't exist.
How do I use the parameter when declaring cursor?
Thanks
delimiter ;;
drop procedure if exists reset_id;;
create procedure reset_id(table_id VARCHAR(25))
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE id INT;
DECLARE id_new INT;
DECLARE getid CURSOR FOR SELECT entryId FROM table_id ORDER BY entryId;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
SET #id_new = 1;
OPEN getid;
FETCH getid into id;
REPEAT
UPDATE table_id SET entryId = #id_new WHERE entryId = id;
SET #id_new = #id_new + 1;
FETCH getid into id;
UNTIL done END REPEAT;
CLOSE getid;
END
;;
CALL reset_id('Test');
After modifying the procedure, still 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');

The table name has to be specified in the SQL text; it cannot be a variable.
To accomplish what you are trying to do, you are going to need to dynamically create a string that contains the SQL text you want to execute.
To prepare the statement from arbitrary string:
SET #sqltext := CONCAT('UPDATE ',table_id,' SET entryId = ? WHERE entryId = ?');
PREPARE stmt FROM #sqltext;
Note that the table_id value is getting incorporated into a string variable, and then the PREPARE statement is (essentially) turning that string into an actual SQL statement.
To execute the prepared statement and supply values for the bind variables, you'd do something like this:
EXECUTE stmt USING #new_id, #id;
You can re-execute a prepared statement multiple times, without needing to prepare it again. So, the PREPARE would be done before your loop, the EXECUTE can be done inside the loop.
Once you are finished with the statement, following the loop, the best practice is to deallocate the statement like this:
DEALLOCATE PREPARE stmt;
NOTE:
The restriction about the table name not being a variable actually applies to all identifiers in a SQL statement, including names of tables, views, columns, functions, etc. Those all have to be literals in the SQL text, just like the reserved keywords do.

Related

MySQL - Pass input parameter into Cursor query

Is it possible to pass input parameter into Cursor SELECT statement WHERE clause?
For some reason I think it isn't working.
I'm trying to pass _TAG and _ITEM_NAME into where clause.
DECLARE cursor_test cursor for SELECT itemid FROM items WHERE key_ LIKE "sometext_#_TAG_sometext_#_ITEM_NAME" AND STATUS = '0';
Here is the the Stored procedure:
DELIMITER //
CREATE PROCEDURE getSomething(IN _HOSTNAME VARCHAR(20), _TAG VARCHAR(20), _ITEM_NAME VARCHAR(50))
BEGIN
declare FINISHED BOOL default false;
DECLARE cursor_test cursor for SELECT itemid FROM items WHERE hostid = #_HOSTID AND key_ LIKE "sometext_#_TAG_sometext_#_ITEM_NAME" AND STATUS = '0';
DECLARE CONTINUE HANDLER for not found set FINISHED := true;
SET #HOSTNAME = _HOSTNAME;
PREPARE STMT1 FROM
"SELECT hostid INTO #_HOSTID FROM hosts WHERE NAME = ?";
EXECUTE STMT1 USING #HOSTNAME;
DEALLOCATE PREPARE STMT1;
open cursor_test;
SET #TOTAL_VALUE := 0;
loop_itemid: loop
fetch cursor_test into _ITEMID;
SELECT _ITEMID;
if FINISHED then
leave loop_itemid;
end if;
SET #TOTAL_VALUE := #TOTAL_VALUE + (SELECT value from history_uint WHERE itemid = _ITEMID ORDER BY clock DESC LIMIT 1);
end loop loop_itemid;
SELECT #TOTAL_VALUE;
close cursor_test;
END //
Thanks to akina's comment. Using CONCAT in where condition worked.
WHERE key_ LIKE CONCAT('sometext_', _TAG, '_sometext_', _ITEM_NAME)

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

MYSQL variable with CURSOR and FETCH

I wanted to use table names and run a statement with the table name as variable. I used cursor/fetch but when I run a statement with the variable it is not using the value of the variable but just seems to use the variable_name itself. I have seen example with concat where another variable was defined but what if I just wanted to reference the table name in a COMMAND?
DELIMITER $$
DROP PROCEDURE IF EXISTS test $$
CREATE PROCEDURE test()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE v_table_name TEXT;
DECLARE cur1 CURSOR FOR
SELECT TABLE_NAME FROM information_schema.tables where table_schema = 'rt';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done= TRUE;
OPEN cur1;
myloop: loop
FETCH cur1 INTO v_table_name;
IF done THEN
LEAVE myloop;
END IF;
COMMAND table v_table_name;
END loop;
close cur1;
END $$
If by COMMAND you mean you want to use the value of a variable as an identifier in another SQL statement... you may be able to make use of a prepared statement (in the context of a MySQL stored program).
Reference: https://dev.mysql.com/doc/refman/5.6/en/sql-syntax-prepared-statements.html
As a trivial example of what that might look like:
SET #sql = CONCAT('select * from `',v_table_name,'` limit 1');
PREPARE stmt1 FROM #sql;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
SET #sql = NULL;
Note that this approach is not safe from SQL Injection vulnerabilities.
If that's not what you are looking for, I'm at a loss. I don't understand what you are referring to as a COMMAND.

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' );

MySQL Error 1064 : Stored procedure

thanks, It's work
It was on this 2 line :
SET valueName = CONCAT(valueName, ' ,', _valueSplit);
SET valueValue = CONCAT(valueValue,' ,', json(_entryData, _valueSplit));
I have declared the variable, but at NULL so CONCAT return NULL and the query go on NULL to
thanks to Devart for helping me
the post :
when I try to use my Stored Procedure I have this error
call _extract() Error Code: 1064. You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'NULL' at line 1
but I see nothing in the procedure
this my procedure, but no instruction NULL on it
CREATE PROCEDURE _extract()
BEGIN
DECLARE _entryType VARCHAR(45);
DECLARE _entryData VARCHAR(1024);
DECLARE _entryTime BIGINT(20);
DECLARE no_more_rows BOOLEAN;
DECLARE num_rows INT DEFAULT 0;
DECLARE entryCursor CURSOR FOR SELECT entryValue, entryTime FROM TrackingEntry;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_more_rows = TRUE;
OPEN entryCursor;
select FOUND_ROWS() into num_rows;
mainLoop: LOOP
FETCH entryCursor INTO _entryData, _entryTime;
IF no_more_rows THEN
CLOSE entryCursor;
LEAVE mainLoop;
END IF;
SET _entryType = json(_entryData, "type");
CALL split_string(json(_entryData, "data"), ",");
CALL _extractJson(_entryType, _entryData);
END LOOP mainLoop;
END$$
_extractJson procedure :
the next part of the extration of the data
CREATE PROCEDURE _extractJson(`_entryType` VARCHAR(255))
BEGIN
DECLARE _valueSplit VARCHAR(255);
DECLARE valueName VARCHAR(255);
DECLARE valueValue VARCHAR(255);
DECLARE split_no_more_rows BOOLEAN;
DECLARE split_num_rows INT DEFAULT 0;
DECLARE splitCursor CURSOR FOR SELECT SQL_CALC_FOUND_ROWS _value FROM SplitValues;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET split_no_more_rows = TRUE;
OPEN splitCursor;
select FOUND_ROWS() into split_num_rows;
dataLoop: LOOP
FETCH splitCursor INTO _valueSplit;
IF split_no_more_rows THEN
CLOSE splitCursor;
LEAVE dataLoop;
END IF;
SET valueName = CONCAT(valueName, ' ,', _valueSplit);
SET valueValue = CONCAT(valueValue,' ,', json(_entryData, _valueSplit));
END LOOP dataLoop;
SET #query = CONCAT('INSERT INTO ',_entryType, ' (',valueName,') VALUES (',valueValue,')' );
PREPARE stmt FROM #query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
--end stuffs here
END$$
to explain what i want to do
some Data are stored in trackingEntry, each row contain information about what the user do (in a social game) and where he come from (action, referer, and some other value)
theses data are stored in a Json format like that :
{
"type" : "tableName",
"data" : "row1,row2,row3",
"row1" : "value",
"row2" : "value",
"row3" : "value"
}
the type of the data (the action (connection, publish on wall)) it's a table's name of one of our dashboard application
The "data" is the list of the available datas
and after we have the Datas
The problem can be with a prepared INSERT statement, it seems that one of the variables is NULL. Try to comment prepared statements and select #query, you will see the problem -
SET #query = CONCAT('INSERT INTO ',_entryType, ' (',valueName,') VALUES (',valueValue,')' );
SELECT #query;
-- PREPARE stmt FROM #query;
-- EXECUTE stmt;
-- DEALLOCATE PREPARE stmt;
You have to add SQL_CALC_FOUND_ROWS in the select statement, so that the FOUND_ROWS() function works.
So change this line
DECLARE entryCursor CURSOR FOR SELECT entryValue, entryTime FROM TrackingEntry;
like this
DECLARE entryCursor CURSOR FOR SELECT SQL_CALC_FOUND_ROWS entryValue, entryTime FROM TrackingEntry;
You can read more about it here.