Unioning from a variable number of tables. (Overloaded stored MySQL Procedure) - mysql

I have a 10 tables with the same schema. I'm trying to create an overloaded stored procedure so that I can union a bunch of tables together with simple selects (SELECT * FROM tableX). If each table has 1000 (different) rows, then I want to create a stored procedure where the following would happen:
CALL getRowsByNum(table1); -> 1000 rows
CALL getRowsByNum(table1, table2, table4); -> 3000 rows
...etc.
I got part of the way through writing 10 overloaded procedures that would SELECT * FROM X UNION ALL SELECT * FROM X UNION ALL..... etc, but that's really madness.
Anyone have a different suggestion? This silly setup is the result of an architectural decision made a while ago.
Thanks!

I agree that you have to rethink your db structure.
By the way, just for fun :)
drop database if exists my_test;
create database my_test;
use my_test;
create table table1 (
id int not null auto_increment primary key,
my_field varchar(10)
) engine = myisam;
create table table2 like table1;
create table table3 like table1;
create table table4 like table1;
create table table5 like table1;
insert into table1 (my_field) values ('aaa'),('bbb');
insert into table2 (my_field) values ('ccc'),('ddd'),('eee');
insert into table3 (my_field) values ('fff'),('ggg');
insert into table4 (my_field) values ('hhh'),('iii'),('jjj');
insert into table5 (my_field) values ('kkk'),('lll');
delimiter //
drop procedure if exists tables_union //
create procedure tables_union (in str varchar(10000),in db varchar(100))
begin
set #qry = (select group_concat(concat('select * from ',table_name) separator ' union all ')
from information_schema.tables
where find_in_set(table_name,str)
and table_schema = db);
-- select #qry;
prepare stmt from #qry;
execute stmt;
deallocate prepare stmt;
end; //
delimiter ;
call tables_union('table4,table3,table1','my_test');
+----+----------+
| id | my_field |
+----+----------+
| 1 | aaa |
| 2 | bbb |
| 1 | fff |
| 2 | ggg |
| 1 | hhh |
| 2 | iii |
| 3 | jjj |
+----+----------+
7 rows in set (0.00 sec)

Not really. You can't have variable number of parameters or procedures with the same name.
I dont think there is an easy solution to your problem.
EDIT: I've thought of an really ugly solution but I'll leave it to you if you wanna use it. This is untested pseudo code only.
Create a proc where you take a varchar in, long enough to hold the value "table1,table2,table3,..." and so on for as many tables you like to union at most. (could be another identifier of course)
Write all tables in union. Since they are the same just use * to save time and space
delimiter //
create procedure megaunion (tables varchar(255))
begin
select * from table1 where find_in_set('table1', tables)
union
select * from table2 where find_in_set('table2', tables)
....
end//
list all your tables. At least you don't have to list the possible permutations and the user of the procedure won't know how you did it :)

Related

Use ResultSet of SELECT...FOR UPDATE in MySQL Stored Procedure

I'm using MySql 5.7 trying to create a stored procedure that will update a set of rows in a transaction and return the rows that were updated. After creating the locks, I then update those rows, but can't figure out how to use the ids returned from the SELECT...FOR UPDATE statement. Instead I have to scan the table again in the update statement looking for the rows I just locked. Here's an example of my procedure.
DELIMITER //
CREATE PROCEDURE cleanUp()
BEGIN
START TRANSACTION;
SELECT * FROM t WHERE t.state = 'foobar' AND t.value < 10 FOR UPDATE;
UPDATE t SET t.state = 'fizzbuzz' WHERE t.state = 'foobar' AND t.value < 10;
COMMIT;
END //
I'd prefer to not have to scan the table twice for t.state = 'foobar' AND t.value < 10. I'd also like to guarantee that I only update the rows I just locked, not other rows that might have been changed to meet that criteria mid-transaction.
Is there a way to use the results of SELECT..FOR UPDATE in the UPDATE statement so I can update rows by id instead?
Note: I've tried loading the results into a temp table and using a cursor and both do not work.
MySQL can't declared variable as table, so you need o write the select in a FROM clause, which can for example do as IN clause like the query below
CREATE TABLe t(id int, state varchar(10), value int)
INSERT INTO t VALUEs(1,'foobar',1),(2,'foobar',1),(3,'foobar',1)
CREATE PROCEDURE cleanUp()
BEGIN
START TRANSACTION;
UPDATE t SET t.state = 'fizzbuzz' WHERE id IN (SELECT id FROM (SELECT id FROM t WHERE t.state = 'foobar' AND t.value < 10 FOR UPDATE) t1 );
COMMIT;
END
CALL cleanUp()
SELECT * FROM t
id | state | value
-: | :------- | ----:
1 | fizzbuzz | 1
2 | fizzbuzz | 1
3 | fizzbuzz | 1
db<>fiddle here

MySQL Stored Procedure Variable as Table Name concatenated

I have the following query I want to execute in my stored procedure WITHOUT PREPARING the query, since this gives me problems with OUT to pass back parameters.
DELIMITER //
CREATE PROCEDURE Test (
IN CID BIGINT(20),
IN IDs LONGTEXT
)
BEGIN
#EXECUTE UNDERNEATH QUERY
SELECT * FROM CONCAT('Part1_OfTableName', CID); #CID IS CustomerID
END //
DELIMITER ;
However, this fails and I don't know how to fix the problem.
(Note that in the example I have no spaces in my table name, however in my situation I might have a space in my table name though)
PREPARE should have no bearing on your ability to successfully set OUT parameters of your procedure
SET DELIMITER //
CREATE PROCEDURE test(IN cid INT, IN ids TEXT, OUT out_int INT)
BEGIN
SET #sql = CONCAT('SELECT * FROM `table_', cid, '`', CASE WHEN ids IS NULL THEN '' ELSE CONCAT(' WHERE id IN( ', ids, ')') END);
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET out_int = 1;
END//
SET DELIMITER ;
Sample usage:
mysql> CALL test(1, '2,3', #out_int);
+------+
| id |
+------+
| 2 |
| 3 |
+------+
2 rows in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT #out_int;
+----------+
| #out_int |
+----------+
| 1 |
+----------+
1 row in set (0.00 sec)
If you need to return results from a stored procedure using sql statement that must be prepared, you can use an intermediate temp table.
BEGIN
CREATE TEMPORARY TABLE `myresults` blah blah....;
//construct and prepare select you would've used, but start it with an insert like so...
// INSERT INTO `myresults` SELECT ....
// Execute the prepared query
SELECT * FROM `myresults`;
DROP TEMPORARY TABLE `myresults`;
END
...at least I am pretty sure this technique used to work; I've been working more in MSSQL the last couple years.
Something to note:
Temporary tables are connection/session specific, so while safe from a global perspective using a generic name like myresults can be problematic if queries executed earlier on the connection/session (or by a procedure calling this one) use the same name; in practice/paranoia, I tended to use a different guid (in each procedure using this technique) as a prefix for any temporary tables generated within it.

Stored procedure

I've been googling to find an answer but can't find anything. I have a cursor statement that pulls the name of the tables that are present in the database.
THe goal is:
a stored procedure with 2 parameters, database1 and database2
comparing both databases and outputting the difference.
database names are tab/space delimited
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE db_tables VARCHAR(256);
DECLARE cursor1 CURSOR FOR
SELECT TABLE_NAME, TABLE_SCHEMA
FROM information_schema.tables
WHERE TABLE_SCHEMA = db1
AND TABLE_TYPE = 'BASE TABLE';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cursor1;
FETCH cursor1 into db_tables;
WHILE done = FALSE DO
SET query1 = SELECT * FROM db1 WHERE table1 IN(table_name);
END WHILE;
CLOSE cursor1;
END
This uses the INFORMATION_SCHEMA.TABLES information
The Schema
create database db1;
create database db2;
create table db1.s1 (id int);
create table db1.s2 (id int);
create table db1.s3 (id int);
create table db2.s2 (id int);
create table db2.s3 (id int);
create table db2.s4 (id int);
The Query
select t1.table_name, 2 as 'not in this one'
from INFORMATION_SCHEMA.TABLES t1
where t1.table_schema='db1'
and not exists (select * from INFORMATION_SCHEMA.TABLES t2 where t2.table_schema='db2' and t2.table_name=t1.table_name)
union
select t1.table_name, 1 as 'not in this one'
from INFORMATION_SCHEMA.TABLES t1
where t1.table_schema='db2'
and not exists (select * from INFORMATION_SCHEMA.TABLES t2 where t2.table_schema='db1' and t2.table_name=t1.table_name)
The Results
+------------+-----------------+
| table_name | not in this one |
+------------+-----------------+
| s1 | 2 |
| s4 | 1 |
+------------+-----------------+
This means that table s1 is in database db1, but not in db2, and that table s4 is in the database db2, but not in db1.
Stored Proc
delimiter $$
create procedure showDBDiffInTableNames
( x1 varchar(40),x2 varchar(40) )
BEGIN
--
-- passed parameters, x1 is a string containing the name of a database
-- x2 is a string containing the name of another database
--
select t1.table_name, 2 as 'not in this one'
from INFORMATION_SCHEMA.TABLES t1
where t1.table_schema=x1
and not exists (select * from INFORMATION_SCHEMA.TABLES t2 where t2.table_schema=x2 and t2.table_name=t1.table_name)
union
select t1.table_name, 1 as 'not in this one'
from INFORMATION_SCHEMA.TABLES t1
where t1.table_schema=x2
and not exists (select * from INFORMATION_SCHEMA.TABLES t2 where t2.table_schema=x1 and t2.table_name=t1.table_name);
END
$$
DELIMITER ;
Test it:
call showDBDiffInTableNames('x1','x2');
same results
t1 and t2 are just table aliases. See the manual page here. From the manual page:
The following list describes general factors to take into account when
writing joins.
A table reference can be aliased using tbl_name AS alias_name or
tbl_name alias_name:
....
I almost never write a query without an alias if, knowing ahead of time, I am going after two or more tables. It cuts down on the typing. They are especially common in self-joins (to the same table). You need a way to differentiate which one you are dealing with to remove Ambiguous errors from queries. So that is why that alias is in there. Plus, you will note that the table is gone after twice.
There are two ways you can write it, as seen in the pink/peach block above.

How can I select out parameters in a MySQL procedure as table columns?

Environment: MySQL 5.1, Linux
I have a stored procedure that computes several values from a single input value. The values are returned as OUT parameters. I would like to call this procedure for every row set and have the values appear as columns in the result set. The values have a complex relationship such that distinct functions for each value is not easily constructed.
The question: How can I get OUT parameters to show up as columns in a table?
Here's what I have so far:
DELIMITER $_$
DROP PROCEDURE IF EXISTS in_out;
CREATE PROCEDURE in_out (
IN s TEXT,
OUT op TEXT,
OUT opn INT,
OUT opd TEXT,
OUT len INT
)
BEGIN
SET op = 'del';
SET opn = 1;
SET opd = substr(s,4);
SET len = LENGTH(SUBSTR(s,4));
END
$_$
DELIMITER ;
Then:
mysql> call in_out('delACT',#op,#opn,#opd,#len);
Query OK, 0 rows affected (0.00 sec)
mysql> select #op,#opn,#opd,#len;
+------+------+------+------+
| #op | #opn | #opd | #len |
+------+------+------+------+
| snv | 1 | ACT | 3 |
+------+------+------+------+
1 row in set (0.00 sec)
So far so good, but I can't figure out how to call this procedure for every row and return the results in the result set. I want is something like this:
dream> select mycol,in_out(mycol) from mytable
+---------+------+------+------+------+
| mycol | #op | #opn | #opd | #len |
+---------+------+------+------+------+
| delACT | del | 1 | ACT | 3 |
+---------+------+------+------+------+
Thanks!
You confuse the stored procedures and stored functions:
stored function will be return a value, the results can be used in
expressions (like COS() and other mysql built-in functions).
stored procedure need use CALL , is an independent operation, can not
be used in expressions.
If you want to "select mycol,in_out(mycol) from mytable",you must:
CREATE FUNCTION in_out( ...
This appears to be a trick question: one can't create table relations out of function/procedure results in MySQL. I ended up refactoring into separate functions (as suggested by MichaƂ). I had been hoping for a MySQL equivalent to PostgreSQL's table functions (http://goo.gl/77QVE).
I'd recommend to prepare data in stored procedure for each possible value in:
select distinct mycol
from mytable
where <... condition that you would use anyway in final result ...>
where mycol is your parameter for stored procedure save it to temporary table and than join to this values.
-- way the temp table may look in your sp
create temporary table tmptable (
mycol text
op text,
opn int,
opd text,
len int
)
after that use join:
select m.mycol, t.op, t.opn, t.opd, t.len
from mytable m
join tmptable t on m.mycol = t.mycol
where <... condition that you would use anyway in final result ...>
Bit different question, are you absolutely sure that there is no different way to process your final result than using a stored procedure?

MySQL Procedure within a Select?

I have a procedure that works like this:
mysql> call Ticket_FiscalTotals(100307);
+---------+--------+----------+------------+------------+
| Service | Items | SalesTax | eTaxAmount | GrandTotal |
+---------+--------+----------+------------+------------+
| 75.00 | 325.00 | 25.19 | 8.00 | 433.19 |
+---------+--------+----------+------------+------------+
1 row in set (0.08 sec)
I would like to call this procedure from within a select, like so:
SELECT Ticket.TicketID as `Ticket`,
Ticket.DtCheckOut as `Checkout Date / Time`,
CONCAT(Customer.FirstName, ' ', Customer.LastName) as `Full Name`,
Customer.PrimaryPhone as `Phone`,
(CALL Ticket_FiscalTotals(Ticket.TicketID)).Service as `Service`
FROM Ticket
INNER JOIN Customer ON Ticket.CustomerID = Customer.CustomerID
ORDER BY Ticket.SiteHomeLocation, Ticket.TicketID
However I know that this is painfully wrong. Can someone please point me in the proper direction? I will need access to all of the columns from the procedure to be (joined?) in the final Select. The SQL code within that procedure is rather painful, hence the reason for it in the first place!
The Ticket_FiscalTotals procedure returns a data set with some fields, but you need just one of them - Service. Rewrite your procedure to stored function - Get_Ticket_FiscalTotals_Service.
Another way is to create and fill temporary table in the procedure, and add this temporary to a query, e.g.:
DELIMITER $$
CREATE PROCEDURE Ticket_FiscalTotals()
BEGIN
DROP TEMPORARY TABLE IF EXISTS temp1;
CREATE TEMPORARY TABLE temp1(
Service FLOAT(10.2),
Items FLOAT(10.2),
SalesTax FLOAT(10.2),
eTaxAmount FLOAT(10.2),
GrandTotal FLOAT(10.2)
);
INSERT INTO temp1 VALUES (75.0, 325.0, 25.19, 8.0, 433.19);
END
$$
DELIMITER ;
-- Usage
CALL Ticket_FiscalTotals();
SELECT t.*, tmp.service FROM Ticket t, temp1 tmp;
You can't join directly to stored procedure. You can join to temporary table that this stored procedure fills:
create temporary table,
execute SP that fills data in your temp table,
join to temp table in your query,
drop temp table.
Of course it is not one line solution.
The other way (worse in my opinion) I think of is to have as many UDF as columns in SP result set, this might look like fallowing code:
SELECT
Ticket.TicketID as `Ticket`,
Ticket.DtCheckOut as `Checkout Date / Time`,
CONCAT(Customer.FirstName, ' ', Customer.LastName) as `Full Name`,
Customer.PrimaryPhone as `Phone`,
Ticket_FiscalTotals_Service(Ticket.TicketID) as `Service`,
Ticket_FiscalTotals_Items(Ticket.TicketID) as `Items`,
Ticket_FiscalTotals_SalesTax(Ticket.TicketID) as `SalesTax`,
Ticket_FiscalTotals_eTaxAmount(Ticket.TicketID) as `eTaxAmount`,
Ticket_FiscalTotals_GrandTotal(Ticket.TicketID) as `GrandTotal`
FROM Ticket
INNER JOIN Customer ON Ticket.CustomerID = Customer.CustomerID
ORDER BY Ticket.SiteHomeLocation, Ticket.TicketID