On the same server, I have around 30 sites, and the DB of 30 sites have the same structure, now I want to update a table: tx_tip(same table name on 30 DBs). instead of using phpmyadmin to update one by one, is there a way that I can use mysql command to update all the DBs together?
You can update multiple tables using a JOIN. If the tables are in different databases, you have to put the database prefix in the query.
UPDATE db1.tx_tip t1
JOIN db2.tx_tip t2
JOIN db3.tx_tip t3
...
SET t1.col = new_val,
t2.col = new_val,
t3.col = new_val,
WHERE <condition>
If you maintain this table on a regular basis you may consider to create a simple stored procedure that might look something like this
DELIMITER $$
CREATE PROCEDURE sp_update_tx_tip(IN p_id INT, IN p_new_value VARCHAR(128))
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE db_name VARCHAR(64);
DECLARE cur CURSOR FOR
SELECT TABLE_SCHEMA
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 'tx_tip';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
SET #id = p_id, #new_value = p_new_value;
read_loop: LOOP
FETCH cur INTO db_name;
IF done THEN LEAVE read_loop; END IF;
SET #sql = CONCAT('UPDATE ', db_name, '.tx_tip t SET t.value = ? WHERE t.id = ?');
PREPARE stmt FROM #sql;
EXECUTE stmt USING #new_value, #id;
DEALLOCATE PREPARE stmt;
END LOOP;
CLOSE cur;
END$$
DELIMITER ;
And use it like
CALL sp_update_tx_tip(1, 'new value');
It will pull names of all databases that have tx_tip table from INFORMATION_SCHEMA.TABLES, construct and execute update statements for you.
Related
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');
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');
I am trying this command to choose all wp_%_options tables in all sites:
SELECT * FROM (SELECT * FROM information_schema.tables WHERE table_name LIKE 'wp_%_options') as t
But I need to get one level deeper and get the option_name = 'template' in all of these folders.
Tried this:
SELECT * FROM (SELECT * FROM information_schema.tables WHERE table_name like 'wp_%_options') as t WHERE option_name='template'
Not working.
Any ideas?
You can not treat table name (or column name) as variable in your standard SELECT queries. To solve the task you have to write a stored procedure.
The stored procedure should:
Fetch all table names wp_%_options;
Walk through the fetched names and select from the second level what you need using PREPARE statement.
DELIMITER |
DROP PROCEDURE IF EXISTS get_my_tables;
CREATE PROCEDURE get_my_tables (IN option_name VARCHAR(255))
BEGIN
DECLARE table_name CHAR(255);
DECLARE done INT DEFAULT FALSE;
DECLARE first INT DEFAULT TRUE;
DECLARE cur1 CURSOR FOR SELECT `tables`.`TABLE_NAME` FROM `information_schema`.`tables` `tables` WHERE `tables`.`TABLE_NAME` LIKE 'wp_%_options';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur1;
read_loop: LOOP
FETCH cur1 INTO table_name;
IF done THEN
LEAVE read_loop;
END IF;
IF (first) THEN
SET first = FALSE;
DROP TABLE IF EXISTS `result_table`;
SET #sql = CONCAT("CREATE TEMPORARY TABLE `result_table` (SELECT * FROM `", table_name ,"` WHERE `option_name` = '", option_name, "')");
ELSE
SET #sql = CONCAT("INSERT INTO `result_table` SELECT * FROM `", table_name, "` WHERE `option_name` = '", option_name, "'");
END IF;
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP;
CLOSE cur1;
SELECT * FROM `result_table`;
END;
|
DELIMITER ;
CALL get_my_tables('template');
I tested this example now it works as expected. You should tune some logic for your needs. For example: how to work with temporary table, fetch fesult and maybe use UNION instead of temporary table.
This is what I did in the end:
UPDATE wp_2_options SET option_value='classic' WHERE option_name='template' OR option_name='stylesheet';
Then I created as many as I need in Excel. Took me 1 min to change them all.
Try using LIKE instead of =.
So that the second query will be:
SELECT * FROM (SELECT * FROM information_schema.tables WHERE table_name like 'wp_%_options') as t WHERE option_name LIKE 'template'
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'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