MySQL: One stored procedure for several tables - mysql

I need to perform the same procedure for several tables in my DB. The poblem is that that procedure contains the following line:
DECLARE tableIt CURSOR FOR select id from table where column=inputParam ;
table is the table the procedure works with. And I can't find a way to make that table name to be dynamic, i.e. to read it from an input parameter.
Right now I have 8 different procedures (one for each table) which differentiate from each other only by one word (the name of the table).
That is really a pain since I have to make every change to the procedure 8 times.
Is it possible to parameterize the select statement for the CURSOR so I can have only one procedure??

Dynamic cursors does not seem to be supported in Mysql.
http://dev.mysql.com/doc/refman/5.1/en/cursors.html
You can work around it
http://forums.mysql.com/read.php?98,133197,149099#msg-149099
"DROP VIEW IF EXISTS v1;
SET #stmt_text=CONCAT("CREATE VIEW v1 AS SELECT c_text FROM ", t_name);
PREPARE stmt FROM #stmt_text;
EXECUTE stmt;
BEGIN
DECLARE v_text VARCHAR(45);
DECLARE done INT DEFAULT 0;
DECLARE c cursor FOR SELECT c_text FROM v1;"

Related

MySQL use cursor to clone existing tables

I'm trying to write a MySQL stored proceedure that loops through all existing tables in my database and creates a copy/clone of each table. I'm using a cursor to loop through the table names then create a new table like this:
DELIMITER //
CREATE PROCEDURE CopyTables()
BEGIN
DECLARE finished INT DEFAULT 0;
DECLARE tableName VARCHAR(100);
DECLARE copyTableName VARCHAR(100);
DECLARE curTables
CURSOR FOR
SELECT table_name FROM INFORMATION_SCHEMA.TABLES;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1;
OPEN curTables;
create_loop: LOOP
FETCH curTables INTO tableName;
IF finished THEN LEAVE create_loop; END IF;
SELECT concat('Processing table ', tableName);
SET copyTableName = CONCAT('copy_',tableName);
SELECT concat('Creating table ', copyTableName);
CREATE TABLE copyTableName LIKE tableName;
END LOOP;
CLOSE curTables;
END //
DELIMITER;
But I get an error when calling the stored procedure:
> call CopyTables()
[2020-12-08 18:16:03] 1 row retrieved starting from 1 in 77 ms (execution: 15 ms, fetching: 62 ms)
[2020-12-08 18:16:03] [S1000] Attempt to close streaming result set com.mysql.cj.protocol.a.result.ResultsetRowsStreaming#7a714591 that was not registered. Only one streaming result set may be open and in use per-connection. Ensure that you have called .close() on any active result sets before attempting more queries.
Is the result set exception effectively complaining because I'm creating new tables which is effectively messing with the cursor/select? I've got additional table changes on both the original and copied table to perform, like adding new columns, creating triggers, modifying constraints.
The list of table names is not static, and this should be able to run on whatever database I need it.
Can you suggest another way to achieve this without the cursor perhaps?
The problem is that the procedure is returning multiple result sets, but your Java client is not handling that correctly.
Refer to How do you get multiple resultset from a single CallableStatement?
Another problem with your procedure is that you aren't creating tables the way you think you are.
This statement:
CREATE TABLE copyTableName LIKE tableName;
will only create a table named literally copyTableName that is like another table that is literally tableName. It will NOT use the values of variables by those names.
To do what you want, you need to use a prepared statement:
SET #sql = CONCAT('CREATE TABLE `', copyTableName, '` LIKE `', tableName, '`');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
This way the value of your variables is concatenated into an SQL statement.
Note that PREPARE only accepts a user-defined session variable, the type with the # sigil. It doesn't work with local variables you create in your procedure with DECLARE. Read https://dev.mysql.com/doc/refman/8.0/en/prepare.html and https://dev.mysql.com/doc/refman/8.0/en/user-variables.html

MySQL temporary tables do not clear

Background - I have a DB created from a single large flat file. Instead of creating a single large table with 106 columns. I created a "columns" table which stores the column names and the id of the table that holds that data, plus 106 other tables to store the data for each column. Since not all the records have data in all columns, I thought this might be a more efficient way to load the data (maybe a bad idea).
The difficulty with this was rebuilding a single record from this structure. To facilitate this I created the following procedure:
DROP PROCEDURE IF EXISTS `col_val`;
delimiter $$
CREATE PROCEDURE `col_val`(IN id INT)
BEGIN
DROP TEMPORARY TABLE IF EXISTS tmp_record;
CREATE TEMPORARY TABLE tmp_record (id INT(11), val varchar(100)) ENGINE=MEMORY;
SET #ctr = 1;
SET #valsql = '';
WHILE (#ctr < 107) DO
SET #valsql = CONCAT('INSERT INTO tmp_record SELECT ',#ctr,', value FROM col',#ctr,' WHERE recordID = ',#id,';');
PREPARE s1 FROM #valsql;
EXECUTE s1;
DEALLOCATE PREPARE s1;
SET #ctr = #ctr+1;
END WHILE;
END$$
DELIMITER ;
Then I use the following SQL where the stored procedure parameter is the id of the record I want.
CALL col_val(10);
SELECT c.`name`, t.`val`
FROM `columns` c INNER JOIN tmp_record t ON c.ID = t.id
Problem - The first time I run this it works great. However, each subsequent run returns the exact same record even though the parameter is changed. How does this persist even when the stored procedure should be dropping and re-creating the temp table?
I might be re-thinking the whole design and going back to a single table, but the problem illustrates something I would like to understand.
Unsure if it matters but I'm running MySQL 5.6 (64 bit) on Windows 7 and executing the SQL via MySQL Workbench v5.2.47 CE.
Thanks,
In MySQL stored procedures, don't put an # symbol in front of local variables (input parameters or locally declared variables). The #id you used refers to a user variable, which is kind of like a global variable for the session you're invoking the procedure from.
In other words, #id is a different variable from id.
That's the explanation of the immediate problem you're having. However, I would not design the tables as you have done.
Since not all the records have data in all columns, I thought this might be a more efficient way to load the data
I recommend using a conventional single table, and use NULL to signify missing data.

Why does this simple MySQL procedure take way too long to complete?

This is a very simple MySQL stored procedure. Cursor "commission" has only 3000 records, but the procedure call takes more than 30 seconds to run. Why is that?
DELIMITER //
DROP PROCEDURE IF EXISTS apply_credit//
CREATE PROCEDURE apply_credit()
BEGIN
DECLARE done tinyint DEFAULT 0;
DECLARE _pk_id INT;
DECLARE _eid, _source VARCHAR(255);
DECLARE _lh_revenue, _acc_revenue, _project_carrier_expense, _carrier_lh, _carrier_acc, _gross_margin, _fsc_revenue, _revenue, _load_count DECIMAL;
DECLARE commission CURSOR FOR
SELECT pk_id, eid, source, lh_revenue, acc_revenue, project_carrier_expense, carrier_lh, carrier_acc, gross_margin, fsc_revenue, revenue, load_count FROM ct_sales_commission;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
DELETE FROM debug;
OPEN commission;
REPEAT
FETCH commission INTO
_pk_id, _eid, _source, _lh_revenue, _acc_revenue, _project_carrier_expense, _carrier_lh, _carrier_acc, _gross_margin, _fsc_revenue, _revenue, _load_count;
INSERT INTO debug VALUES(concat('row ', _pk_id));
UNTIL done = 1 END REPEAT;
CLOSE commission;
END//
DELIMITER ;
CALL apply_credit();
SELECT * FROM debug;
If you select some datas, and insert into another table, you can do this:
INSERT INTO debug
SELECT concat('row ', _pk_id)
FROM ct_sales_commission;
It's faster than using a cursor.
Some minor turning:
Remove all indexes on the table debug.
Replace the DELETE FROM into TRUNCATE TABLE.
Add DELAYED to the insert statement.
INSERT DELAYED INTO ... VALUES(....)
The database is hosted in a data centre very far away from my MySQL client.
Connected to a MySQL client which is closely located with the MySQL server makes execution time almost 60 times faster (it takes less than one second for the procedure to complete).
I suspect that MySQL client CLI has an issue handling a remote data connection like that.

Populate until certain amount

I am trying to create a procedure which will fill up the table until certain amount of elements.
At the current moment I have
CREATE PROCEDURE PopulateTable(
IN dbName tinytext,
IN tableName tinytext,
IN amount INT)
BEGIN
DECLARE current_amount INT DEFAULT 0;
SET current_amount = SELECT COUNT(*) FROM dbName,'.',tableName;
WHILE current_amount <= amount DO
set #dll=CONCAT('INSERT INTO `',dbName,'`.`',tableName,'`(',
'`',tableName,'_name`) ',
'VALUES(\'',tableName,'_',current_amount,'\')');
prepare stmt from #ddl;
execute stmt;
SET current_amount = current_amount + 1;
END WHILE;
END;
So what I am trying to do is, once user calls the procedure, the function will check and see how many current elements exist, and fill up the remaining elements.
First problem I have is that I do not know how to count the elements, so my SELECT COUNT(*) FROM dbName,'.',tableName; does not work.
I also want to a suggestion since I am kind of new to databases if what I am doing is correct or if there is a better way to do this?
Also just if this is of any help the table I am trying to do this to only has 2 fields, one of them being id, which is auto incremented and is primary and the other being profile_name which I am populating.
Thanks to anyone for their help!
Firstly, I think you'll have a delimiter problem if you try to execute the code you pasted. The procedure declaration delimiter has to be different from the one you use into the procedure code (here ';'). You will have to use DELIMITER statement;
Your procedure belongs to a schema; I'm not sure you can query tables from other shemas (especially without USE statement);
This is not a good idea if your database contains tables whitch are not supposed to be populated through your procedure;
If you have a limited number of concerned tables, I think it will be a better idea to define one procedure for each table. In this way, table name will be explicit in each procedure code and it will avoid the use of prepared statements;
Be careful about you 'amount' parameter : are you sure that your server can handle requests if I pass the maximum value for INT as amount ?
I think you should use '<' instead of '<=' in your WHILE condition
If you want to insert a large number of lines, you'll obtain better performances performing "grouped" inserts, or generating a temporary table (for example with MEMORY engine) containing all your lines and performing a unique INSERT selecting your temporary table's content.

MySQL fetch next cursor issue

I have a problem fetching values from a MySQL cursor.
I create a temporary table which is a mere copy of another table (the original table has a variable name, that's passed as the procedure's parameter, and because MySQL doesn't support variable table names, I have to create a copy - can't use the original directly).
The temporary table creation goes just fine, all data that are supposed to be in it are there. Then I define a cursor to iterate through my temporary table... but when I try to fetch from the cursor in a while loop my variables are not filled with data from the "cursored" table... most of them are just NULL, only last 2 seem to have correct values inside.
Here is the chunk of my code:
-- variables to be filled from the cursor
DECLARE id,rain,snow,hs,clouds,rain2,cape,status,done int;
DECLARE v_v,v_u double;
-- cursor declaration
DECLARE iter CURSOR FOR (SELECT id,cape,rain,snow,hstones,clouds,raining,wind_u,wind_v FROM temp_tbl);
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
-- drop the old temporary table if any
DROP TABLE IF EXISTS temp_tbl;
-- a statement to create the temporary table from a table with the specified name
-- (table_name is a parameter of the stored procedure this chunk is from)
SET #temp_table = CONCAT('CREATE TABLE temp_tbl AS SELECT * FROM ', table_name, ' WHERE 1');
-- prepare, execute and deallocate the statement
PREPARE ctmp FROM #temp_table;
EXECUTE ctmp;
DEALLOCATE PREPARE ctmp;
-- now the temp_table exists, open the cursor
OPEN iter;
WHILE NOT done DO
-- fetch the values
FETCH iter INTO id,cape,rain,snow,hs,clouds,rain2,v_u,v_v;
-- fetch doesnt work, only v_u and v_v variables are fetched correctly, others are null
-- ... further statements go here
END WHILE;
CLOSE iter;
Is there any type-checking within the FETCH statement that might cause such problem? The columns in my temporary table (which is derived from the original one) are just small-ints or tiny-ints, so these should be perfectly compatible with ints I use in the fetch statement. Those two last ones are doubles, but weird that only these two doubles are fetched. Even the ID int column, which is the primary key isn't fetched.
I work with the dbForge Studio to step into and debug my procedures, but that shouldn't be the problem.
In MySQL functions, when the parameter or variable names conflict with the field names, the parameter or variable names are used.
In these statements:
DECLARE id,rain,snow,hs,clouds,rain2,cape,status,done int;
DECLARE iter CURSOR FOR (SELECT id,cape,rain,snow,hstones,clouds,raining,wind_u,wind_v FROM temp_tbl);
you select the uninitialized variables, not the fields. It is the same as doing:
DECLARE iter CURSOR FOR (SELECT NULL, NULL, NULL, NULL, NULL, NULL, NULL, wind_u,wind_v FROM temp_tbl);
The last two field name do not conflict and are selected correctly.
Prepend the variable names with an underscore:
DECLARE _id, _rain, _snow, _hs, _clouds, _rain2, _cape, _status, _done INT;