I have a two tables:
people_en: id, name
people_es: id, name
(please, dont bother about normalization. the design is normalized. the tables are much more complex than this
but this is just a way to simplify my problem).
I then have a stored procedure:
CREATE PROCEDURE myproc(lang char(2))
BEGIN
set #select = concat('SELECT * FROM ', lang, ' limit 3');
PREPARE stm FROM #select;
EXECUTE stm;
DEALLOCATE PREPARE stm;
SET #cnt = FOUND_ROWS();
SELECT #cnt;
IF #cnt = 3 THEN
//Here I need to loop through the rows
ELSE
//Do something else
END IF;
END$$
More or less, the logic in the procedure is:
If the select gives 3 rows, then we have to loop through the rows and do something with the value in each row.
Otherwise somwthing else (not important what, but I put this to make you understand that I need to have
an if statement before looping.
I have seen and read about cursors, but couldnt find much for selects created by concat (does it matter?)
and especially created with a prepared statement.
How can I iterate through the result list and use the values from each row?
Thanks.
I have some bad and good news for you.
First the bad news.
MySQL manual says 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.
So there are no dynamical cursors so far... Here you would need something like this.
But now the good news: there are at least two ways to bypass it - using vw or tbl.
Below I rewrote your code and applied view to make 'dynamical' cursor.
DELIMITER //
DROP PROCEDURE IF EXISTS myproc;
CREATE PROCEDURE myproc(IN lang VARCHAR(400))
BEGIN
DECLARE c VARCHAR(400);
DECLARE done BOOLEAN DEFAULT FALSE;
DECLARE cur CURSOR FOR SELECT name FROM vw_myproc;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
SET #select = concat('CREATE VIEW vw_myproc as SELECT * FROM ', lang, ' limit 3');
PREPARE stm FROM #select;
EXECUTE stm;
DEALLOCATE PREPARE stm;
SET #select = concat('SELECT * FROM ', lang, ' limit 3');
PREPARE stm FROM #select;
EXECUTE stm;
DEALLOCATE PREPARE stm;
SET #cnt = FOUND_ROWS();
SELECT #cnt;
IF #cnt = 3 THEN
OPEN cur;
read_loop: LOOP
FETCH cur INTO c;
IF done THEN
LEAVE read_loop;
END IF;
#HERE YOU CAN DO STH WITH EACH ROW e.g. UPDATE; INSERT; DELETE etc
SELECT c;
END LOOP read_loop;
CLOSE cur;
DROP VIEW vw_myproc;
ELSE
SET c = '';
END IF;
END//
DELIMITER ;
And to test the procedure:
CALL myproc('people_en');
#clickstefan, you will have problems with two or more users trying to execute your script at the same time. The second user will get error message 'View vw_myproc already exists' for the line:
SET #select = concat('CREATE VIEW vw_myproc as SELECT * FROM ', lang, ' limit 3');
The solution is temporary table - it exists for the lifetime of current connection only, and users may simultaneously create temporary tables with the same name. So, code may looks like:
DROP TABLE IF EXISTS vw_myproc;
SET #select = concat('CREATE TEMPORARY TABLE vw_myproc AS SELECT * FROM ', lang, ' limit 3');
Related
I have to send comma separated values into a select statement where it will update values through #sql statement.
I have common table in all Databases I need to update the table column by one update statement in the procedure.
For Example : Input Param will be ('DataBase1','Database2',....., 'Database10')
Below is the sample procedure :
DELIMITER &&
CREATE PROCEDURE update_stmt (IN DBName varchar(100))
BEGIN
Declare DBName = #DB;
**comma seperated values loop and placed into the #DB**
use #DB;
SELECT concat(update #DB.sample SET COL = 0 where ID = \'',ID,'\','; ) as stmt FROM
Test.Sample into #s;
SET #sql = #s
PREPARE stmt from #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END &&
DELIMITER ;
so that update statement will execute in each of the databases.
Here's another approach. I don't try to split the comma-separated string, I use it with FIND_IN_SET() to match schema names in INFORMATION_SCHEMA.TABLES. This filters to schemas in the list that actually exist, and tables that actually exist in that schema.
Then use a cursor to loop over the matching rows, so you don't have to split any strings, which is awkward to do in a stored procedure.
I supposed that you would want to specify the id of the row to update too, so I added that to the procedure parameters.
Also notice the use of quotes when I create #sql. You can concatenate strings, but those must be quote-delimited like any other string literal. Variables must not be inside the quoted string. There's no feature to expand variables inside string literals in MySQL.
DELIMITER &&
CREATE PROCEDURE update_stmt (IN schema_name_list VARCHAR(100), IN in_id INT)
BEGIN
DECLARE done INT DEFAULT false;
DECLARE schema_name VARCHAR(64);
DECLARE cur1 CURSOR FOR
SELECT TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 'sample' AND FIND_IN_SET(TABLE_SCHEMA, schema_name_list);
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = true;
SET #id = in_id;
OPEN cur1;
schema_loop: LOOP
FETCH cur1 INTO schema_name;
IF done THEN
LEAVE schema_loop;
END IF;
SET #sql = CONCAT('UPDATE `', schema_name, '`.sample SET col = 0 WHERE id = ?');
PREPARE stmt FROM #sql;
EXECUTE stmt USING #id;
DEALLOCATE PREPARE stmt;
END LOOP;
CLOSE cur1;
END &&
DELIMITER ;
Frankly, I hardly ever use stored procedures in MySQL. The procedure language is primitive, and the tasks I see people try to do in stored procedures could be done a lot more easily in virtually every other programming language.
I have a two tables:
people_en: id, name
people_es: id, name
(please, dont bother about normalization. the design is normalized. the tables are much more complex than this
but this is just a way to simplify my problem).
I then have a stored procedure:
CREATE PROCEDURE myproc(lang char(2))
BEGIN
set #select = concat('SELECT * FROM ', lang, ' limit 3');
PREPARE stm FROM #select;
EXECUTE stm;
DEALLOCATE PREPARE stm;
SET #cnt = FOUND_ROWS();
SELECT #cnt;
IF #cnt = 3 THEN
//Here I need to loop through the rows
ELSE
//Do something else
END IF;
END$$
More or less, the logic in the procedure is:
If the select gives 3 rows, then we have to loop through the rows and do something with the value in each row.
Otherwise somwthing else (not important what, but I put this to make you understand that I need to have
an if statement before looping.
I have seen and read about cursors, but couldnt find much for selects created by concat (does it matter?)
and especially created with a prepared statement.
How can I iterate through the result list and use the values from each row?
Thanks.
I have some bad and good news for you.
First the bad news.
MySQL manual says 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.
So there are no dynamical cursors so far... Here you would need something like this.
But now the good news: there are at least two ways to bypass it - using vw or tbl.
Below I rewrote your code and applied view to make 'dynamical' cursor.
DELIMITER //
DROP PROCEDURE IF EXISTS myproc;
CREATE PROCEDURE myproc(IN lang VARCHAR(400))
BEGIN
DECLARE c VARCHAR(400);
DECLARE done BOOLEAN DEFAULT FALSE;
DECLARE cur CURSOR FOR SELECT name FROM vw_myproc;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
SET #select = concat('CREATE VIEW vw_myproc as SELECT * FROM ', lang, ' limit 3');
PREPARE stm FROM #select;
EXECUTE stm;
DEALLOCATE PREPARE stm;
SET #select = concat('SELECT * FROM ', lang, ' limit 3');
PREPARE stm FROM #select;
EXECUTE stm;
DEALLOCATE PREPARE stm;
SET #cnt = FOUND_ROWS();
SELECT #cnt;
IF #cnt = 3 THEN
OPEN cur;
read_loop: LOOP
FETCH cur INTO c;
IF done THEN
LEAVE read_loop;
END IF;
#HERE YOU CAN DO STH WITH EACH ROW e.g. UPDATE; INSERT; DELETE etc
SELECT c;
END LOOP read_loop;
CLOSE cur;
DROP VIEW vw_myproc;
ELSE
SET c = '';
END IF;
END//
DELIMITER ;
And to test the procedure:
CALL myproc('people_en');
#clickstefan, you will have problems with two or more users trying to execute your script at the same time. The second user will get error message 'View vw_myproc already exists' for the line:
SET #select = concat('CREATE VIEW vw_myproc as SELECT * FROM ', lang, ' limit 3');
The solution is temporary table - it exists for the lifetime of current connection only, and users may simultaneously create temporary tables with the same name. So, code may looks like:
DROP TABLE IF EXISTS vw_myproc;
SET #select = concat('CREATE TEMPORARY TABLE vw_myproc AS SELECT * FROM ', lang, ' limit 3');
Seen a lot for dropping tables using a wildcard but not a direct SQL statement except this one:
http://azimyasin.wordpress.com/2007/08/11/mysql-dropping-multiple-tables/
It says:
SHOW TABLES LIKE ‘phpbb_%’;
then DROP TABLES, is there a neat way to combine this all into one SQL Statement?
You could use dynamic SQL to do it, inside a stored procedure. It'd look something like this (untested):
CREATE PROCEDURE drop_like (IN pattern VARCHAR(64))
BEGIN
DECLARE q tinytext;
DECLARE done INT DEFAULT FALSE;
DECLARE cur CURSOR FOR
SELECT CONCAT('DROP TABLE "', table_schema, '"."', table_name, '"')
FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_name LIKE pattern;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
drop_loop: LOOP
FETCH cur INTO q;
IF done THEN
LEAVE drop_loop;
END IF;
PREPARE stmt FROM #q;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP;
CLOSE cur;
END;
Using dynamic SQL in a query, as per derobert's answer, is the only to do this with pure SQL (no app code).
I wrote a generalized procedure to do this sort of thing (run a query for every table in a database) that you can find here - to use it, you would just need to run this query:
CALL p_run_for_each_table('databasename', 'DROP TABLE `{?database}`.`{?table}`');
It works in essentially the same way as derobert's answer.
However, the writer of that blog post was probably expecting you to write app code to turn the names of tables into a single DROP statement.
To do this, you would iterate over the results of the SHOW TABLE in your code and build a single query like this:
DROP TABLE table1, table2, tablewhatever;
This can be achieved via stored procedure, for example:
CREATE DEFINER=`some_user`#`%` PROCEDURE `drop_tables`()
LANGUAGE SQL
NOT DETERMINISTIC
MODIFIES SQL DATA
SQL SECURITY DEFINER
COMMENT ''
BEGIN
#We need to declare a variable with default 0 to determine weather to continue the loop or exit the loop.
DECLARE done INT DEFAULT 0;
DECLARE archive_table_name VARCHAR(100);
#Select desired tables from `information_schema`
DECLARE cur CURSOR FOR
SELECT t.`TABLE_NAME` FROM information_schema.`TABLES` t WHERE t.`TABLE_NAME` LIKE 'some_table_name%'
AND t.CREATE_TIME BETWEEN DATE_SUB(NOW(), INTERVAL 9 MONTH) AND DATE_SUB(NOW(), INTERVAL 6 MONTH);
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cur;
read_loop: LOOP
#Fetch one record from CURSOR and set variable (if not found, then variable `done` will be set to 1 by continue handler)
FETCH cur INTO archive_table_name;
IF done THEN
LEAVE read_loop; #If done is set to 1, then exit the loop, else continue
END IF;
#Do your work
-- Create the truncate query
SET #s = CONCAT('DROP TABLE IF EXISTS ', archive_table_name);
-- Prepare, execute and deallocate the truncate query
PREPARE drop_statement FROM #s;
EXECUTE drop_statement;
DEALLOCATE PREPARE drop_statement;
END LOOP;
CLOSE cur; #Closing the cursor
END
Pay attention to the database user, which is creating/executing the stored routine: it must have appropriate credentials for executing/dropping tables.
I have a joomla mysql database with a table name prefix of "jos_" on all of my table names. But I would like to remove it from all of my tables. I understand how to rename each table, one at a time, but I have 600 tables. Is there an easy to run a sql query to do this.
If someone has a solution, could you please post the exact sql query I can use?
In phpmyadmin select all tables of your database.
From the dropdown 'With selected:' choose 'Replace table prefix'
Set from->to replacement.
DONE
You can generate the necessary statements with a single query:
select 'RENAME TABLE ' || table_name || ' TO ' || substr(table_name, 5) ||';'
from information_schema.tables
Save the output of that query to a file and you have all the statements you need.
Or if that returns 0s and 1s rather the statemenets, here's the version using concat instead:
select concat('RENAME TABLE ', concat(table_name, concat(' TO ', concat(substr(table_name, 5), ';'))))
from information_schema.tables;
You can create your own stored procedure to rename your tables, with that you don't need to open an external editor everything will be done on the server:
delimiter //
CREATE PROCEDURE rename_tables( IN db CHAR(255), IN srch CHAR(255), IN rplc CHAR(255) )
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE from_table CHAR(255);
DECLARE cur1 CURSOR FOR SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA=db;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cur1;
read_loop: LOOP
IF done THEN
LEAVE read_loop;
END IF;
FETCH cur1 INTO from_table;
SET #to_table = REPLACE(from_table, srch, rplc);
IF from_table != #to_table THEN
SET #rename_query = CONCAT('RENAME TABLE ', db, '.', from_table, ' TO ', #to_table, ';');
PREPARE stmt FROM #rename_query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END IF;
END LOOP;
CLOSE cur1;
END//
delimiter ;
Usage:
CALL rename_tables('test', 'jos_', '');
Update: This was my first MySQL stored procedure and I ran into the 6 years old bug #5967 which was quite annoying, your variable names must be different from the field names, because if they aren't you'll get NULL values in your variables.
So be aware of that if you decide to write a MySQL stored procedure.
I'm trying to pass a table name into my mysql stored procedure to use this sproc to select off of different tables but it's not working...
this is what I"m trying:
CREATE PROCEDURE `usp_SelectFromTables`(
IN TableName varchar(100)
)
BEGIN
SELECT * FROM #TableName;
END
I've also tried it w/o the # sign and that just tells me that TableName doesn't exist...which I know :)
SET #cname:='jello';
SET #vname:='dwb';
SET #sql_text = concat('select concept_id,concept_name,',#vname,' from enc2.concept a JOIN enc2.ratings b USING(concept_id) where concept_name like (''%',#cname,'%'') and 3 is not null order by 3 asc');
PREPARE stmt FROM #sql_text;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
An extra bit that caused me problems.
I wanted to set the table name and field dynamically in a query as #kyle asked, but I also wanted to store the result of that query into a variable #a within the query.
Instead of putting the variable #a into the concat literally, you need to include it as part of the string text.
delimiter //
CREATE PROCEDURE removeProcessed(table_name VARCHAR(255), keyField VARCHAR(255), maxId INT, num_rows INT)
BEGIN
SET #table_name = table_name;
SET #keyField = keyField;
SET #maxId = maxId;
SET #num_rows = num_rows;
SET #sql_text1 = concat('SELECT MIN(',#keyField,') INTO #a FROM ',#table_name);
PREPARE stmt1 FROM #sql_text1;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
loop_label: LOOP
SET #sql_text2 = concat('SELECT ',#keyField,' INTO #z FROM ',#table_name,' WHERE ',#keyField,' >= ',#a,' ORDER BY ',#keyField,' LIMIT ',#num_rows,',1');
PREPARE stmt2 FROM #sql_text2;
EXECUTE stmt2;
DEALLOCATE PREPARE stmt2;
...Additional looping code...
END LOOP;
END
//
delimiter ;
So in #sql_text1 assign the result of the query to #a within the string using:
') INTO #a FROM '
Then in #sql_text2 use #a as an actual variable:
,' WHERE ',#keyField,' >= ',#a,' ORDER BY '
It depends on the DBMS, but the notation usually requires Dynamic SQL, and runs into the problem that the return values from the function depend on the inputs when it is executed. This gives the system conniptions. As a general rule (and therefore probably subject to exceptions), DBMS do not allow you to use placeholders (parameters) for structural elements of a query such as table names or column names; they only allow you to specify values such as column values.
Some DBMS do have stored procedure support that will allow you to build up an SQL string and then work with that, using 'prepare' or 'execute immediate' or similar operations. Note, however, that you are suddenly vulnerable to SQL injection attacks - someone who can execute your procedure is then able to control, in part, what SQL gets executed.