find out list of inverted hierarchy in mysql - 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

Related

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.

execute expression stored in a column

I need some help about executing expression(bitwise) stored in a column of a table.
Input :
ID | expression
----|-------------
1 | 1&0
2 | (1&1)|(0&1)
Desired Output :
ID | expression | value
----|-------------|-------
1 | 1&0 | 0
2 | (1&1)|(0&1) | 1
I am trying something like below but it is not executing the expression.
PREPARE stmt from 'select ? into #outvar';
set #invar = '1&0';
execute stmt using #invar;
select #outvar;
The output of above is 1&0 but the desired output is 0.
Actually I want to store the output in a variable as framed in my above pseudo code.
I have built you a procedure to do this
DROP TABLE IF EXISTS test10;
DROP PROCEDURE IF EXISTS `sp_test10`;
create table test10 (ID int, expression VARCHAR(20));
insert into test10 values ( 1 , '1&0');
insert into test10 values ( 2 , '(1&1)|(0&1)');
CREATE PROCEDURE sp_test10 (IN IN_ID int, OUT OUT_VAL INT)
BEGIN
SELECT expression INTO #exp FROM test10 WHERE ID=IN_ID;
SET #q = CONCAT('SELECT ',#exp,' INTO #exp ');
PREPARE stmt1 FROM #q;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
SET OUT_VAL = #exp;
END\\
;
call sp_test10(2,#result);
select #result;
Results :
call sp_test10(1,#result) returns 0
call sp_test10(2,#result) returns 1
http://rextester.com/live/SMLB31207
Using prepared statements, what are sent to server in the second round are treated literally. They can't be part of SQL. You shouldn't use placeholders for this:
SET #invar = '(1&1)&(0&1)';
SET #s = CONCAT('SELECT ', #invar, ' INTO #outvar');
PREPARE stmt FROM #s;
EXECUTE stmt;
SELECT #outvar;

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.

MYSQL Procedure call saved query in table

That I need is
Save all of query in one table
query_id | sql_query
1 | select * from ms_user
2 | select * from ms_privileges
Create Procedure
this procedure will call
call my_procedure(query_id)
I tried the code below
DROP PROCEDURE IF EXISTS mp_test;
CREATE PROCEDURE global_procedire(IN id_query int(10));
BEGIN
SET #Query = 'SELECT sql_query from ms_query where query_id = id_query';
SET #Query = CONCAT(#Query);
PREPARE stmt FROM #Query;
EXECUTE stmt;
END

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