MySQL Stored Procedure Variable as Table Name concatenated - mysql

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.

Related

MySQL Copy table procedure fails [duplicate]

This question already has answers here:
MySQL Stored Procedure Variable as Table Name concatenated
(2 answers)
Closed last month.
What am i doing wrong with this procedure??
# Copy tabel
CREATE PROCEDURE `table_backup`(tablename varchar(50))
begin
set #copy_from = tablename;
set #copy_to = CONCAT(tablename, `_`, DATE_FORMAT(NOW(), '%d_%m_%yt%H_%i_%s'));
CREATE TABLE #copy_to LIKE #copy_from;
INSERT #copy_to SELECT * FROM #copy_from;
end
CALL table_backup('table_name');
You will need to perform your query dynamically. This is how you can achieve it in a stored procedure:
mysql> delimiter //
mysql> create procedure dynamic_query()
-> begin
-> set #query=concat("select *from DemoTable2033 where Id=3");
-> prepare st from #query;
-> execute st;
-> end
-> //
Query OK, 0 rows affected (0.13 sec)
mysql> delimiter ;
You will need to perform the create table and insert into as dynamic query. Depending on your settings you might need to perform them as separate dynamic queries, using the same pattern.
Read more at https://www.tutorialspoint.com/implement-dynamic-sql-query-inside-a-mysql-stored-procedure

MySql update. difference between procedure results and phpmyadmin sql

I am having difficult getting a procedure to update a table in the way I require. I am using phpmyadmin on my local computer. In phpmyadmin I can put the following code into the SQL tab and one row will be updated:
SET `adjCost` = 22.05 WHERE `Name` LIKE CONCAT('magic', '%') AND `idKey` = '2016fulham02345';
As expected and wanted, IF the name begins with magic AND the idKey is '2016fulham02345' THEN the adjCost is updated to 22.05.
There will be between 2 and 50 rows with the same idKey. The Name will never be repeated in a set with the same idKey.
I created a procedure with the following parameters:
IN idK VARCHAR 255 Charset
IN aName VARCHAR 255 Charset
IN cost FLOAT 5,2
BEGIN
UPDATE `raceresults` SET `adjCost` = cost WHERE `Name` LIKE CONCAT(aName, '%') AND `idKey` = idK;
END
When I run this procedure it updates ALL adjCost where the idKey = idk and (seems) to ignore the name parameter.
I have tried concatenating the name string first:
BEGIN
SELECT CONCAT(aName, '%') INTO #str;
UPDATE `raceresults` SET `adjCost` = cost WHERE `Name` = #str AND `idKey` = idK;
END
but to no avail.
I looked through w3schools, stackoverflow and google and have not been able to find the answer.
My question is:
How can I correct my procedure to get it to work as I would like?
UPDATE: as requested.
CREATE DEFINER=`root`#`localhost` PROCEDURE `importAltUpdateAjdCost`(IN `idK` VARCHAR(255), IN `aName` VARCHAR(255), IN `cost` FLOAT(5,2))
NO SQL
BEGIN
UPDATE `costingPP`
SET `adjCost` = cost
WHERE
`Name` LIKE CONCAT(aName, '%')
AND
`idKey` = idK;
END
To get this, I selected export on my list of procedures on phpmyadmin.
I'm not entirely sure what or how you did, but here's what I did and it instantly worked. Since you didn't specify MySQL version, I used 5.7.
EDIT: Now as I went back to see your procedure creation statement I realised that NO SQL was introduced in MySQL 8.0. Since your procedure clearly is SQL then please remove the NO SQL and re-create the procedure.
I'm leaving my MySQL 5.7 sample here for reference:
1) Created a simple table:
mysql> CREATE TABLE raceresults (
-> idKey VARCHAR(255),
-> Name VARCHAR(255),
-> adjCost FLOAT(5,2)
-> );
Query OK, 0 rows affected (0.06 sec)
2) Here we insert a sample data row:
mysql> INSERT INTO raceresults VALUES ('2016fulham02345', 'magicFlyingHorse', 0.00);
Query OK, 1 row affected (0.01 sec)
3) To create a (STORED) PROCEDURE we have to temporarily set a different delimiter, so query parser wouldn't terminate procedure creation on default semi-colon, as it's used inside the procedure. After delimiter's change we create the procedure and set the delimiter back to semi-colon
mysql> DELIMITER //
mysql> CREATE PROCEDURE update_test(IN idK VARCHAR(255), IN aName VARCHAR(255), IN cost FLOAT(5,2))
-> BEGIN
-> UPDATE `raceresults` SET `adjCost` = cost WHERE `Name` LIKE CONCAT(aName, '%') AND `idKey` = idK;
-> END//
mysql> DELIMITER ;
Query OK, 0 rows affected (0.00 sec)
4) Now let's see how it all works. Before and after the procedure call I'm selecting the rows from database. You can see the cost column value changing:
mysql> SELECT * FROM raceresults;
+-----------------+------------------+---------+
| idKey | Name | adjCost |
+-----------------+------------------+---------+
| 2016fulham02345 | magicFlyingHorse | 0.00 |
+-----------------+------------------+---------+
1 row in set (0.00 sec)
mysql> CALL update_test('2016fulham02345', 'magic', 1.23);
Query OK, 1 row affected (0.02 sec)
mysql> SELECT * FROM raceresults;
+-----------------+------------------+---------+
| idKey | Name | adjCost |
+-----------------+------------------+---------+
| 2016fulham02345 | magicFlyingHorse | 1.23 |
+-----------------+------------------+---------+
1 row in set (0.00 sec)
And now one piece of advise too:
If possible, use only lower case table, column, indexes, functions, procedures, etc... names, while always writing all SQL commands in uppercase (which you did). This is kind of a de facto standard and makes life easier both for you and others reading your code.

Data manipulation in a mysql table

I have 2 columns in the table which have incorrect entries. The size of the table runs in to billions of records. I had like to swap data between two columns (c1 and C2)
The approach taken is to export the data in small chunks in to CSV files and then import it back with corrected entries. For example, below is the data set
--------
|C1 | C2 |
|3 | 4 |
|4 | 6 |
I would then export the data in to a semicolon delimted CSV file (complete command NOT shown) as below
SELECT C2,C1 FROM TABLE temp INTO OUTFILE /tmp/test.csv
The output of such command would be
4;3
6;4
When I import back the data (after deleting the data in question), the data will be corrected as follows
| C1 C2 |
| 3 4 |
| 4 6 |
It is really a matter of OUTFILE and INFILE operation, I believe
Question
Does the approach makes sense? The real data also expects NULL, int
values in some of the columns apart from data swaps.
The other complexity is in the production database, I will need to
use the WHERE clause. The table name would also be fetched
dynamically.
With reference to point 2, how do I add dynamicity to the queries.
Should I use a STORED procedure or SHELL SCRIPT? STORED Procedure
does not seem to support LOAD DATA INFILE functionality.
If I am left with shell, any sample script that I can reuse? The CSV
filename, table name and WHERE clause will have to be built at run
time.
Also the size of the chunk to be exported and imported will be calculated dynamically.
Any other approach?
Note - This is a INFOBRIGHT column based table on top of mysql. The UPDATE query is non-performant and ALTER TABLE is not supported by INFOBRIGHT.
you can use this approach
create a temporary table 'temp_table' and the use this procedure, then call it from anywhere with the name of the table like this.
call change_fields('table_origen');
the procedure works like this
delete the data in the temp_table
insert into this temp_table with the order changed from the table_origen
delete from the table_origen
insert into the table_origen the information from the temp_table
You can go crazy and how many variables you want to accept
CREATE DEFINER=`root`#`%` PROCEDURE `change_fields`(IN `tableName` VARCHAR(200))
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
SET #hr1 = CONCAT('Delete from temp_table;');
PREPARE hrStmt1 FROM #hr1;
EXECUTE hrStmt1;
DEALLOCATE PREPARE hrStmt1;
SET #hr1 = CONCAT('insert into temp_table (C_1, C_2) Select C2, C1 from `',tableName,'`;');
PREPARE hrStmt1 FROM #hr1;
EXECUTE hrStmt1;
DEALLOCATE PREPARE hrStmt1;
SET #hr1 = CONCAT('Delete from `',tableName,'`;');
PREPARE hrStmt1 FROM #hr1;
EXECUTE hrStmt1;
DEALLOCATE PREPARE hrStmt1;
SET #hr1 = CONCAT('insert into `',tableName,'` (C1,C2) select C_1, C_2 from temp_table;');
PREPARE hrStmt1 FROM #hr1;
EXECUTE hrStmt1;
DEALLOCATE PREPARE hrStmt1;
END;

Creating columns of a table using the rows of another

I'm working on my project in php [handling students attendance system]. I have a list of students along with their unique id[jntuno] and I need to create a database in mysql for storing the daily attendance of each student for each subject. So I created my tables in this way :
I have a table students in mysql with the following fields and data in it :
now I want to create a new table with the each of the values in the jntuno field as a columns of my new table.
I want my new table [let us name it attendance] to have columns like this :
+------------+-----------+----------+-----------+-----------+
|11341A0501 |11341A0502 |11341A0503|11341A0504 |11341A0505 |......
+------------+-----------+----------+-----------+-----------+
| | | | | |
How to do this in mysql ?
I will later add 3 fields to the attendance table namely :
-> date[the date on which a particular subject is taught] ,
->subject[the name of the subject taught] and
->hours taught[the number of hours for which a particular subject is taught(can be 1 or 2 or 3 ... upto 6)]
every subject taught on a particular date will be adding a new row to the attendance table
Example:
+------------+-----------+-----------------+------------+-----------+----------+-----------+-----------+
|date |subject | classes taught |11341A0501 |11341A0502 |11341A0503|11341A0504 |11341A0505 |..
+------------+-----------+-----------------+------------+-----------+----------+-----------+-----------+
|2013-09-31 |OOPS |3 |2 |3 |0 |1 |3 |
I choose the tables in this way so that the entry of attendance into the table would be more faster.
But many call this a BAD DATABASE STRUCTURE . So please suggest me if there's some other good and efficient database design for my problem
Create the new table with the following statements:
select #s:=concat('create table students_col (',group_concat(jntunno,' CHAR(10)' order by slno),')') from students;
prepare stmt from #s;
execute stmt;
deallocate prepare stmt;
observe how the CREATE TABLE is constructed using the group_concat
Demo: SQL Fiddle
In case you also want to insert the names, this is the statement to it:
select #s:=concat('insert into students_col values (',group_concat(concat('"',name,'"') order by slno),')') from students;
prepare stmt from #s;
execute stmt;
deallocate prepare stmt;
select * from students_col;
Here is my whole trail:
mysql> drop table if exists students;
Query OK, 0 rows affected (0.00 sec)
mysql> create table students (slno integer, jntunno char(10), name varchar(50));
Query OK, 0 rows affected (0.07 sec)
mysql> insert into students values (1,'1134A0501','ADARI GOPI');
Query OK, 1 row affected (0.00 sec)
mysql> insert into students values (2,'1134A0502','BALU');
Query OK, 1 row affected (0.00 sec)
mysql> insert into students values (3,'1134A0503','GEETHA');
Query OK, 1 row affected (0.00 sec)
mysql> drop table if exists students_col;
Query OK, 0 rows affected (0.00 sec)
mysql> select #s:=concat('create table students_col (',group_concat(jntunno,' CHAR(10)' order by slno),')') from students;
+-----------------------------------------------------------------------------------------------+
| #s:=concat('create table students_col (',group_concat(jntunno,' CHAR(10)' order by slno),')') |
+-----------------------------------------------------------------------------------------------+
| create table students_col (1134A0501 CHAR(10),1134A0502 CHAR(10),1134A0503 CHAR(10)) |
+-----------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> prepare stmt from #s;
Query OK, 0 rows affected (0.00 sec)
Statement prepared
mysql> execute stmt;
Query OK, 0 rows affected (0.21 sec)
mysql> deallocate prepare stmt;
Query OK, 0 rows affected (0.01 sec)
mysql>
mysql> select #s:=concat('insert into students_col values (',group_concat(concat('"',name,'"') order by slno),')') from students;
+------------------------------------------------------------------------------------------------------+
| #s:=concat('insert into students_col values (',group_concat(concat('"',name,'"') order by slno),')') |
+------------------------------------------------------------------------------------------------------+
| insert into students_col values ("ADARI GOPI","BALU","GEETHA") |
+------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> prepare stmt from #s;
Query OK, 0 rows affected (0.00 sec)
Statement prepared
mysql> execute stmt;
Query OK, 1 row affected (0.00 sec)
mysql> deallocate prepare stmt;
Query OK, 0 rows affected (0.00 sec)
mysql>
mysql> select * from students_col;
+------------+-----------+-----------+
| 1134A0501 | 1134A0502 | 1134A0503 |
+------------+-----------+-----------+
| ADARI GOPI | BALU | GEETHA |
+------------+-----------+-----------+
1 row in set (0.00 sec)
mysql>
This procedure will do the work:
DELIMITER ||
DROP PROCEDURE IF EXISTS `test`.`pivot`;
CREATE PROCEDURE `test`.`pivot`()
MODIFIES SQL DATA
BEGIN
DROP TABLE IF EXISTS `test`.`new_table`;
SELECT GROUP_CONCAT(CONCAT(`jntunno`, ' CHAR(10) NOT NULL') SEPARATOR ', ') FROM `test`.`students` INTO #sql;
SET #sql := CONCAT('CREATE TABLE `test`.`new_table` (', #sql, ') ENGINE=InnoDB;');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET #sql := NULL;
END;
||
DELIMITER ;
If you cannot use stored procedures, you can easily translate that code into PHP, or any language you use.
create table new_table
select distinct jntuno from students;
or
create table new_table (jntuno varchar(10));
insert into new_table
select distinct jntuno from students;
try
create table new_table (jntuno varchar(10),fieldsname varchar(10));
insert into new_table(jntuno,fieldsname)
values(select distinct jntuno from s tudents,'test');
I'm going to try an answer your question of how to better design this DB:
The Explanation:
Instead of having a column for each student and I assume a row for each day, you should set the table up to have a row for each student x by each day and you can use foreign keys to enforce data integrity
The main reason for this is generally something like the number of students is not a static number. In order to optimize your data usage in a setup like this you would essentially need to recreate the table every time a student is added or removed.
So if you start out with 30 students you then have a table with 30 columns, if then the number of students drops to 15 every time you add a record you are creating 15 unused values and this will gradually take up a lot more storage than you actually need.
Solution:
If you structure your attendance table like this:
attendance ID | JNTUNO | Date | Subject | Hours
Attendance id will simply be a unique identifier for the row, I would suggest either a guid or an auto incremented int
JNTUNO would be a foreign key, pointing back to your Student table
This way on the scenario I presented earlier, with 30 students you will add 30 rows every day with 5 values each but when your number of students changes to 15 you add only 15 rows.
I don't know MYSQL syntax very well but if you'd like I can try to piece together a concrete implementation if my explanation isn't clear; just let me know.
You don't want to be adding columns every time you add a student.
I'd design it like this:
Table: Attendance
Columns:
Date
Subject
ClassNumber
Missed_jntuno
Then you add a row for each student/class combo that is missed (or, if students miss more than they attend, you might want to flip that to a Attended_jntuno field instead and add the row when they make it to class — or if you're really feeling completest, always write a row for every student and then have another bit/boolean column for attended or not).
One advantage to this way is that you can see which classes got which students in addition to a daily sum. Another is that this is much friendlier to things like OLAP cubes for flexible reporting.
declare #s as varchar(8000);
declare #s2 as varchar(25);
set #s ='create table attendance (';
declare s1 cursor
for
select jntunno from students
OPEN s1
fetch next from s1 into #s2
set #s = #s +'['+#s2 +'] numeric(4,2)'
fetch next from s1 into #s2
WHILE ##FETCH_STATUS = 0
BEGIN
set #s = #s +',['+#s2 +'] numeric(4,2)'
fetch next from s1 into #s2
end
CLOSE s1 -- close the cursor
DEALLOCATE s1
set #s = #s+')'
exec(#s)
Basic database design is jumping up right now and shouting in your face: "You're doing it wrong!".
What you're trying to do here is put a many to many relationship in 1 table, while it should actually be in 3 tables.
What you should do is keep your student table as-is and add a subjects table with 1 row for each subject, but not including the date.
Then you want to have another table Attendance with a reference column to Student ID, a reference column to subject ID, a Date field and a Presence field.
What you're trying to do right now is trying to store what's basically volatile data in your metadata about your table, which you should never do. For example, what if you have a student join your school 1 week into the grade? then your table suddenly gets an extra column with no values for the first week.
In addition, your system means that database queries are much harder, since you're storing data you want to filter on in your column header. Other people in here have given a number of reasons why this is a bad idea.

Why is mysql caching the column names of a temporay table that is deleted?

I've reduced my issue down to this simple SP. The column names are getting cached in the SELECT * at the end. I have no idea why or how to stop it. I tried adding SQL_NO_CACHE but that makes no difference.
DROP TABLE IF EXISTS foo;
CREATE TABLE foo(
col1 int,
col2 int);
INSERT INTO foo VALUES(1,2),(3,4),(5,6);
DROP PROCEDURE IF EXISTS mysp;
DELIMITER ;;
CREATE DEFINER=root#localhost PROCEDURE mysp(c INT)
BEGIN
DROP TABLE IF EXISTS mydata;
SET #mycol='col1';
IF c > 0 THEN SET #mycol:='col2';
END IF;
SET #s=CONCAT('CREATE TEMPORARY TABLE mydata AS SELECT ', #mycol, ' FROM foo');
PREPARE stmt FROM #s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- The following select call fails on 2nd and subsequent executions of the SP
SELECT SQL_NO_CACHE * FROM mydata;
SELECT "Please see new temp table mydata" as Result;
END ;;
DELIMITER ;
Version
mysql> SELECT VERSION();
+------------+
| VERSION() |
+------------+
| 5.5.15-log |
+------------+
1 row in set (0.00 sec)
First run works fine as expected
mysql> CALL mysp(0);
+------+
| col1 |
+------+
| 1 |
| 3 |
| 5 |
+------+
3 rows in set (0.17 sec)
+----------------------------------+
| Result |
+----------------------------------+
| Please see new temp table mydata |
+----------------------------------+
1 row in set (0.17 sec)
Query OK, 0 rows affected (0.17 sec)
Now if I try and run it again using the other column
mysql> CALL mysp(1);
ERROR 1054 (42S22): Unknown column 'qlgqp1.mydata.col1' in 'field list'
mysql> SELECT #mycol;
+--------+
| #mycol |
+--------+
| col2 |
+--------+
1 row in set (0.00 sec)
If I recreate the storedprocedure again its works
mysql> CALL mysp(1);
+------+
| col2 |
+------+
| 2 |
| 4 |
| 6 |
+------+
3 rows in set (0.18 sec)
+----------------------------------+
| Result |
+----------------------------------+
| Please see new temp table mydata |
+----------------------------------+
1 row in set (0.18 sec)
Query OK, 0 rows affected (0.18 sec)
But if I try switching back to the first column - even if I try dropping the temp table first - it still doesn't work
mysql> CALL mysp(0);
ERROR 1054 (42S22): Unknown column 'qlgqp1.mydata.col2' in 'field list'
mysql> DROP TABLE mydata;
Query OK, 0 rows affected (0.03 sec)
mysql> CALL mysp(0);
ERROR 1054 (42S22): Unknown column 'qlgqp1.mydata.col2' in 'field list'
mysql>
*Additional info asked for by eggyal. Also I tried this on another mysql version with same result. *
mysql> CALL mysp(1);
+------+
| col2 |
+------+
| 2 |
| 4 |
| 6 |
+------+
3 rows in set (0.20 sec)
+----------------------------------+
| Result |
+----------------------------------+
| Please see new temp table mydata |
+----------------------------------+
1 row in set (0.20 sec)
Query OK, 0 rows affected (0.20 sec)
mysql> describe mydata;
+-------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| col2 | int(11) | YES | | NULL | |
+-------+---------+------+-----+---------+-------+
1 row in set (0.00 sec)
mysql> CALL mysp(0);
ERROR 1054 (42S22): Unknown column 'test.mydata.col2' in 'field list'
mysql> describe mydata;
+-------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| col1 | int(11) | YES | | NULL | |
+-------+---------+------+-----+---------+-------+
1 row in set (0.00 sec)
Interesting development of a fix - changing the last few lines to a prepared statement works - but using exactly the same query as before.
-- The following select call fails on 2nd and subsequent executions of the SP
PREPARE stmt FROM 'SELECT SQL_NO_CACHE * FROM mydata';
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SELECT "Please see new temp table mydata" as Result;
MySQL is reusing the statement that was prepared on the previous execution. It's not really "caching" column names; what it's "caching" (if you will) is the prepared statement.
The easy workaround is to use dynamic SQL statement to gain control over the behavior, and avoid the reuse of the previously prepared statement:
SET #s=CONCAT('SELECT ',#mycol,' FROM mydata');
PREPARE stmt FROM #s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
It's not matter of the column names being "cached", or the results of the query being cached. It's a performance optimization; it's a matter of that statement already being prepared, within your session.
By using dynamic SQL, you can control when the statement is prepared (i.e. parsing the SQL text for syntax (statement formation, keywords, etc.), checked for semantics (object names exist, column names exist, user has required privileges, etc.), and preparing an execution plan.
With the static SQL, all of that happens on the first execution, and then MySQL hangs on to the prepared statement.
For performance reasons, we wouldn't want the overhead of a "hard parse" every time a static statement is executed. This is especially true on a function that gets called multiple times, from a SQL statement.
(NOTE: Oracle does the same thing, BUT, Oracle does a good job of marking prepared statements as INVALID whenever a referenced object is altered or dropped.)
MySQL opted not to do that, probably because of the overhead of tracking all the dependencies. And, in the vast majority of cases, that overhead is not required.
I think the lesson here is if you are going to use dynamic SQL to create a table that is going to have DIFFERENT columns in it, you are going to have to use dynamic SQL to query that table.
My recommendation is that you avoid using SELECT *, unless your statement is in complete control of the columns being returned, for example, from an inline view. Otherwise, SQL statements that use SELECT * are fundamentally broken... they may work now, but a change to a table (adding a column for example) will break an application.
Q: Please explain how it is not a bug.
It is not a bug because the SELECT statement in your stored procedure is really just shorthand for what is actually happening.
On the first execution of your procedure, MySQL is doing the parse of your query text, and preparing and executing a statement. Basically, equivalent to:
PREPARE s1 FROM 'SELECT * FROM mydata';
EXECUTE s1;
On the second execution of the procedure, MySQL is simply executing the statement that has been previously prepared. Basically, equivalent to:
EXECUTE s1;
On that second execution, you seem to be expecting MySQL to run the equivalent of:
DEALLOCATE PREPARE s1;
PREPARE s1 FROM 'SELECT * FROM mydata';
EXECUTE s1;
You can make a case that this is what MySQL should be doing on the second execution. You could argue that statements prepared during a previous execution of a procedure should be discarded, and re-parsed and re-prepared on subsequent executions.
It would not be wrong for a DBMS to do this. But there would be, as always, the consideration about the impact on performance.
You could also make a case that MySQL should track all the database objects that a particular prepared statement is dependent on. You could argue that whenever one of those database objects is dropped or altered, MySQL should invalidate all the prepared statements (and all other objects) that depend on the altered or dropped object. Again, it would not be wrong for a DBMS to do this. Some DBMSs (such as Oracle) do this quite well. But again, the developers of the DBMS also take performance into consideration when making these design and implementation decisions.
The bottom line is that MySQL does provide you with a way to make happen what you want to happen. It's just that the syntax in your procedure, what you are expecting to make it happen, doesn't actually make it happen.
first of all it is a temp table so really should not be expected to be there, 2nd - it is dropped
I think you are reading something different into the "TEMPORARY" keyword than is defined in the specification. A TEMPORARY table is really just like a regular table, except that it is visible only to the session that created it, and it will be automatically dropped when the MySQL session ends. (We also note that a TEMPORARY table is not displayed by a SHOW TABLES command, and does not appear in the information_schema views.)
As to which tables (TEMPORARY or otherwise) MySQL should expect "to be there", I don't believe the documentation really addresses that, except noting that when a SQL statement is executed, and that statement references an object that does not exist, MySQL will throw an exception.
The same behavior you observe with a TEMPORARY table, you will also observe with a non-TEMPORARY table. The issue is not related to whether the table is defined as TEMPORARY or not.
where does SELECT * compare to PREPARE s1 FROM SELECT *
Those two forms effectively follow the same code path. The first execution of a static SELECT * is effectively equivalent to:
PREPARE s1 FROM 'SELECT *';
EXECUTE s1;
(Note the absence of a DEALLOCATE statement following the exeuction.) On a subsequent execution, the statement is already prepared, so it's effectively equivalent to:
EXECUTE s1;
This is similar to what would happen if you were coding in PHP mysqli
$s1 = $mysqli->prepare("SELECT * FROM mydata");
$mysqli->execute($s1);
/* rename the columns in the mydata table */
$mysqli->execute($s1);
I understand this is relatively old (+6 months) but I encountered this problem in prepared statements, and the only way I got around it was to concatenate the field names and use a prepared statement that effectively calls "select *" but uses the actual field names. I'm using prepared statements to create a temporary table, and the only standard field is the first one, while the rest causes the caching problem on "Select *".
Select fields we want to use into a temp table
Iterate through table rows of field names, and on each iteration:
set sql01 = concat(sql01,',',sFieldName,''); (just fields) and: set sql02 = concat(sql02,',',sFieldName,' varchar(50) '); (fields + field type only, for create table statement)
create the output table:
set #sql = concat('CREATE TEMPORARY TABLE tOutput(FirstField varchar(50), ',sql02,');'); PREPARE STMT FROM #sql; EXECUTE STMT; DEALLOCATE PREPARE STMT;
At the end:
set #sql = concat('SELECT FirstField,',sql01,' FROM tOutput;'); PREPARE STMT FROM #sql; EXECUTE STMT; DEALLOCATE PREPARE STMT;