How to update table using execute statement? - mysql

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.

Related

Run different SQL statements using If Then with a variable

Trying to do something very simple. Using If/Then, is there a way to run a separate Select statement based on the value of a variable?
The function GetTotalActiveUnits() in the below code returns an integer.
set #RetVal = GetTotalActiveUnits(CustomerCode);
if #RetVal = 0 then
Select * from tblREF_UnitInfo;
else
select * from tblREF_State;
end if
There is no problem with your code - at least as far as IF is concerned and if it is in a stored routine.
drop procedure if exists p;
delimiter $$
create procedure p(inp int)
begin
set #RetVal = inp;# GetTotalActiveUnits(CustomerCode);
if #RetVal = 0 then
select #retval ;
else
select #retval ;
end if ;
end $$
delimiter ;
call p(1);
+---------+
| #retval |
+---------+
| 1 |
+---------+
1 row in set (0.001 sec)
call p(0)
+---------+
| #retval |
+---------+
| 0 |
+---------+
1 row in set (0.001 sec)

MySQL use a wild card in table name

So I have a software that store data on those tables. I know how those tables start but there will be always a suffix to them that's a number which I have no idea to know
example of those table name is "itemid5_4423"
I know there is a table with the name itemid5 but i have no way to know the suffix number
is there a wild card something similar to this logic select * from itemid5_*;
Let's say you have 2 tables like this:
create table itemid5_1111 (id int, itemname varchar(100));
create table itemid5_2222 (id int, itemname varchar(100));
You insert data into them:
insert into itemid5_1111 values (1, 'first table');
insert into itemid5_2222 values (2, 'second table');
Your goal is to get output like this from all itemid5* tables.
+------+--------------+
| id | itemname |
+------+--------------+
| 1 | first table |
| 2 | second table |
+------+--------------+
You can do that by typing:
select * from itemid5_1111
union all select * from itemid5_2222;
But, that's a lot of manual typing. You can make a stored procedure to dynamically query table names starting with itemid5 and then create a SQL dynamically and execute it.
Stored procedure
delimiter $$
drop procedure if exists get_items$$
create procedure get_items()
begin
declare eof boolean default false;
declare mytable varchar(255);
declare first_run boolean default true;
declare tablenames_cursor cursor for
select table_name from information_schema.tables
where table_name like 'itemid%';
declare continue handler for not found
set eof = true;
set #my_query = '';
open tablenames_cursor;
read_loop: loop
fetch tablenames_cursor into mytable;
if eof then
leave read_loop;
end if;
if first_run then
set #my_query = concat('select * from ', mytable);
set first_run = false;
else
set #my_query = concat(#my_query, ' union all ', 'select * from ', mytable);
end if;
end loop;
close tablenames_cursor;
prepare stmt from #my_query;
execute stmt;
deallocate prepare stmt;
end$$
delimiter ;
You call this procedure like so to get your results:
call get_items();
If you created a 3rd table like so:
create table itemid5_3333 (id int, itemname varchar(100));
insert into itemid5_3333 values (3, 'third table');
And then, you called the proc, you'd get
call get_items();
+------+--------------+
| id | itemname |
+------+--------------+
| 1 | first table |
| 2 | second table |
| 3 | third table |
+------+--------------+
i think using the data dictionary to retrieve the result would help, run this.
select * from information_schema.tables where table_name like 'itemid5_% ';
you can choose the columns you want that this query outputs, table_name is one of them you need, like we used it in where clause.
SELECT
REPLACE
(
GROUP_CONCAT(
CONCAT("SELECT * FROM ", `TABLE_NAME`)
),
",",
" UNION ALL "
)
INTO #sq
FROM
information_schema.tables
WHERE
`TABLE_SCHEMA` = "test";
USE
test;
PREPARE
stmt1
FROM
#sq;
EXECUTE
stmt1;
DELIMITER //
CREATE PROCEDURE merge_tables(IN in_sname VARCHAR(64),IN in_tname VARCHAR(64))
READS SQL DATA
BEGIN
DECLARE sname VARCHAR(64);
DECLARE tname VARCHAR(64);
DECLARE cname VARCHAR(64);
DECLARE done INT DEFAULT FALSE;
DECLARE table_cur CURSOR FOR SELECT table_schema, table_name FROM
information_schema.TABLES WHERE table_schema = in_sname AND table_name LIKE
'table%';
DECLARE column_cur CURSOR FOR SELECT `COLUMN_NAME` FROM
`INFORMATION_SCHEMA`.`COLUMNS` where table_schema = in_sname and table_name
= in_tname;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
-- build column list (Using the column list for table listed in second
parameter in PROC Call)
SET #column = '';
OPEN column_cur;
column_cur_loop: LOOP
FETCH column_cur INTO cname;
IF done THEN
-- SET #column := CONCAT(#column, ') ');
LEAVE column_cur_loop;
END IF;
IF #column = '' THEN
SET #column := CONCAT(#column,cname);
ELSE
SET #column := CONCAT(#column,',',cname);
END IF;
END LOOP;
CLOSE column_cur;
-- Build UNION Query for all table starting with table%)
SET done = FALSE;
SET #sql = '';
OPEN table_cur;
table_list_loop: LOOP
FETCH table_cur INTO sname, tname;
IF done THEN
LEAVE table_list_loop;
END IF;
IF #sql = '' THEN
SET #sql := CONCAT('INSERT INTO MERGED_TABLE (', #column , ') SELECT
', #column , ' FROM `', sname, '`.`', tname, '`');
ELSE
SET #sql := CONCAT(#sql, ' UNION ALL SELECT ' , #column , ' FROM `',
sname, '`.`', tname, '`');
END IF;
END LOOP;
CLOSE table_cur;
PREPARE stmt FROM #sql; -- prepare and execute the dynamically
EXECUTE stmt; -- created query.
DEALLOCATE PREPARE stmt;
END //
DELIMITER ;`
call merge_tables(testdb,table1)
testdb is Schema Name where tables reside
table1 is one of the tables which needs to be merged to get column names
table% in the procedure is the prefix of the all the tables that needs to be merged.

How to create tables with same table structure from an existed one in mysql?

How can I automatically create say 100 tables from an existed table. Tables names would like to be table1, table2, table3...table100. I don't want to execute sql:"create bable table2 like table1" 100 times. Thanks in advance.
You can create a stored procedure and call it once. Here is an example of creating 5 table with name test1...test5 from a table test
Change the loop value and table name in the below procedure
delimiter //
create procedure `create_tables`()
begin
declare x INT ;
set x = 1 ;
while x <= 5 do
set #qry = concat("create table test",x," like test ");
prepare stmt from #qry;
execute stmt ;
deallocate prepare stmt ;
set x = x + 1;
end while;
end ;//
delimiter ;
Finally call the procedure as
call create_tables();
Then run the command to see the tables
show tables;
Here is a test case in mysql cli
mysql> delimiter //
mysql> create procedure `create_tables`()
-> begin
-> declare x INT ;
-> set x = 1 ;
-> while x <= 5 do
-> set #qry = concat("create table test",x," like test ");
-> prepare stmt from #qry;
-> execute stmt ;
-> deallocate prepare stmt ;
-> set x = x + 1;
-> end while;
-> end ;//
Query OK, 0 rows affected (0.22 sec)
mysql> delimiter ;
mysql> call create_tables() ;
Query OK, 0 rows affected (0.74 sec)
mysql> show tables ;
+-----------------------+
| Tables_in_test |
+-----------------------+
| test |
| test1 |
| test2 |
| test3 |
| test4 |
| test5 |
+-----------------------+

Run MySQL queries on different databases

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 | |
+-------+---------------+------+-----+---------+----------------+

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