MYSQL Stored Procedure failed on second run - mysql

I have a stored procedure to create a roster table. Source is a view which collects the needed data. The stored procedure should prepare the data to be displayed to the team. Either for a specific team member or an overview for all. When I call the procedure the first time anything is fine and I receive the data as expected. But on the second run with a different parameter it fails with an error 'SQL Error [1054] [42S22]: (conn:218) Unknown column 'd0294220.duties.TM 3' in 'field list' Query is : call somp_roster('Ritchie')'
The missing column depends on the parameter in the first run.
The view collects the data from 4 tables:
CREATE VIEW somv_roster AS
SELECT bd.businessday AS Datum,
v.venue AS Dropzone,
d.duty AS Service,
c.nickname AS Staff
FROM som_roster r
INNER JOIN som_businessday bd ON bd.businessdayID = r.ID_businessday
INNER JOIN som_venue v ON v.venueID = bd.ID_venue
INNER JOIN som_duty d ON d.dutyID = r.ID_duty
INNER JOIN som_contact c ON c.contactID = r.ID_contact
ORDER BY bd.businessday, v.venue
WHERE bd.businessday >= CURDATE();
Result looks like this:
Datum Dropzone Service Staff
2018-04-28 Illertissen TM 2 Manu
2018-04-28 Illertissen Packer Martina B.
2018-04-28 Illertissen TM 1 Rued
2018-04-28 Illertissen TM 3 Hane
2018-04-29 Illertissen Manifest Sissi
2018-04-29 Illertissen TM 2 Ritchie
2018-04-29 Illertissen Packer Martina B.
2018-04-29 Illertissen TM 1 Rued
2018-04-29 Illertissen TM 3 Hane
2018-05-01 Illertissen TM 1 Ritchie
2018-05-01 Illertissen TM 3 Hane
2018-05-01 Illertissen TM 2 Purzl
2018-05-01 Illertissen Packer Martina B.
2018-05-26 Illertissen TM 1 Rued
2018-05-26 Kempten TM 1 Ritchie
2018-05-26 Kempten TM 2 Hane
2018-05-26 Kempten Manifest Sissi
Now I'm going to build a new table which should show this:
Datum Dropzone Manifest Packer TM 1 TM 2 TM 3
2018-04-28 Illertissen [NULL] Martina B. Rued Manu Hane
2018-04-29 Illertissen Sissi Martina B. Rued Ritchie Hane
2018-05-01 Illertissen [NULL] Martina B. Ritchie Purzl Hane
2018-05-26 Illertissen [NULL] [NULL] Rued [NULL] [NULL]
2018-05-26 Kempten Sissi [NULL] Ritchie Hane [NULL]
Or for a specific team member it should look like this:
Datum Dropzone TM 2 TM 3
2018-04-28 Illertissen [NULL] Hane
2018-04-29 Illertissen [NULL] Hane
2018-05-01 Illertissen [NULL] Hane
2018-05-26 Kempten Hane [NULL]
In the stored procedure I define a cursor and loop through that cursor. In a temp table I add the results and display the table at the end.
The stored procedure:
/*
* Create routine somp_roster()
* Requires somp_roster_et() to expand the columns dynamically.
* Requires somv_roster view to read the rosters.
* 4Air 2018 (c)
*/
DROP PROCEDURE IF EXISTS somp_roster;
DELIMITER $$
CREATE PROCEDURE somp_roster(IN staff_name VARCHAR(25))
BEGIN
/*
* variables have to be declared at the start of the procedure
* declare variables for the loops
* flag to be set at the end of the loop
*/
DECLARE finishedloop INTEGER DEFAULT 0;
-- flag to loop through the duty table to
DECLARE temp_date DATE DEFAULT NULL;
DECLARE temp_venue VARCHAR(25) DEFAULT '';
DECLARE temp_duty VARCHAR(25) DEFAULT '';
DECLARE temp_staff VARCHAR(25) DEFAULT '';
-- this var holds the string for the alter table statement
DECLARE alttable VARCHAR(250) DEFAULT '';
-- the first cursor to prepare the temp table
DECLARE roster_cursor CURSOR FOR
SELECT * FROM somv_roster WHERE Datum >= CURDATE() AND Staff LIKE CONCAT('%',staff_name) ORDER BY Service;
-- to recognize the end of the loops. Set to 0 before starting the loop!
DECLARE CONTINUE HANDLER FOR NOT FOUND SET finishedloop = 1;
-- cleanup first from a interrupted previous run
DROP TEMPORARY TABLE IF EXISTS duties;
-- now we need a temp table for the duties
CREATE TEMPORARY TABLE duties (Datum DATE, Dropzone VARCHAR(25), PRIMARY KEY(Datum, Dropzone)) ENGINE=MEMORY DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
-- loop through the temp table
SET finishedloop = 0;
OPEN roster_cursor;
SET #alttable = '';
get_roster_loop: LOOP
-- now read the duties and create the new rows in the temp rosters
FETCH roster_cursor INTO temp_date, temp_venue, temp_duty, temp_staff;
IF finishedloop = 1 THEN
LEAVE get_roster_loop;
END IF;
/*
* fist step - try to expand the table with needed column
* the statement has to be this:
* call somp_rosters_et('tablename','columnname','columndefs')
* example:
* CALL somp_rosters_et('duties','TM 1','VARCHAR(25)');
*/
CALL somp_roster_et('duties',temp_duty,'VARCHAR(25)');
/*
* next prepare the statement to fill the data...
* implies the test if the date and venue already exists
* if so, then use UPDATE TABLE rather then INSERT TABLE
*/
SET #alttable = CONCAT('INSERT INTO duties (`Datum`,`Dropzone`,`', temp_duty , '`) VALUES (''' , temp_date , ''',''' , temp_venue , ''',''' , temp_staff , ''') ON DUPLICATE KEY UPDATE `', temp_duty , '` = ''', temp_staff , ''';') ;
PREPARE stmt FROM #alttable;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP get_roster_loop;
CLOSE roster_cursor;
SELECT * FROM duties ORDER BY Datum, Dropzone;
DROP TEMPORARY TABLE IF EXISTS duties;
END $$
DELIMITER ;
The second stored procedure to add the needed column to the temp table:
I build this to have an second handler for the sql exception when I try to add an existing column.
/*
* Stored Procedure to expand the table and do not care about an error if the column exists.
* Used by somp_roster();
* 4Air 2018 (c).
*/
DROP PROCEDURE IF EXISTS somp_roster_et;
DELIMITER $$
CREATE PROCEDURE somp_roster_et(IN t_name VARCHAR(25), IN c_name VARCHAR(25), IN c_attribute VARCHAR(25))
BEGIN
DECLARE eflag INTEGER DEFAULT 0;
DECLARE modtable VARCHAR(150) DEFAULT "";
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET eflag = 1;
SET #modtable = CONCAT('ALTER TABLE ' , t_name , ' ADD COLUMN `' , c_name , '` ' , c_attribute , ';');
-- INSERT INTO som_log (Entry) VALUES(CONCAT('somp_roster_et: ',#modtable));
PREPARE stmt FROM #modtable;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END $$
DELIMITER ;
This works for the first run. On the second run with a different team member as a parameter for the first procedure the SELECT statement at the end of the procedure throws an error:
SQL Error [1054] [42S22]: (conn:218) Unknown column 'd0294220.duties.TM 3' in 'field list' Query is : call somp_roster('Ritchie')
When I remove the last SELECT from the procedure and also remove the drop table command I can receive the data manually:
call somp_roster('Ritchie');
SELECT * FROM duties;
call somp_roster('Martina B.');
SELECT * FROM duties;
This works fine. Why do I get the exception when I select the data inside the procedure?
Richard
2018-dec-04
Still fighting with this... I've changed the procedure a bit but the strange behavior is still present.
New procedure:
/*
* Create routine somp_roster()
*
* 2018-02-15 / implement a second handler for sql exceptions to handle the expand and select in this script
*
* somp_roster_et is depricated with this change.
*
* Requires somv_roster view to read the rosters.
* 4Air 2018 (c)
*/
DROP PROCEDURE IF EXISTS somp_roster;
DELIMITER $$
CREATE PROCEDURE somp_roster(IN staff_name VARCHAR(25))
BEGIN
/*
* variables have to be declared at the start of the procedure
* declare variables for the loops
* flag to be set at the end of the loop
*/
DECLARE finishedloop,eflag, loopcount INTEGER DEFAULT 0;
-- flag to loop through the duty table to
DECLARE temp_date DATE DEFAULT NULL;
DECLARE temp_venue, temp_duty, temp_staff VARCHAR(25) DEFAULT '';
-- this var holds the string for the alter table statement
DECLARE alttable,modtable VARCHAR(250) DEFAULT '';
-- the first cursor to prepare the temp table
DECLARE roster_cursor CURSOR FOR
SELECT * FROM somv_roster WHERE date >= CURDATE() AND staff LIKE CONCAT('%',staff_name) ORDER BY service;
-- to recognize the end of the loops. Set to 0 before starting the loop!
DECLARE CONTINUE HANDLER FOR NOT FOUND SET finishedloop = 1;
-- to recognize a sql exception and continue
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET eflag = 1;
-- cleanup first from a interrupted previous run
DROP TEMPORARY TABLE IF EXISTS duties;
-- now we need a temp table for the duties
CREATE TEMPORARY TABLE duties (Datum DATE, Dropzone VARCHAR(25), PRIMARY KEY(Datum, Dropzone)) ENGINE=MEMORY DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
-- loop through the temp table
SET finishedloop = 0;
OPEN roster_cursor;
SET #alttable = '';
get_roster_loop: LOOP
-- now read the duties and create the new rows in the temp rosters
FETCH roster_cursor INTO temp_date, temp_venue, temp_duty, temp_staff;
IF finishedloop = 1 THEN
LEAVE get_roster_loop;
END IF;
SET #modtable = CONCAT('ALTER TABLE duties ADD COLUMN `' , temp_duty , '` VARCHAR(25);');
PREPARE stmt FROM #modtable;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
/*
* next prepare the statement to fill the data...
* implies the test if the date and venue already exists
* if so, then use UPDATE TABLE rather then INSERT TABLE
*/
SET #alttable = CONCAT('INSERT INTO duties (`Datum`,`Dropzone`,`', temp_duty , '`) VALUES (''' , temp_date , ''',''' , temp_venue , ''',''' , temp_staff , ''') ON DUPLICATE KEY UPDATE `', temp_duty , '` = ''', temp_staff , ''';') ;
PREPARE stmt FROM #alttable;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP get_roster_loop;
CLOSE roster_cursor;
SELECT * FROM duties ORDER BY Datum, Dropzone;
DROP TEMPORARY TABLE IF EXISTS duties;
END $$
DELIMITER ;
Now I changed the temporary table to a persistent INNODB table to see what the procedure creates and this is always the result I expect. Now I change the SELECT statement at the end to this:
SELECT COUNT(*) FROM duties;
and I always get the correct count.
Any ideas what is the reason for this?
Richard

Related

MYSQL concat char to create table name with select

I tried to use such code
select
price
from
(select concat("_",`id`) -- to set table name
as
dta
from
druk
where
date >= '2021-02-01' and date < '2021-03-01') d
If I put * instead price I get for example "_5438" - table name. One or more. In this way I can't get price.
I tried to use variables from examples I found, but some of them mysql do'es not recognize. What should I do to make proper, dynamic table name with concat?
You can use dynamic sql for this.
First you get the table nane into #dta and after that constructs the actual query which you run as prepare statements
But this onlöy works, if your forwst select only goves back 1 eow as result, that is why it is LIMIT 1
SELECT concat("_",`id`)
INTO
#dta
FROM
druk
WHERE
date >= '2021-02-01' AND `date` < '2021-03-01' LIMIT 1;
SET #sql := CONCAT('SELECT price FROM ',#dta );
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Still this does look like a poor design. to have many tables with the same design
For multiple ids you need a stored procedure, it can look like this).
But i didn_'t test it
CREATE TABLE druk(`id` INT , `date` date)
INSERT INTO druk VALUES (1,now()),(2,now())
CREATE TABLE _1 (price DECIMAL(8,3))
INSERT INTO _1 VALUE (2.3),(4.6),(7.9)
CREATE TABLE _2 (price DECIMAL(8,3))
INSERT INTO _2 VALUE (0.3),(1.6),(3.9)
CREATE PROCEDURE createpricelist( IN _datefrom varchar(29),IN _dateto varchar(20)
)
BEGIN
DECLARE finished INTEGER DEFAULT 0;
DECLARE _id VARCHAR(29);
DEClARE curid
CURSOR FOR
SELECT concat("_",`id`) -- to set table name
FROM
druk
WHERE
`date` >= _datefrom AND `date` < _dateto;
-- declare NOT FOUND handler
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET finished = 1;
SET #datefrom := _datefrom;
SET #dateto := _dateto;
DROP TEMPORARY TABLE IF EXISTS tableprice;
CREATE TEMPORARY TABLE tableprice (price DECIMAL(8,2));
OPEN curid;
getprice: LOOP
FETCH curid INTO _id;
IF finished = 1 THEN
LEAVE getprice;
END IF;
SET #od = _id;
SET #sql := CONCAT('INSERT INTO tableprice (price) select price from ',_id);
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP getprice;
CLOSE curid;
-- Diplay temporary table you can use it outside the procedure
SELECT * FROM tableprice;
END
✓
CALL createpricelist(NOW() -INTERVAL 1 DAY,NOW())
| price |
| ----: |
| 2.30 |
| 4.60 |
| 7.90 |
| 0.30 |
| 1.60 |
| 3.90 |
✓
db<>fiddle here

MySQL - Stored Procedure

I need to fetch data from the stored procedure and present it in a dashboard. So the background for this is.
For every select query in the Stored procedure will return only one record I've around 10 queries like that. Apart from that I've also a query which will return say some 5 rows in the same stored procedure.
I want this 5 records to be presented as 1 line along with the other queries in the same stored procedure.
Example:
CALL dashboard_proc() Will return
Date Amount total_orders
1-1-2017 500.00 50
A part from above output I want to append another select query to the same stored procedure (dashboard_proc). The other query which will return
Place Total
Hyderabad 100
Bangalore 100
Chennai 200
Mumbai 100
And I need below
Date Amount total_orders Hyderabad Bangalore Chennai Mumbai
1-1-2016 500.00 50 100 100 200 100
Based on your inputs:
First Part of the report has only one record so I have used the below structure
CREATE TABLE `ResultPart1` (
`ReportDate` date NOT NULL,
`Amount` float DEFAULT NULL,
`total_orders` int(11) DEFAULT NULL
)
insert into ResultPart1 select '2017-01-01', 500.00 , 50;
2. Second Part of the report will have distinct Places, here the places used are single word and no keyword used.
CREATE TABLE `ResultPart2` (
`Place` varchar(50) NOT NULL,
`Total` int(11) DEFAULT NULL
)
insert into ResultPart2()
Select 'Hyderabad', 100 union
Select 'Bangalore' , 100 union
Select 'Chennai' , 200 union
Select 'Mumbai' , 100;
3. Create Stored Procedure
CREATE PROCEDURE `new_procedureDashboard`()
BEGIN
Declare vPlaceCount int;
Declare vquery varchar(1000);
declare vPlace varchar(1000);
declare vPlaceTotal int;
-- select * from ResultPart1;
-- Select * from ResultPart2;
CREATE TEMPORARY TABLE IF NOT EXISTS Table_ResultPart2
(
Id int NOT NULL AUTO_INCREMENT,
Place varchar(100),
PlaceTotal int ,
PRIMARY KEY (id)
);
insert into Table_ResultPart2(Place,PlaceTotal)
Select distinct Place,Total from ResultPart2;
SET vPlaceCount=(Select count(*)from Table_ResultPart2);
WHILE vPlaceCount>0 DO
SET vPlace=(Select Place from Table_ResultPart2 where Id=vPlaceCount);
SET vPlaceTotal=(Select PlaceTotal from Table_ResultPart2 where Id=vPlaceCount);
SET vquery=concat("", "alter table ResultPart1 add " ,vPlace ," int ;");
Select vquery into #AddColumn;
PREPARE stmt FROM #AddColumn;
-- Select #AddColumn;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET vquery="";
-- Select vPlace,vPlaceTotal;
-- Update
SET SQL_SAFE_UPDATES = 0;
SET vquery=concat("", "update ResultPart1 SET " ,vPlace ," = " ,vPlaceTotal ," where 1=1 ;");
Select vquery into #UpdateColumn;
-- select #UpdateColumn;
PREPARE stmt1 FROM #UpdateColumn;
-- Select #AddColumn;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
SET SQL_SAFE_UPDATES = 1;
SET vPlaceCount=vPlaceCount-1;
SET vPlace="" ;
END WHILE;
Select * from ResultPart1;
drop TEMPORARY TABLE IF EXISTS Table_ResultPart2;
END
Explanation:
Added the required column in the main resultant table using loop, and updated the values of the newly place(added) column using the loop. You will require to make changes in your stored procedure as I have used permanent tables (ResultPart1 and ResultPart2 ) .
4. Result
Alter Statements to drop column Just in case you want to rerun the stored procedure.
alter table ResultPart1 drop column Hyderabad;
alter table ResultPart1 drop column Bangalore;
alter table ResultPart1 drop column Chennai;
alter table ResultPart1 drop column Mumbai;

MySQL create view across all prefixed databases' table

I have databases named company_abc, company_xyz, etc. Those company_* databases have all the same structure and they contain users table.
What I need to do is to aggregate all users data from just company_* databases and replicate this view to another server. The view would just be something like
COMPANY NAME | USERNAME
abc | user#email.com
abc | user1#email.com
xyz | user2#email.com
company3 | user3#email.com
Is something like that possible in MySQL?
The databases are created dynamically, as well as the users so I can't create a view with just a static set of databases.
As you say you want to create view with dynamic database names - so the result you want to achieve is not possible in current versions of mysql.
So you have example following options:
Option 1
If you want to get result of all databases users tables you could define a stored procedure that uses prepared statement. This procedure needs parameter db_prefix what in your case is company_%. Basicly this procedure selects all tables named as users from information_schema when database name is like db_prefix parameter value. After that it loops through results and creates query string as union all users tables and executes this query. When creating a query string i also add field called source, so i can identify from what database this result is coming. In my example my databases are all in default collation utf8_unicode_ci.
In this case you can define procedure example "getAllUsers"
-- Dumping structure for procedure company_abc1.getAllUsers
DELIMITER //
CREATE DEFINER=`root`#`localhost` PROCEDURE `getAllUsers`(IN `db_prefix` TEXT)
DETERMINISTIC
COMMENT 'test'
BEGIN
DECLARE qStr TEXT DEFAULT '';
DECLARE cursor_VAL VARCHAR(255) DEFAULT '';
DECLARE done INTEGER DEFAULT 0;
DECLARE cursor_i CURSOR FOR SELECT DISTINCT (table_schema) FROM information_schema.tables WHERE table_name = 'users' AND table_schema LIKE db_prefix COLLATE utf8_unicode_ci;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cursor_i;
read_loop: LOOP
FETCH cursor_i INTO cursor_VAL;
IF done = 1 THEN
LEAVE read_loop;
END IF;
IF qStr != '' THEN
SET qStr = CONCAT(qStr, ' UNION ALL ');
END IF;
SET qStr = CONCAT(qStr, ' SELECT *, \'', cursor_VAL ,'\' as source FROM ', cursor_VAL, '.users');
END LOOP;
CLOSE cursor_i;
SET #qStr = qStr;
PREPARE stmt FROM #qStr;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET #qStr = NULL;
END//
DELIMITER ;
Now you can get your all users result as:
CALL getAllUsers('company_%');
In my example database it results as:
id name source
1 User 1 company_abc1
2 User 2 company_abc1
3 User 3 company_abc1
1 User 1 company_abc2
2 User 2 company_abc2
3 User 3 company_abc2
1 User 1 company_abc3
2 User 2 company_abc3
3 User 3 company_abc3
1 User 1 company_abc4
2 User 2 company_abc4
3 User 3 company_abc4
1 User 1 company_abc5
2 User 2 company_abc5
3 User 3 company_abc5
Option 2
If you really, really need view then you can modify first procedure and instead of executeing select you can create view. Example like this:
-- Dumping structure for procedure company_abc1.createAllUsersView
DELIMITER //
CREATE DEFINER=`root`#`localhost` PROCEDURE `createAllUsersView`(IN `db_prefix` TEXT)
DETERMINISTIC
COMMENT 'test'
BEGIN
DECLARE qStr TEXT DEFAULT '';
DECLARE cursor_VAL VARCHAR(255) DEFAULT '';
DECLARE done INTEGER DEFAULT 0;
DECLARE cursor_i CURSOR FOR SELECT DISTINCT (table_schema) FROM information_schema.tables WHERE table_name = 'users' AND table_schema LIKE db_prefix COLLATE utf8_unicode_ci;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cursor_i;
read_loop: LOOP
FETCH cursor_i INTO cursor_VAL;
IF done = 1 THEN
LEAVE read_loop;
END IF;
IF qStr != '' THEN
SET qStr = CONCAT(qStr, ' UNION ALL ');
END IF;
SET qStr = CONCAT(qStr, ' SELECT *, \'', cursor_VAL ,'\' as source FROM ', cursor_VAL, '.users');
END LOOP;
CLOSE cursor_i;
SET #qStr = CONCAT('CREATE OR REPLACE VIEW allUsersView AS ', qStr);
PREPARE stmt FROM #qStr;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET #qStr = NULL;
END//
DELIMITER ;
In this stored procedure we create/replace view called allUsersView, so basicly every time you will execute this procedure it will updates view.
In my test case it creates view like this:
CREATE OR REPLACE VIEW `allusersview` AS
SELECT *, 'company_abc1' as source FROM company_abc1.users
UNION ALL SELECT *, 'company_abc2' as source FROM company_abc2.users
UNION ALL SELECT *, 'company_abc3' as source FROM company_abc3.users
UNION ALL SELECT *, 'company_abc4' as source FROM company_abc4.users
UNION ALL SELECT *, 'company_abc5' as source FROM company_abc5.users ;
And now you can use view.
SELECT * FROM allusersview
And result is same as in first option.
All tested on:
Mysql 5.6.16
To find the list of database names:
SELECT SCHEMA_NAME
FROM information_schema.`SCHEMATA`
WHERE SCHEMA_NAME LIKE 'company%';
If you can code in something like PHP, the rest is pretty easy -- build a UNION of SELECTs from each database. But, if you must do it just in SQL...
To build the UNION, write a Stored Procedure. It will do the above query in a CURSOR. Inside the loop that walks through the cursor, CONCAT() a constructed SELECT onto a UNION you are building.
When the loop is finished, PREPARE and EXECUTE the constructed UNION. That will deliver something like the output example you had.
But, if you now need to INSERT the results of that into another server, you should leave the confines of the Stored Procedure and use some other language.
OK, OK, if you must stay in SQL, then you need some setup: Create a "Federated" table that connects to the other server. Now, in your SP, concatenate INSERT INTO fed_tbl in front of the UNION. Then the execute should do the entire task.
If you have trouble with the FEDERATED Engine, you may need to switch to FederatedX in MariaDB.
"The details are left as an exercise to the reader."
I already marked this as duplicate of Mysql union from multiple database tables
(SELECT *, 'abc' as COMPANY_NAME from company_abc.users)
union
(SELECT *, 'xyz' as COMPANY_NAME from company_xyz.users)
union
(SELECT *, 'company3' as COMPANY_NAME from company_company3.users)
...
I think that the only method to make this is to write a stored procedure that read all database and table name from information_schema.table, build a string with union select * from company_abc.users union all select * from company_xyz and then execute the command with prepared statement: http://dev.mysql.com/doc/refman/5.0/en/sql-syntax-prepared-statements.html

MySql Recursive Query Alternative? [duplicate]

I have the following table:
id | parent_id | quantity
-------------------------
1 | null | 5
2 | null | 3
3 | 2 | 10
4 | 2 | 15
5 | 3 | 2
6 | 5 | 4
7 | 1 | 9
Now I need a stored procedure in mysql that calls itself recursively and returns the computed quantity.
For example the id 6 has 5 as a parent which as 3 as a parent which has 2 as a parent.
So I need to compute 4 * 2 * 10 * 3 ( = 240) as a result.
I am fairly new to stored procedures and I won't use them very often in the future because I prefer having my business logic in my program code rather then in the database. But in this case I can't avoid it.
Maybe a mysql guru (that's you) can hack together a working statement in a couple of seconds.
its work only in mysql version >= 5
the stored procedure declaration is this,
you can give it little improve , but this working :
DELIMITER $$
CREATE PROCEDURE calctotal(
IN number INT,
OUT total INT
)
BEGIN
DECLARE parent_ID INT DEFAULT NULL ;
DECLARE tmptotal INT DEFAULT 0;
DECLARE tmptotal2 INT DEFAULT 0;
SELECT parentid FROM test WHERE id = number INTO parent_ID;
SELECT quantity FROM test WHERE id = number INTO tmptotal;
IF parent_ID IS NULL
THEN
SET total = tmptotal;
ELSE
CALL calctotal(parent_ID, tmptotal2);
SET total = tmptotal2 * tmptotal;
END IF;
END$$
DELIMITER ;
the calling is like
(its important to set this variable) :
SET ##GLOBAL.max_sp_recursion_depth = 255;
SET ##session.max_sp_recursion_depth = 255;
CALL calctotal(6, #total);
SELECT #total;
Take a look at Managing Hierarchical Data in MySQL by Mike Hillyer.
It contains fully worked examples on dealing with hierarchical data.
How about avoiding procedures:
SELECT quantity from (
SELECT #rq:=parent_id as id, #val:=#val*quantity as quantity from (
select * from testTable order by -id limit 1000000 # 'limit' is required for MariaDB if we want to sort rows in subquery
) t # we have to inverse ids first in order to get this working...
join
( select #rq:= 6 /* example query */, #val:= 1 /* we are going to multiply values */) tmp
where id=#rq
) c where id is null;
Check out Fiddle!
Note! this will not work if row's parent_id>id.
Cheers!
DELIMITER $$
CREATE DEFINER=`arun`#`%` PROCEDURE `recursivesubtree`( in iroot int(100) , in ilevel int(110) , in locid int(101) )
BEGIN
DECLARE irows,ichildid,iparentid,ichildcount,done INT DEFAULT 0;
DECLARE cname VARCHAR(64);
SET irows = ( SELECT COUNT(*) FROM account WHERE parent_id=iroot and location_id=locid );
IF ilevel = 0 THEN
DROP TEMPORARY TABLE IF EXISTS _descendants;
CREATE TEMPORARY TABLE _descendants (
childID INT, parentID INT, name VARCHAR(64), childcount INT, level INT
);
END IF;
IF irows > 0 THEN
BEGIN
DECLARE cur CURSOR FOR
SELECT
f.account_id,f.parent_id,f.account_name,
(SELECT COUNT(*) FROM account WHERE parent_id=t.account_id and location_id=locid ) AS childcount
FROM account t JOIN account f ON t.account_id=f.account_id
WHERE t.parent_id=iroot and t.location_id=locid
ORDER BY childcount<>0,t.account_id;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
OPEN cur;
WHILE NOT done DO
FETCH cur INTO ichildid,iparentid,cname,ichildcount;
IF NOT done THEN
INSERT INTO _descendants VALUES(ichildid,iparentid,cname,ichildcount,ilevel );
IF ichildcount > 0 THEN
CALL recursivesubtree( ichildid, ilevel + 1 );
END IF;
END IF;
END WHILE;
CLOSE cur;
END;
END IF;
IF ilevel = 0 THEN
-- Show result table headed by name that corresponds to iroot:
SET cname = (SELECT account_name FROM account WHERE account_id=iroot and location_id=locid );
SET #sql = CONCAT('SELECT CONCAT(REPEAT(CHAR(36),2*level),IF(childcount,UPPER(name),name))',
' AS ', CHAR(39),cname,CHAR(39),' FROM _descendants');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DROP PREPARE stmt;
END IF;
END$$
DELIMITER ;

mysql stored procedure that calls itself recursively

I have the following table:
id | parent_id | quantity
-------------------------
1 | null | 5
2 | null | 3
3 | 2 | 10
4 | 2 | 15
5 | 3 | 2
6 | 5 | 4
7 | 1 | 9
Now I need a stored procedure in mysql that calls itself recursively and returns the computed quantity.
For example the id 6 has 5 as a parent which as 3 as a parent which has 2 as a parent.
So I need to compute 4 * 2 * 10 * 3 ( = 240) as a result.
I am fairly new to stored procedures and I won't use them very often in the future because I prefer having my business logic in my program code rather then in the database. But in this case I can't avoid it.
Maybe a mysql guru (that's you) can hack together a working statement in a couple of seconds.
its work only in mysql version >= 5
the stored procedure declaration is this,
you can give it little improve , but this working :
DELIMITER $$
CREATE PROCEDURE calctotal(
IN number INT,
OUT total INT
)
BEGIN
DECLARE parent_ID INT DEFAULT NULL ;
DECLARE tmptotal INT DEFAULT 0;
DECLARE tmptotal2 INT DEFAULT 0;
SELECT parentid FROM test WHERE id = number INTO parent_ID;
SELECT quantity FROM test WHERE id = number INTO tmptotal;
IF parent_ID IS NULL
THEN
SET total = tmptotal;
ELSE
CALL calctotal(parent_ID, tmptotal2);
SET total = tmptotal2 * tmptotal;
END IF;
END$$
DELIMITER ;
the calling is like
(its important to set this variable) :
SET ##GLOBAL.max_sp_recursion_depth = 255;
SET ##session.max_sp_recursion_depth = 255;
CALL calctotal(6, #total);
SELECT #total;
Take a look at Managing Hierarchical Data in MySQL by Mike Hillyer.
It contains fully worked examples on dealing with hierarchical data.
How about avoiding procedures:
SELECT quantity from (
SELECT #rq:=parent_id as id, #val:=#val*quantity as quantity from (
select * from testTable order by -id limit 1000000 # 'limit' is required for MariaDB if we want to sort rows in subquery
) t # we have to inverse ids first in order to get this working...
join
( select #rq:= 6 /* example query */, #val:= 1 /* we are going to multiply values */) tmp
where id=#rq
) c where id is null;
Check out Fiddle!
Note! this will not work if row's parent_id>id.
Cheers!
DELIMITER $$
CREATE DEFINER=`arun`#`%` PROCEDURE `recursivesubtree`( in iroot int(100) , in ilevel int(110) , in locid int(101) )
BEGIN
DECLARE irows,ichildid,iparentid,ichildcount,done INT DEFAULT 0;
DECLARE cname VARCHAR(64);
SET irows = ( SELECT COUNT(*) FROM account WHERE parent_id=iroot and location_id=locid );
IF ilevel = 0 THEN
DROP TEMPORARY TABLE IF EXISTS _descendants;
CREATE TEMPORARY TABLE _descendants (
childID INT, parentID INT, name VARCHAR(64), childcount INT, level INT
);
END IF;
IF irows > 0 THEN
BEGIN
DECLARE cur CURSOR FOR
SELECT
f.account_id,f.parent_id,f.account_name,
(SELECT COUNT(*) FROM account WHERE parent_id=t.account_id and location_id=locid ) AS childcount
FROM account t JOIN account f ON t.account_id=f.account_id
WHERE t.parent_id=iroot and t.location_id=locid
ORDER BY childcount<>0,t.account_id;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
OPEN cur;
WHILE NOT done DO
FETCH cur INTO ichildid,iparentid,cname,ichildcount;
IF NOT done THEN
INSERT INTO _descendants VALUES(ichildid,iparentid,cname,ichildcount,ilevel );
IF ichildcount > 0 THEN
CALL recursivesubtree( ichildid, ilevel + 1 );
END IF;
END IF;
END WHILE;
CLOSE cur;
END;
END IF;
IF ilevel = 0 THEN
-- Show result table headed by name that corresponds to iroot:
SET cname = (SELECT account_name FROM account WHERE account_id=iroot and location_id=locid );
SET #sql = CONCAT('SELECT CONCAT(REPEAT(CHAR(36),2*level),IF(childcount,UPPER(name),name))',
' AS ', CHAR(39),cname,CHAR(39),' FROM _descendants');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DROP PREPARE stmt;
END IF;
END$$
DELIMITER ;