Search code examples
mysqlsqltemp-tablesprepare

MySQL SELECT * fails on temporary table created by PREPARE using dynamic column and table names after first pass


MySQL 5.2, CentOS 6.4.

MySQL SELECT * fails on temporary table created by PREPARE using dynamic column and table names after first pass, when column name and table name are changed to different values from the first pass.

The work-around is to use a column alias that remains the same from pass to pass.

DROP PROCEDURE IF EXISTS test1;
DELIMITER $$
CREATE PROCEDURE test1( column_name VARCHAR(20), table_name VARCHAR(20) )
BEGIN
    SET @prepared_stmt_arg = 'prepared_stmt_arg_value';

    DROP TABLE IF EXISTS tmp1;
    CREATE TEMPORARY TABLE tmp1
        SELECT 1 AS col_tmp1;

    DROP TABLE IF EXISTS tmp2;
    CREATE TEMPORARY TABLE tmp2
        SELECT 2 AS col_tmp2;

    # drop tmp table if it exists
    DROP TABLE IF EXISTS tmp_test1;

    # prepared statement
    SET @prepared_stmt = 
        CONCAT("
            CREATE TEMPORARY TABLE tmp_test1
                SELECT ? AS prepared_stmt_arg, ", column_name, " # AS constant_col_alias
                    FROM ", table_name, "
            "); # END statement

    # display prepared statement before executing it
    SELECT @prepared_stmt;

    # prepare the statement
    PREPARE ps FROM @prepared_stmt;

    # execute
    EXECUTE ps USING @prepared_stmt_arg;

    # deallocate
    DEALLOCATE PREPARE ps;

    # display
    SELECT * FROM tmp_test1;

END $$
DELIMITER ;

The SELECT statement at the very end of the procedure fails. (You may need to scroll down to see the error message.)

mysql> CALL test1('col_tmp1', 'tmp1');
+---------------------------------------------------------------------------------------------------------------------------------+
| @prepared_stmt                                                                                                                  |
+---------------------------------------------------------------------------------------------------------------------------------+
|
                                CREATE TEMPORARY TABLE tmp_test1
                                        SELECT ? AS prepared_stmt_arg, col_tmp1 # AS constant_col_alias
                                                FROM tmp1
                                 |
+---------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

+-------------------------+----------+
| prepared_stmt_arg       | col_tmp1 |
+-------------------------+----------+
| prepared_stmt_arg_value |        1 |
+-------------------------+----------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

mysql> CALL test1('col_tmp2', 'tmp2');
+---------------------------------------------------------------------------------------------------------------------------------+
| @prepared_stmt                                                                                                                  |
+---------------------------------------------------------------------------------------------------------------------------------+
|
                                CREATE TEMPORARY TABLE tmp_test1
                                        SELECT ? AS prepared_stmt_arg, col_tmp2 # AS constant_col_alias
                                                FROM tmp2
                                 |
+---------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

ERROR 1054 (42S22): Unknown column 'dev.tmp_test1.col_tmp1' in 'field list'

However, if you uncomment the column alias (remove the # just before AS constant_col_alias), all works well. (You may need to scroll down to see Query OK.)

mysql> CALL test1('col_tmp1', 'tmp1');
+-------------------------------------------------------------------------------------------------------------------------------+
| @prepared_stmt                                                                                                                |
+-------------------------------------------------------------------------------------------------------------------------------+
|
                                CREATE TEMPORARY TABLE tmp_test1
                                        SELECT ? AS prepared_stmt_arg, col_tmp1 AS constant_col_alias
                                                FROM tmp1
                                 |
+-------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

+-------------------------+--------------------+
| prepared_stmt_arg       | constant_col_alias |
+-------------------------+--------------------+
| prepared_stmt_arg_value |                  1 |
+-------------------------+--------------------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

mysql> CALL test1('col_tmp2', 'tmp2');
+-------------------------------------------------------------------------------------------------------------------------------+
| @prepared_stmt                                                                                                                |
+-------------------------------------------------------------------------------------------------------------------------------+
|
                                CREATE TEMPORARY TABLE tmp_test1
                                        SELECT ? AS prepared_stmt_arg, col_tmp2 AS constant_col_alias
                                                FROM tmp2
                                 |
+-------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

+-------------------------+--------------------+
| prepared_stmt_arg       | constant_col_alias |
+-------------------------+--------------------+
| prepared_stmt_arg_value |                  2 |
+-------------------------+--------------------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Solution

  • Well it seems to be a bug or a feature (if you want) up to version 5.6.

    See Bug #32868 Stored routines do not detect changes in meta-data.

    Workaround: flush the stored routine cache by doing this:
    CREATE OR REPLACE VIEW tmpview AS SELECT 1;

    Here is SQLFiddle demo MySql 5.1.X
    Here is SQLFiddle demo MySql 5.5.X

    If you comment out CREATE OR REPLACE VIEWtmpviewAS SELECT 1 you'll get your error.

    Here is SQLFiddle demo MySql 5.6.X shows that it's no longer a problem


    Now you have at least these options to go with:

    1. don't use SELECT * use explicit column names instead.
    2. use proposed workaround
    3. upgrade to 5.6.X