Loop over table and call stored procedure - mysql

Right now, i am trying to loop over a temporary table, that an import process created. I've tried using a cursor, but the tablename must be passed to the stored procedure.
CREATE PROCEDURE `do_update`(tablename VARCHAR(100))
BEGIN
DECLARE done BOOLEAN DEFAULT FALSE;
DECLARE c_ean VARCHAR(20);
DECLARE c_sku VARCHAR(20);
DECLARE c_mpn VARCHAR(20);
DECLARE c_manufacturerName VARCHAR(100);
DECLARE c_manufacturerUniqueId VARCHAR(100);
DECLARE c_images TEXT;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done := TRUE;
-- move declare cursor into sql to be executed
SET #sqlstatement = CONCAT('DECLARE import_cursor CURSOR FOR SELECT ean, sku, mpn, manufacturerName, manufacturerUniqueId, images FROM ', tablename);
PREPARE stmt FROM #sqlstatement;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
OPEN import_cursor;
importloop: LOOP
FETCH import_cursor INTO c_ean, c_sku, c_mpn, c_manufacturerName, c_manufacturerUniqueId, c_images;
IF done THEN
LEAVE importloop;
END IF;
CALL upsert_article(c_ean, c_sku, c_mpn, c_manufacturerName, c_manufacturerUniqueId, c_images);
END LOOP testloop;
CLOSE import_cursor;
END
Note: This import script will be called several times over the day and could be run concurrently. So a defined tablename is not an option.
What i have tried so far:
Creating a cursor with given tablename. --> Works, but the name must be dynamic.
Creating a cursor with dynamic sql. --> Failed
Skip using cursors and loop over table in a different way. --> No method found for using dynamic sql
So, is there a way to make this work with a cursor or a workaround?

Creating a temporary table and inserting the results of my dynamic query did the trick at the end.
With that i was able to use mysql cursor with the static named temporary table. And since it is an temporary table, multiple import-jobs won't interfere with each other, because they are hidden to other sessions.
In the end this is the code, that worked for me:
CREATE PROCEDURE `do_update`(IN
tablename VARCHAR(100))
BEGIN
DECLARE done INT;
DECLARE c_ean VARCHAR(20);
DECLARE c_sku VARCHAR(20);
DECLARE c_mpn VARCHAR(20);
DECLARE c_manufacturerName VARCHAR(100);
DECLARE c_manufacturerUniqueId VARCHAR(100);
DECLARE c_images NVARCHAR(3000);
DECLARE import_cursor CURSOR FOR SELECT ean, sku, mpn, manufacturerName, manufacturerUniqueId, images FROM importjob;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done := TRUE;
DROP TEMPORARY TABLE IF EXISTS importjob;
SET #sqlstatement = CONCAT('CREATE TEMPORARY TABLE importjob SELECT ean, sku, mpn, manufacturerName, manufacturerUniqueId, images FROM ', tablename);
PREPARE stmt FROM #sqlstatement;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET #DEBUG = true;
OPEN import_cursor;
importloop: LOOP
FETCH import_cursor INTO c_ean, c_sku, c_mpn, c_manufacturerName, c_manufacturerUniqueId, c_images;
IF done THEN
LEAVE importloop;
END IF;
CALL upsert_article(c_ean, c_sku, c_mpn, c_manufacturerName, c_manufacturerUniqueId, c_images);
END LOOP importloop;
CLOSE import_cursor;
DROP TEMPORARY TABLE IF EXISTS importjob;
END

Related

Cursor in Procedure does not return any values

I have a database where temp tables are created, those table names are randomly generated and saved in Checkouts.unid. I want to drop all those tables and truncate the table Checkouts. I thought the niftiest solution would be a procedure, but it will not work:
DROP PROCEDURE IF EXISTS `spCheckoutsCleanup`;
CREATE PROCEDURE `spCheckoutsCleanup` ()
SQL SECURITY INVOKER
BEGIN
DECLARE `t` VARCHAR(64);
DECLARE `ch` CURSOR FOR SELECT `unid` FROM `Checkouts`;
OPEN `ch`;
drop_tables: LOOP
FETCH `ch` INTO `t`;
DROP TABLE IF EXISTS `t`;
END LOOP;
CLOSE `ch`;
TRUNCATE TABLE `Checkouts`;
END
I always get "No data - zero rows fetched, selected, or processed" although those tables are there and the table Checkouts is not empty though.
You have to add something like this in order to end your loop:
DECLARE CONTINUE HANDLER FOR NOT FOUND SET ...;
See example in the documentation.. E.g. ...
DROP PROCEDURE IF EXISTS `spCheckoutsCleanup`;
CREATE PROCEDURE `spCheckoutsCleanup` ()
SQL SECURITY INVOKER
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE `t` VARCHAR(64);
DECLARE `ch` CURSOR FOR SELECT `unid` FROM `Checkouts`;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN `ch`;
drop_tables: LOOP
FETCH `ch` INTO `t`;
IF done THEN
LEAVE drop_tables;
END IF;
DROP TABLE IF EXISTS `t`;
END LOOP;
CLOSE `ch`;
TRUNCATE TABLE `Checkouts`;
END
Otherwise you will get an error once you reached the end of your cursor.
I got it working with this:
DROP PROCEDURE IF EXISTS `spCheckoutsCleanup`;
CREATE PROCEDURE `spCheckoutsCleanup` ()
SQL SECURITY INVOKER
BEGIN
DECLARE done int DEFAULT 0;
DECLARE t CHAR(64);
DECLARE ch CURSOR FOR SELECT `unid` FROM `Checkouts`;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN ch;
drop_table: LOOP
FETCH ch INTO t;
IF done = 1 THEN
LEAVE drop_table;
END IF;
SET #sql := CONCAT('DROP TABLE IF EXISTS `', t, '`');
PREPARE dropt FROM #sql;
EXECUTE dropt;
END LOOP;
CLOSE ch;
TRUNCATE TABLE `Checkouts`;
END;
CALL spCheckoutsCleanup;

prepared statement in stored procedure not refreshing

Using a dynamically created view WITHIN a Loop in this MySQL stored procedure. The problem I'm having is the contents of temp_controller_view view only reflect the result from the view's first creation (e.g. first loop) as if the statement CREATE OR REPLACE VIEW isn't being read.
Obviously I'm trying to get the view to update upon each Looping so that I can work with the contents of the dynamic view
MYSQL ##VERSION > 5.1.61
DROP procedure if exists SITETOEPRISE;
DELIMITER //
CREATE PROCEDURE SITETOEPRISE()
BEGIN
DECLARE bDone INT;
DECLARE sid int(11); -- or approriate type
DECLARE curr_site_db VARCHAR(10);
DECLARE Var3 VARCHAR(50);
DECLARE site_curs CURSOR FOR SELECT siteid FROM sites;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET bDone = 1;
OPEN site_curs;
SET bDone = 0;
site_loop : LOOP
FETCH site_curs INTO sid;
IF bDone THEN
CLOSE site_curs;
LEAVE site_loop;
END IF;
select sid as 'sid_in_loop';
SET #dyn_sql = CONCAT('CREATE OR REPLACE VIEW `temp_controller_view` AS SELECT * from site',sid,'.controller;');
select #dyn_sql;
PREPARE stmt_dyn_view FROM #dyn_sql;
EXECUTE stmt_dyn_view;
DEALLOCATE PREPARE stmt_dyn_view;
select * from temp_controller_view;
drop view temp_controller_view;
END LOOP;
END
//
DELIMITER ;

Dynamic cursor in stored procedure

I would like to use LIMIT in a cursor. The cursor should be used and updated several times within a loop, each time with different parameters of LIMIT. Here some code:
DELIMITER $$
CREATE PROCEDURE `updateIt`() READS SQL DATA
BEGIN
declare done int(1) default 0;
declare counter int(10) default 0;
declare xabc int(10) default 0;
declare tab1Cursor cursor for select abc from tab1 limit 100000*counter, 100000;
declare continue handler for not found set done=1;
loopCounter: LOOP
set done = 0;
open tab1Cursor;
igmLoop: loop
fetch tab1Cursor into xabc;
if done = 1 then leave igmLoop; end if;
-- do something
end loop igmLoop;
close tab1Cursor;
if (counter = 1039)
leave loopCounter;
end if;
set counter = counter + 1;
END LOOP loopCounter;
END $$
DELIMITER ;
This, however, does not work (I also tried it with the cursor in the LOOP counterLoop). Can Mysql deal with dynamic cursors?
From the MySQL Manual
a cursor cannot be used for a dynamic statement that is prepared and
executed with PREPARE and EXECUTE. The statement for a cursor is
checked at cursor creation time, so the statement cannot be dynamic.
However there are 2 ways, according to this post in mysql forums:
The first is for cases where absolutely only one user at a time will be running the procedure. A prepare statement can be used to create a view with the dynamic SQL and the cursor can select from this statically-named view. There's almost no performance impact. Unfortunately, these views are also visible to other users (there's no such thing as a temporary view), so this won't work for multiple users.
Analogously, a temporary table can be created in the prepare statement and the cursor can select from the temporary table. Only the current session can see a temporary table, so the multiple user issue is resolved. But this solution can have significant performance impact since a temp table has to be created each time the proc runs.
Bottom line: We still need cursors to be able to be created dynamically!
Here's an example of using a view to pass the table name and column name into a cursor.
DELIMITER //
DROP PROCEDURE IF EXISTS test_prepare//
CREATE PROCEDURE test_prepare(IN tablename varchar(255), columnname varchar(50))
BEGIN
DECLARE cursor_end CONDITION FOR SQLSTATE '02000';
DECLARE v_column_val VARCHAR(50);
DECLARE done INT DEFAULT 0;
DECLARE cur_table CURSOR FOR SELECT * FROM test_prepare_vw;
DECLARE CONTINUE HANDLER FOR cursor_end SET done = 1;
SET #query = CONCAT('CREATE VIEW test_prepare_vw as select ', columnname, ' from ', tablename);
select #query;
PREPARE stmt from #query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
OPEN cur_table;
FETCH cur_table INTO v_column_val;
WHILE done = 0 DO
SELECT v_column_val;
FETCH cur_table INTO v_column_val;
END WHILE;
CLOSE cur_table;
DROP VIEW test_prepare_vw;
END;
//
DELIMITER ;

Join all tables with same structure in one MYSQL

I'm working on an old database already in use for years and really crappy designed.
There is a table, "Articles", which contains a "code" column that will be our PK.
And many tables like "idXXXXX" where XXXXX is a "code" value with exactly the same structure.
I looked at the application using this database and saw that relations between tables is made there.
I'm not affraid of redesign the database access in the application, but I don't want to lose years of entries in the database.
I want to create a "campain" table which will have an "id" PK and a "id_code" as FK linking "campain" to "articles"
I'm not a SQL master but I know I can get tables names with
SELECT TABLE_NAME FROM INFORMATION_SCHEMA WHERE TABLE_NAME LIKE 'id%'
But I have really no idea about how to deal with the result (which is fine).
So how can I access to every tables named "idXXX" and insert every rows in the "campain" table + set "id_code" column to "XXX"?
Here is the procedure I saved (I didn't add every fields in the INSERT line for testing purpose) :
CREATE PROCEDURE JoinAllTables()
BEGIN
DECLARE done INT default 0;
DECLARE tableName CHAR(9);
DECLARE buffStr CHAR(7);
DECLARE buffId INT default 0;
DECLARE cur1 CURSOR FOR SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE 'id%';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cur1;
read_loop: LOOP
FETCH cur1 INTO tableName;
IF done THEN
LEAVE read_loop;
END IF;
SET buffStr = SUBSTRING(tableName, 3);
SET buffId = CAST(buffStr AS SIGNED);
set #sql = CONCAT("INSERT INTO campagnes(id, id_code) SELECT null, bufId FROM ",tableName); # Dynamically building sql statement
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP;
CLOSE cur1;
END;
As u can see, I sub 'idXXXXX' to 'XXXXX' then CAST it AS INTEGER (SIGNED).
But I guess that in the "INSERT INTO" line, second tableName doesn't point to the variable. That's why I'm getting a
"#1446 - Tabble 'bddsoufflage.tablename'doesn't exist" Error :) Any idea ?
Edit: Updated answer
We can't have the tableName dynamically changed inside a prepared statement, so we must go through DynamicSQL to build the query using CONCAT, then compile the SQL with PREPARE, EXECUTE it and DEALLOCATE it.
DELIMITER //
CREATE PROCEDURE JoinAllTables()
BEGIN
DECLARE done INT default 0;
DECLARE tableName CHAR(9);
DECLARE buffStr CHAR(7);
DECLARE buffId INT default 0;
DECLARE cur1 CURSOR FOR SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE 'id%';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cur1;
read_loop: LOOP
FETCH cur1 INTO tableName;
IF done THEN
LEAVE read_loop;
END IF;
SET buffStr = SUBSTRING(tableName, 3);
SET buffId = CAST(buffStr AS SIGNED);
set #sql = CONCAT("INSERT INTO campagnes(id, id_code) SELECT null, ", buffId, " FROM ",tableName); # Dynamically building sql statement
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP;
CLOSE cur1;
END; //
See also this answer MySQL Pass table name to cursor select
Old answer
The procedure should look something like this. Thanks Mchl for providing an Insert Into query example, I simply added it to the rest of the procedure.
DELIMITER //
CREATE PROCEDURE JoinAllTables()
BEGIN
DECLARE done INT default 0;
DECLARE tableName CHAR(7); # Variable to contain table names CHAr(7) is assuming id + 5Xs as characters.
DECLARE cur1 CURSOR FOR SELECT TABLE_NAME FROM INFORMATION_SCHEMA WHERE TABLE_NAME LIKE 'id%'; # Create a cursor to iterate over the tables
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cur1;
read_loop: LOOP
FETCH cur1 INTO tableName;
IF done THEN
LEAVE read_loop;
END IF;
#Your Insert statement here, using tableName as a field.
INSERT INTO campain (id, id_code, otherfields) SELECT null, tableName, otherfields FROM tableName;
END LOOP;
CLOSE cur1;
END;//
Easiest way would be to run the information_schema query you have within some script (PHP,Python,Perl - whichever suits you best) and use it's results to create queries like:
INSERT INTO
campain (id, id_code, otherfields)
SELECT
null, 'idXXXX', otherfields FROM idXXXX

Deleting temporary tables with a stored procedure

I have been trying to make a stored procedure which autodeletes temporary tables.
CREATE PROCEDURE DeleteTemp()
BEGIN
DECLARE no_more_rows BOOLEAN;
DECLARE loop_cntr INT DEFAULT 0;
DECLARE num_rows INT DEFAULT 0;
DECLARE tmptablename VARCHAR(100);
DECLARE tmpTables CURSOR FOR
SELECT TABLE_NAME
FROM information_schema.TABLES
WHERE TABLE_SCHEMA='myDB' AND TABLE_NAME LIKE 'tmp%';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_more_rows = TRUE;
OPEN tmpTables;
SELECT FOUND_ROWS() into num_rows;
the_loop: LOOP
FETCH tmpTables INTO tmptablename;
DROP TABLE tmptablename;
IF no_more_rows THEN
CLOSE tmpTables;
LEAVE the_loop;
END IF;
END LOOP the_loop;
END
However, all I get is:
Query : call DeleteTemp
Error Code : 1051
Unknown table 'tmptablename'
How can I pass the variable tmptablename properly into the "DROP TABLE" command?
Fixed using prepared statements.
CREATE
PROCEDURE DeleteTemp()
BEGIN
DECLARE no_more_rows BOOLEAN;
DECLARE loop_cntr INT DEFAULT 0;
DECLARE num_rows INT DEFAULT 0;
DECLARE tmptablename VARCHAR(100);
DECLARE tmpTables CURSOR FOR
SELECT TABLE_NAME
FROM information_schema.TABLES
WHERE TABLE_SCHEMA='MY_SCHEMA'
AND TABLE_NAME LIKE 'tmp%';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_more_rows = TRUE;
OPEN tmpTables;
SELECT FOUND_ROWS() INTO num_rows;
the_loop: LOOP
FETCH tmpTables INTO tmptablename;
IF no_more_rows THEN
CLOSE tmpTables;
LEAVE the_loop;
ELSE
SET #a:=CONCAT('DROP TABLE ',tmptablename);
PREPARE stmt1 FROM #a;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
END IF;
END LOOP the_loop;
END
Have you had a look at the PREPARE and EXECUTE commands. This is like executing DYNAMIC SQL
SQL Syntax for Prepared Statements
The following SQL statements can be
used in prepared statements: ALTER
TABLE, CALL, COMMIT, CREATE INDEX,
CREATE TABLE, DELETE, DO, DROP INDEX,
DROP TABLE, INSERT, RENAME TABLE,
REPLACE, SELECT, SET, UPDATE, and most
SHOW statements. ANALYZE TABLE,
OPTIMIZE TABLE, and REPAIR TABLE are
also supported as of MySQL 5.0.23.
This may not be what you are after, but are you aware that your temporary tables are only visible to that connection that created them, and will be dropped automatically once that connection is closed?