Run MySQL queries on different databases - mysql

We have a system where we have for each division a database , currently we have 20+ divisions.
So when we have to update / delete / alter / new table we have to go threw all of those database and run the queries .
Sometime people don't follow procedures (always ?) and we end up having structures that aren't updated .
I was looking into a way to lunch the same queries on all database without having to use bash or external scripts .
So here is some of the stuff that i found :
CALL FOR EACH("SELECT databases WHERE `DATABASE` LIKE 'division_%'" , ${1});
where i could enter a query in the ${1}
or this (less dynamic):
call $('{a, b}' , 'ALTER TABLE division_${1}.caching ADD COLUMN notes VARCHAR(4096) CHARSET utf8'');
But this gives me "No database Selected"
Any idea on how to proceed with this situation ?

This is a solution i found and it works :
USE division_global;
DELIMITER $$
CREATE PROCEDURE `MultipleSchemaQuery`()
BEGIN
declare scName varchar(250);
declare q varchar(2000);
DROP TABLE IF EXISTS ResultSet;
create temporary table ResultSet (
option_value varchar(200)
);
DROP TABLE IF EXISTS MySchemaNames;
create temporary table MySchemaNames (
schemaName varchar(250)
);
insert into MySchemaNames
SELECT distinct
TABLE_SCHEMA as SchemaName
FROM
`information_schema`.`TABLES`
where
TABLE_SCHEMA LIKE 'division_%';
label1:
LOOP
set scName = (select schemaName from MySchemaNames limit 1);
// The Query
set #q = concat('TRUNCATE TABLE ', scName, '.caching');
PREPARE stmt1 FROM #q;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
delete from MySchemaNames where schemaName = scName;
IF ((select count(*) from MySchemaNames) > 0) THEN
ITERATE label1;
END IF;
LEAVE label1;
END LOOP label1;
SELECT * FROM ResultSet;
DROP TABLE IF EXISTS MySchemaNames;
DROP TABLE IF EXISTS ResultSet;
END
$$
Inspired by this :
Querying multiple databases at once

You will need to use a stored procedure and some prepared statements as Simon pointed out in the comments:
cat procedure.sql
DELIMITER $$
DROP PROCEDURE IF EXISTS alter_all $$
CREATE PROCEDURE alter_all()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE _schema VARCHAR(30);
DECLARE cur CURSOR FOR select SCHEMA_NAME from information_schema.SCHEMATA where SCHEMA_NAME like 'division_%';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done := 1;
OPEN cur;
alterLoop: LOOP
FETCH cur into _schema;
if done = 1 THEN
LEAVE alterLoop;
END IF;
SET #mystmt = concat('ALTER TABLE ', _schema, '.caching ADD COLUMN notes VARCHAR(4096)');
PREPARE stmt3 FROM #mystmt;
EXECUTE stmt3;
DEALLOCATE PREPARE stmt3;
END LOOP alterLoop;
CLOSE cur;
END $$
DELIMITER ;
With that, let's try it (using Server version: 5.5.35-0ubuntu0.12.04.2 (Ubuntu)):
> create schema division_1 default character set 'UTF8';
> create table division_1.caching (id int not null auto_increment primary key, value varchar(10));
> create schema division_2 default character set 'UTF8';
> create table division_2.caching (id int not null auto_increment primary key, value varchar(10));
> use division_1;
> source procedure.sql
> CALL alter_all();
Query OK, 0 rows affected, 1 warning (0.05 sec)
> desc caching;
+-------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| value | varchar(10) | YES | | NULL | |
| notes | varchar(4096) | YES | | NULL | |
+-------+---------------+------+-----+---------+----------------+
> desc division_2.caching
+-------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| value | varchar(10) | YES | | NULL | |
| notes | varchar(4096) | YES | | NULL | |
+-------+---------------+------+-----+---------+----------------+

Related

How to update table using execute statement?

I have two tables (much simplified here):
QUADRI
ID SBR_750 b10C TGI
---------------------
Q1 0 1 0
Q2 2 1 0
Q3 1 0 1
CELLE
CELLANAME NEEDED READY
----------------------
SBR_750 NULL 12
b10C NULL 10
TGI NULL 5
I want this result in CELLE:
CELLANAME NEEDED READY
------------------------
SBR_750 3 12
b10C 2 10
TGI 1 5
I tried to write a stored procedure but it doesn't works: ERROR 1210. Incorrect arguments to EXECUTE.
Here is the code:
CREATE DEFINER=`root`#`%.zamberlan.local` PROCEDURE `AggiornaCelle`(IN nomecella varchar(15))
BEGIN
set #s='update celle set needed=(select sum(?) from quadri) where cellaname=?';
set #NC=nomecella;
prepare stmt from #s;
execute stmt using #NC;
deallocate prepare stmt;
END
UPDATE:
It doesn't work so I change strategy:
CREATE DEFINER=`root`#`%.zamberlan.local` PROCEDURE `AggiornaCelle`()
BEGIN
declare i int;
declare num_rows int;
declare col_name varchar(20);
DECLARE col_names CURSOR FOR
SELECT column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = quadri
ORDER BY ordinal_position;
select FOUND_ROWS() into num_rows;
SET i = 1;
the_loop: LOOP
IF i > num_rows THEN
CLOSE col_names;
LEAVE the_loop;
END IF;
FETCH col_names
INTO col_name;
update celle set needed=sum(col_name) where cellaname=col_name;
SET i = i + 1;
END LOOP the_loop;
END
inspired by mysql, iterate through column names.
However I receive the error "Cursor is not open..."
You do need to use dynamic sql to do this. Celle knows about all the columns it needs from quadri so you could drive the creation of the dynamic statements from this fact. Using a cursor is as good a way to do it as any.
drop table if exists quadri;
create table quadri(ID varchar(2),SBR_750 int, b10C int, TGI int);
insert into quadri values
('Q1' , 0 , 1 , 0),
('Q2' , 2 , 1 , 0),
('Q3' , 1 , 0 , 1);
drop table if exists celle;
create table CELLE (CELLANAME varchar(20) ,NEEDED int,READY int);
insert into celle values
('SBR_750' , NULL , 12),
('b10C' , NULL , 10),
('TGI' , NULL , 5);
drop procedure if exists `AggiornaCelle`;
delimiter $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `AggiornaCelle`()
begin
DECLARE done INT DEFAULT FALSE;
declare col_name varchar(20);
declare cur1 CURSOR FOR
SELECT cellaname FROM celle ;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
open cur1;
read_loop: loop
fetch cur1 into col_name;
if done then leave read_loop; end if;
set #sqlstr = concat('update celle set needed = (select sum(',col_name,') from quadri) where cellaname = ', char(39),col_name,char(39),';');
insert into debug_table (msg) values(#sqlstr);
prepare stmt from #sqlstr;
execute stmt ;
deallocate prepare stmt;
end loop;
close cur1;
end $$
delimiter ;
truncate table debug_table;
call `AggiornaCelle`();
select * from debug_table;
select * from celle;
MariaDB [sandbox]> select * from debug_table;
+----+------------------------------------------------------------------------------------------+------+
| id | msg | MSG2 |
+----+------------------------------------------------------------------------------------------+------+
| 1 | update celle set needed = (select sum(SBR_750) from quadri) where cellaname = 'SBR_750'; | NULL |
| 2 | update celle set needed = (select sum(b10C) from quadri) where cellaname = 'b10C'; | NULL |
| 3 | update celle set needed = (select sum(TGI) from quadri) where cellaname = 'TGI'; | NULL |
+----+------------------------------------------------------------------------------------------+------+
3 rows in set (0.00 sec)
MariaDB [sandbox]> select * from celle;
+-----------+--------+-------+
| CELLANAME | NEEDED | READY |
+-----------+--------+-------+
| SBR_750 | 3 | 12 |
| b10C | 2 | 10 |
| TGI | 1 | 5 |
+-----------+--------+-------+
3 rows in set (0.00 sec)
The debug_table only exists so that I can check the update statements.
As per documentation
set #s='update celle set needed=(select sum(?) from quadri) where cellaname=?';
You are supposed to pass two argument.
execute stmt using #NC;
should be or something similar
execute stmt using #NC, #NC;
#NC would be same as you are trying to update a row in celle, which is same as column name in quadri table.

Inserting to selected table name

I have got a table called Aliases in my MySQL database.
Looks like this:
------------------
| Id | Alias |
------------------
|1 | 'TabX' |
------------------
|2 | 'TabY' |
...
| | |
------------------
And I need to insert to those tables like this:
INSERT INTO (SELECT Alias FROM Aliases WHERE id=1) (somevalue) VALUES (value);
This doesn't work. Please help.
You can reach it with prepared statements:
SET #alias = (SELECT Alias FROM Aliases WHERE id = 1);
SET #sql = CONCAT('INSERT INTO ', #alias, ' (somevalue) VALUES (value)');
PREPARE stmt1 FROM #sql;
EXECUTE stmt1;
You need to make PROCEDURE for that. And use prepared statements for this purpose. You need procedure like that:
DELIMITER $$
DROP PROCEDURE IF EXISTS `DB`.`INSERT_VAR`$$
CREATE DEFINER=`user`#`host` PROCEDURE INSERT_VAR(IN tableName VARCHAR(200))
BEGIN
SET #insert_query=CONCAT("INSERT INTO ", tableName, " (id) SELECT id FROM test");
PREPARE stmtInsert FROM #insertTab;
EXECUTE stmtInsert;
END$$
DELIMITER ;
You need to modify this procedure to fit your needs.
Try this
INSERT INTO table_name
SELECT Alias
FROM Aliases
WHERE id = 1

Mirror tables: triggers, deadlock and implicit commits

I'm have 2 similar tables, for example A and B. I want to replicate insertions in A to B, and insertions in B to A to integrate two users systems . I configured "after insert triggers" on each one. Example:
DELIMITER $$
CREATE DEFINER = `root`#`localhost` TRIGGER
`after_A_INSERT`
AFTER INSERT ON `A`
FOR EACH ROW BEGIN
INSERT INTO `B`
SET `id` = NEW.`id`,`name` = NEW.`name`;
END$$
DELIMITER ;
DELIMITER $$
CREATE DEFINER = `root`#`localhost` TRIGGER
`after_B_INSERT`
AFTER INSERT ON `B`
FOR EACH ROW BEGIN
INSERT INTO `A`
SET `id` = NEW.`id`,`name` = NEW.`name`;
END$$
DELIMITER ;
If I insert in A, the triggers calls an insert in B, but this insert executes the trigger in B and a deadlock occurs avoiding a infinite loop.
I've tried to edit triggers to DROP the another table trigger before do the INSERT and then CREATE it again after it. Example:
DELIMITER $$
CREATE DEFINER = `root`#`localhost` TRIGGER
`after_B_INSERT`
AFTER INSERT ON `B`
FOR EACH ROW BEGIN
DROP TRIGGER IF EXISTS `after_A_INSERT`;
INSERT INTO `A`
SET `id` = NEW.`id`, `name` = NEW.`name`;
/* And CREATE again here */
END$$
DELIMITER ;
However CREATE is a Data Definition Language (DDL) statement that makes an implicit commit. Thus, this can't be done.
I've tried to call a PROCEDURE with the DROP inside to handle explicitly the commit, but isn't possible too.
Any suggestion to mirror this 2 tables?
UPDATE: Using Bill Karwin suggestion, I added a origin field to each table with a respective default vale A or B. Then, I alter (DROP and reCREATE) the triggers as follows:
Trigger in A:
...
BEGIN
IF NEW.`origin`='A' THEN
INSERT INTO `B`
SET `id` = NEW.`id`, `name` = NEW.`name`, `origin` = NEW.`origin`;
END IF;
END
Trigger in B:
...
BEGIN
IF NEW.`origin`='B' THEN
INSERT INTO `A`
SET `id` = NEW.`id`, `name` = NEW.`name`, `origin` = NEW.`origin`;
END IF;
END
You need some way to avoid creating a cycle.
I'd suggest adding a column origin in both tables. In table A, make the DEFAULT 'A'. In table B, make the DEFAULT 'B'.
When inserting to either table in your application, always omit the origin column, allowing it to take its default value.
In both triggers, replicate to the other table only if the NEW.origin is equal to the respective table's default.
Re your comment and new error:
Sorry, I forgot to mention that in the trigger when inserting to the other table, you must also copy the value of NEW.origin. Just in your application when you do the original insert do you omit origin.
Trigger in A:
...
BEGIN
IF NEW.`origin`='A' THEN
INSERT INTO `B`
SET `id` = NEW.`id`, `name` = NEW.`name`, `origin` = NEW.`origin`;
END IF;
END
Trigger in B:
...
BEGIN
IF NEW.`origin`='B' THEN
INSERT INTO `A`
SET `id` = NEW.`id`, `name` = NEW.`name`, `origin` = NEW.`origin`;
END IF;
END
I created these triggers and then tested:
mysql> insert into A set name = 'bill';
Query OK, 1 row affected (0.00 sec)
mysql> insert into B set name = 'john';
Query OK, 1 row affected (0.01 sec)
mysql> select * from A;
+----+------+--------+
| id | name | origin |
+----+------+--------+
| 1 | bill | A |
| 2 | john | B |
+----+------+--------+
2 rows in set (0.00 sec)
mysql> select * from B;
+----+------+--------+
| id | name | origin |
+----+------+--------+
| 1 | bill | A |
| 2 | john | B |
+----+------+--------+

find out list of inverted hierarchy in mysql

Imagine I have a table (Mysql myISAM) with a child->parent relationship (categories and multiple levels of subcategories)
+--------+---------+
| id |parent_id|
+--------+---------+
| 1 | null |
| 2 | 1 |
| 3 | 2 |
| 4 | 7 |
| 5 | 1 |
| 6 | 5 |
+--------+---------+
How would you find all children of some ID, like querying for id 1 would output :
2,5,3,6 ? (order has no importance)
So in other words, how to do a reverted children lookup on this parent_link ?
At the moment, I cycle in php and query for the parent_id, then again and concatenate all the results in a string while there are results, but this is so slow...
Ok, so thanks to Deepak code, I managed to write this, a bit shorter readable, it accepts a table as parameter and returns also the depth of the element.
DELIMITER $$
CREATE PROCEDURE get_children(IN V_KEY INT,IN SOURCETABLE VARCHAR(255))
proc:
BEGIN
DECLARE vid text;
DECLARE count int;
DROP TABLE IF EXISTS `temp_child_nodes`;
CREATE TEMPORARY TABLE temp_child_nodes(id int, depth int);
SET vid = V_KEY;
SET #val = '';
SET count = 0;
WHILE (vid is NOT NULL) DO
SET #sql = CONCAT("INSERT INTO temp_child_nodes(id,depth) SELECT id,'",count,"' from ",SOURCETABLE," where parent_id IN (",vid,")");
PREPARE stmt1 FROM #sql;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
SET #tsql = CONCAT("SELECT GROUP_CONCAT(id) INTO #val from ",SOURCETABLE," where parent_id IN (", vid, ")");
PREPARE stmt2 FROM #tsql;
EXECUTE stmt2;
DEALLOCATE PREPARE stmt2;
SET vid = #val;
SET count = count + 1;
END WHILE;
#output data
SELECT * from temp_child_nodes;
END
$$
DELIMITER ;
create table my_table(
id int,
parent_id int
);
insert into my_table values
(1,null),
(2,1),
(3,2),
(4,7),
(5,1),
(6,5);
This stored procedure will get you all the children of any given id
DELIMITER $$
DROP PROCEDURE IF EXISTS get_children$$
CREATE PROCEDURE get_children(IN V_KEY INT)
proc:
BEGIN
DECLARE vid text;
declare oid text;
DECLARE count int;
CREATE TEMPORARY TABLE temp_child_nodes(
id int
);
SET vid = V_KEY;
INSERT INTO temp_child_nodes(id) SELECT id from my_table where parent_id = vid;
SELECT GROUP_CONCAT(concat("'",id,"'")) INTO oid from my_table where parent_id = vid;
SET vid = oid;
SET count = 0;
SET #val = '';
WHILE (vid is NOT NULL) DO
SET #sql = CONCAT("INSERT INTO temp_child_nodes(id) SELECT id from my_table where parent_id IN (",vid,")");
PREPARE stmt1 FROM #sql;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
SET #tsql = CONCAT("SELECT GROUP_CONCAT(id) INTO #val from my_table where parent_id IN (", vid, ")");
PREPARE stmt2 FROM #tsql;
EXECUTE stmt2;
DEALLOCATE PREPARE stmt2;
SET vid = #val;
SET count = count + 1;
END WHILE;
#SELECT count;
SELECT * from temp_child_nodes;
#SELECT oid;
END
$$
DELIMITER ;
CALL get_children(1);
mysql> CALL get_children(1);
+------+
| id |
+------+
| 2 |
| 5 |
| 3 |
| 6 |
+------+
4 rows in set (0.22 sec)
Query OK, 0 rows affected (0.22 sec)
Here is the sqlfiddle demo for your query
http://sqlfiddle.com/#!2/ca90e/6
if there can be 'n' number of child then you need to use a stored procedure

execute query result statements

I've got a set of tables named results_%, all with the same structure.
I would like to add a index to this tables.
I can get the alter statement for each table as a row of a select query result but I don't know how to execute this statements:
select concat( 'alter table ', test_db.table_name, ' add index `did` (`did`);' ) as statement
from information_schema.tables test_db
where test_db.table_name like 'results_%';
What am I missing?
The ouput (which I would like to execute instead of just have it displayed to me):
+---------------------------------------------------------+
| statement |
+---------------------------------------------------------+
| alter table results_Em7777_spa add index `did` (`did`); |
| alter table results_KaEng_eng add index `did` (`did`); |
| alter table results_Ka_spa add index `did` (`did`); |
| alter table results_Mc_spa add index `did` (`did`); |
| alter table results_Mo_eng add index `did` (`did`); |
| alter table results_Pe_eng add index `did` (`did`); |
| alter table results_SU_spa add index `did` (`did`); |
| alter table results_Ta_spa add index `did` (`did`); |
| alter table results_ba_eng add index `did` (`did`); |
| alter table results_br_eng add index `did` (`did`); |
| alter table results_ca_spa add index `did` (`did`); |
| alter table results_ch_spa add index `did` (`did`); |
| alter table results_da_spa add index `did` (`did`); |
| alter table results_ga_eng add index `did` (`did`); |
| alter table results_ge_spa add index `did` (`did`); |
| alter table results_gk_eng add index `did` (`did`); |
+---------------------------------------------------------+
16 rows in set (0.00 sec)
[EDIT]
I tried:
drop procedure if exists altlike;
delimiter //
create procedure altlike()
begin
set group_concat_max_len = 65535;
select #altrlk:= concat( 'alter table ', test_db.table_name , ' add index `did` (`did`);' )
from information_schema.tables test_db
where test_db.table_name like "results_%";
prepare statement from #altrlk;
execute statement;
end //
delimiter ;
call altlike();
But still no luck: It only alters the last matched table (results_gk_eng).
drop procedure if exists `altlike`;
DELIMITER //
CREATE PROCEDURE `altlike` ()
BEGIN
DECLARE a,c VARCHAR(256);
DECLARE b INT;
DECLARE cur1 CURSOR FOR select concat(test_db.table_name)
from information_schema.tables test_db
where test_db.table_name like 'results_%';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET b = 1;
DECLARE CONTINUE HANDLER FOR 1061 SET b = 0;
OPEN cur1;
SET b = 0;
WHILE b = 0 DO
FETCH cur1 INTO a;
IF b = 0 THEN
SET #c = concat ('ALTER IGNORE TABLE `', a, '` ADD INDEX `did` (`did`)');
PREPARE stmt1 FROM #c;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
END IF;
END WHILE;
CLOSE cur1;
END //
call altlike();
You basically printing out lines of string out of the DB, it would not automatically execute it just because it looks like a sql statement;
What you can do is either use a programming language to execute line by line as you get the results back.
Or throw this into a stored procedure where it feed a secondary block of execution.
Example: FROM http://net.tutsplus.com/tutorials/an-introduction-to-stored-procedures/ read more about it.
DELIMITER //
CREATE PROCEDURE `proc_CURSOR` (OUT param1 INT)
BEGIN
DECLARE a, b, c INT;
DECLARE cur1 CURSOR FOR SELECT col1 FROM table1;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET b = 1;
OPEN cur1;
SET b = 0;
SET c = 0;
WHILE b = 0 DO
FETCH cur1 INTO a;
IF b = 0 THEN
SET c = c + a;
END IF;
END WHILE;
CLOSE cur1;
SET param1 = c;
END //