The Lack of Flexibility of Stored Procedures in MySQL
Over three years ago I wrote about how you cannot use a stored procedure in a subquery. Well, it’s 2010, and I’m still annoyed by this and a handful of other things.
I was just working today on a report consisting of a series of queries, taking about a minute to generate. Some of the data would be created in a temporary table and queried against multiple times for performance reasons, and ultimately spit out into a CSV file for someone to examine later. I also would like to be able to return the result set, and perform queries on it, which is much faster than querying a view.
Fortunately, MySQL’s awesome SELECT … INTO OUTFILE can easily write CSV files to disk, so I’m covered there.
So when I started working on this, I was thinking this would be a great opportunity to demonstrate the flexibility and usefulness of stored procedures – after all, we will likely be needing this report again. However, there’s an issue. You can’t return a result set from a stored proc that you can actually do anything with. Everything gets spit directly back to the client. So, what are the alternatives?
Perhaps generate the data into a new table and swap with the old via a RENAME TABLE (if it exists)? Maybe not a bad solution. It works (see example 1 below), but now we’ve coupled the dataset to a global copy of the table, and any references we have to it are now hard coded, rather than passing back a workable dataset we can do anything we want with. Furthermore, if we want to make any changes to the dataset (UPDATE/DELETE) we are unable to – we must make a copy of the table and work from that, unless we don’t mind messing with someone work.
Temporary tables created in a stored procedure for the rest of the connection, so they can’t be used either (example 2), which is somewhat contrary to the manual.
Temporary tables are available past the execution of a stored procedure, contrary to what I wrote earlier. (example 2)
You can use the TEMPORARY keyword when creating a table. A TEMPORARY table is visible only to the current connection, and is dropped automatically when the connection is closed. This means that two different connections can use the same temporary table name without conflicting with each other or with an existing non-TEMPORARY table of the same name. (The existing table is hidden until the temporary table is dropped.) To create temporary tables, you must have the CREATE TEMPORARY TABLES privilege.
Bummer. I’m assuming the stored procedure runs on its’ own isolated connection, which does make sense from a garbage collection point of view.
What about returning the data through the useful OUT parameter? Nope. Since I can’t store more than 1 row in a variable, that’s not happening either (example 3)
You also can’t generate the data in a new table and pass back the table name in an OUT variable. (example 4)
In conclusion: of the above methods, it seems the most likely route I’ll follow in the future will be to store the results in some permanent table, and run the proc off hours to regenerate the table, hoping that no one’s using it. It would need to be read only – and working with it would require creating your own copy of the table. It has the advantage of being easily understood – you can quickly DESCRIBE the columns and know what you’re getting back – so I suppose it’s going to have to be good enough. But what I really wanted was
mysql> call some_proc() into outfile "/tmp/myfile.txt";
For more information on stored procedures, check out the Stored procedure syntax.
Example 1: Swapping tables within a stored procedure
drop procedure test_proc; delimiter // CREATE procedure test_proc () begin create table jon_new LIKE jon; rename table jon to jon_old, jon_new to jon; drop table jon_old; end; // delimiter ; mysql> show tables; +------------------+ | Tables_in_test | +------------------+ | jon | +------------------+ 4 rows in set (0.00 sec) mysql> insert into jon values (1); Query OK, 1 row affected (0.04 sec) mysql> select * from jon; +----+ | id | +----+ | 1 | +----+ 1 row in set (0.00 sec) mysql> call test_proc(); Query OK, 0 rows affected (1.53 sec) mysql> select * from jon; Empty set (0.00 sec)
Example 2: Trying to create a temporary table in a stored procedure (updated)
mysql> delimiter //
mysql> create procedure test_proc3()
-> begin
-> CREATE TEMPORARY TABLE my_table (id serial);
-> end
-> //
Query OK, 0 rows affected (0.00 sec)
mysql> delimiter ;
mysql>
mysql> call test_proc3();
Query OK, 0 rows affected (0.08 sec)
mysql>
mysql> select * from my_table;
Empty set (0.00 sec)
t
Example 3: Trying to store multiple rows in a variable
mysql> create table my_table (id int primary key); Query OK, 0 rows affected (1.46 sec) mysql> set @v = (select * from my_table); Query OK, 0 rows affected (0.01 sec) mysql> insert into my_table values (1),(2),(3); Query OK, 3 rows affected (0.03 sec) Records: 3 Duplicates: 0 Warnings: 0 mysql> set @v = (select * from my_table); ERROR 1242 (21000): Subquery returns more than 1 row
Example 4: Trying to select from a table where the name was defined in a variable
mysql> set @tab = "my_table"; Query OK, 0 rows affected (0.00 sec) mysql> select @tab; +----------+ | @tab | +----------+ | my_table | +----------+ 1 row in set (0.00 sec) mysql> select * from @tab; ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '@tab' at line 1
6 Responses to The Lack of Flexibility of Stored Procedures in MySQL
Leave a Reply Cancel reply
Recent Comments
- Anil on MySQL Triggers Tutorial
- Ashish on MySQL Triggers Tutorial
- David on iCal Agenda
- jon on IP address geolocation SQL database
- pim on IP address geolocation SQL database
- jnns on Redis Wildcard Delete
- K.C. Murphy on iCal Agenda
- BA on Experts Exchange should be removed from Google search results
- Andrew on Executing multiple curl requests in parallel with PHP and curl_multi_exec
- Stu on Executing multiple curl requests in parallel with PHP and curl_multi_exec
Recent Posts
- New Project: Jester
- Open New Terminal Tip
- Installing MySQLdb on MacOS Lion
- Headless VM Server Using Ubuntu 11.10
- Get rid of Facebook’s Awful Ticker
- Api Tester now hosted on Github
- Trac .11 jQuery bug
- Multiple Filetypes in Vim
- Git Tip: Setting Up Your Remote Server
- Install issue pymongo on OSX (setuptools out of date)
Categories
- amazon (1)
- answerbag (6)
- apache (9)
- apple (8)
- awk (2)
- bbedit (2)
- c++ (3)
- chrome (2)
- cluster (1)
- cocoa (1)
- collective intelligence (1)
- curl (3)
- db2 (1)
- demand media (1)
- ebay (1)
- eclipse (4)
- erlang (13)
- facebook (1)
- fortran (1)
- gen_server (1)
- git (5)
- google (4)
- haddad (1)
- hdf5 (1)
- html (1)
- innodb (1)
- itunes (1)
- java (2)
- jester (1)
- kvm (1)
- launchbar (1)
- leex (1)
- letsgetnuts.com (1)
- libvirt (1)
- links (6)
- linux (27)
- lucene (1)
- mac (16)
- memcached (1)
- misconception (1)
- mobile (1)
- mono (1)
- mssql (1)
- munin (1)
- mysql (31)
- numpy (1)
- oracle (1)
- php (23)
- puppet (4)
- pyparsing (1)
- pytables (1)
- python (11)
- q&a (1)
- quicksilver (1)
- rant (6)
- readynas (1)
- redis (2)
- regex (1)
- replication (1)
- search (1)
- shitty code (1)
- solr (3)
- spaces (1)
- sshfs (1)
- stored procedure (1)
- svn (5)
- textmate (2)
- tips (22)
- trac (1)
- tutorial (4)
- ubuntu (3)
- Uncategorized (4)
- unix (1)
- vim (3)
- virtual box (6)
- vmware (1)
- weird (3)
- wikipedia (1)
- windows (1)
- xcode (1)








Perhaps you want to examine the use of PREPARE…
SET @foo_query = CONCAT(“SELECT * FROM “, @tab);
PREPARE foo_stmt FROM @foo_query;
EXECUTE foo_stmt;
You can file this last tip into the category of “Well, it doesn’t help me much.” … In the work that I did for External Language Stored Procedures, I implemented table functions. This would allow you to do perform a join with the derived table generated from the resultset from the stored procedure. I did a presentation involving it at last year’s MySQL Conference.
re:TEMPORARY TABLES
Maybe you were not in the test database or you are using a buggy version of mysql:
mysql> delimiter //
mysql>
mysql> create procedure test_proc3()
-> begin
-> CREATE TEMPORARY TABLE test.my_table (id serial);
-> end
-> //
Query OK, 0 rows affected (0.03 sec)
mysql>
mysql> delimiter ;
mysql>
mysql> call test_proc3();
Query OK, 0 rows affected (0.09 sec)
mysql> use test;
Database changed
mysql>
mysql> select * from my_table;
Empty set (0.00 sec)
I use temporary tables A LOT in the Flexviews stored procedures. I use one for a recursive stored procedure to track state at the proper depth, for instance.
Try this:
delimiter //
– FUNCTION Rows
– PURPOSE Execute the given SQL statement and store
– PARAMETERS
– v_sql: The SELECT statement to execute
– v_table: The table name to store the rows
– into. This parameter may be NULL,
– in which case a random temporary table
– name will be generated
drop procedure if exists rows
//
create procedure rows(IN v_sql TEXT, INOUT v_table TEXT)
begin
— use a session variable to count how many times the rows function has been
— invoked in this session. This is used to increment the automatically
— generated table name to prevent collisions
— be careful not to overwrite any user defined variable by using
— an ugly prefix
IF @__rows_CNT IS NULL THEN
SET @__rows_CNT := 1;
ELSE
SET @__rows_CNT := @__rows_CNT + 1;
END IF;
IF v_table IS NULL THEN
SET v_table = CONCAT(‘test.tbl_’, CONNECTION_ID(), ‘_’, @__rows_CNT);
END IF;
SET @__rows_SQL = CONCAT(‘CREATE TEMPORARY TABLE ‘, v_table,
‘ AS ‘, v_sql);
PREPARE __handle from @__rows_SQL;
EXECUTE __handle;
DEALLOCATE PREPARE __handle;
end
//
delimiter ;
mysql> call rows(“select 1 from dual”, @out);Query OK, 0 rows affected (0.03 sec)mysql> select @out;
+———————+
| @out |
+———————+
| test.tbl_32804915_3 |
+———————+
1 row in set (0.00 sec)
mysql> select * from test.tbl_32804915_3;
+—+
| 1 |
+—+
| 1 |
+—+
1 row in set (0.00 sec)
Justin – weird – I just tried it again and it worked… really odd. I’ll update my post accordingly.
Thanks,
Jon
Example 2 – looks like you forgot to actually call the procedure
I have found that creating a view inside a stored procedure is a very easy way to do some of this with less work in certain situations. And it is the only way to be able to use a passed in table to get the data from. example from inside a sproc:
DROP VIEW if exists temp_view;
SET @query := CONCAT(‘CREATE VIEW temp_view as select * from ‘,_tableIn,’ where col_6 “” and col_5 “”"-”"”‘);
PREPARE stmt from @query;
EXECUTE stmt;
…your queries against temp_view here.
DROP VIEW if exists temp_view;
This is easy to work with with the advantage of preforming the same query on any passed table with just the selection you want to work with. making it fast and powerful.