Why doesn't my mysql code work - mysql

I wrote this code and when I execute it it says I have a problem with mysql syntax near the update statement
set #s1 = (select if ((select count(*) from information_schema.columns where table_name='foo' and column_name='bar_id') > 0,
'select 1',
'alter table foo add column bar_id bigint; update foo set bar_id = baz_id;'));
prepare stmt from #s1;
execute stmt;
deallocate prepare stmt;
If I change my code to
set #s1 = (select if ((select count(*) from information_schema.columns where table_name='foo' and column_name='bar_id') > 0,
'select 1',
'alter table foo add column bar_id bigint;'));
prepare stmt from #s1;
execute stmt;
deallocate prepare stmt;
update foo set bar_id = baz_id;
then it works. but I want the update statement inside the if condition.
I cannot make this into a SP.
Error:
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 'update foo set bar_id = baz_id' at line 1

In your first code block, you attempt to prepare a string that contains two SQL statements. Unfortunately, MySQL prepare / execute cannot have multiple statements.
If you can't use SP, I think I'd suggest doing this like so:
set #s1 = (select if ((select count(*) from information_schema.columns where table_name='foo' and column_name='bar_id') > 0,
'select 1',
concat('alter table foo add column bar_id bigint default ', baz_id)));
prepare stmt from #s1;
execute stmt;
deallocate prepare stmt;
alter table foo alter column bar_id drop default;
But, honestly, I'd suggest you minimize DDL changes as those can have unpredictable run-time behavior. In this case, that means adding the foo.bar_id out-of-band and just perform an update as needed.

The problem is that MySQL's prepared statements do not support multi-statements.
If you want to script the database structure updates, easiest way is to use a procedure without dynamic SQL (you might want to check the table_schema as well when you are doing the changes).
create procedure sp_fix_structure()
begin
declare v_cnt int;
select count(*) into v_cnt
from information_schema.columns
where table_schema=database() and table_name='foo' and column_name='bar_id';
if (v_cnt=0) then
alter table foo add column bar_id bigint;
update foo set bar_id = baz_id;
end if;
end

Related

MySQL Stored Procedure fails with ERROR 1064 (42000)

I have written a MySQL stored procedure that will add a new partition to an existing table:
DELIMITER //
DROP PROCEDURE IF EXISTS cr_par //
CREATE PROCEDURE cr_par (
IN p_table VARCHAR(256),
IN p_date DATE
) BEGIN
DECLARE stmt VARCHAR(1024);
DECLARE ddl VARCHAR(512);
DECLARE par_name VARCHAR(20) DEFAULT '';
DECLARE par_no INT DEFAULT 0;
DECLARE lt_value INT DEFAULT 0;
SET par_no = TO_DAYS(p_date) + 1;
SET par_name = CONCAT('p', par_no);
SET lt_value = par_no + 1;
SET ddl = CONCAT('ALTER TABLE ', p_table, ' ADD PARTITION (PARTITION ', par_name, ' VALUES LESS THAN (', lt_value, '))');
PREPARE stmt FROM #ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SELECT ddl;
END //
DELIMITER ;
When I run the stored procedure I get this error:
mysql> CALL cr_par('test', '2021-09-13');
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 'NULL' at line 1
If I comment out the PREPARE, EXECUTE and DEALLOCATE statements and re-run the stored procedure I get this, which is a valid DDL statement:
mysql> CALL cr_par('test', '2021-09-13');
+------------------------------------------------------------------------------+
| ddl |
+------------------------------------------------------------------------------+
| ALTER TABLE test ADD PARTITION (PARTITION p738412 VALUES LESS THAN (738413)) |
+------------------------------------------------------------------------------+
1 row in set (0.01 sec)
I've also tried these variations and all return the same error:
SET ddl = 'ALTER TABLE test ADD PARTITION (PARTITION p738412 VALUES LESS THAN (738413));';
PREPARE stmt FROM #ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
and this...
SET ddl = CONCAT('ALTER TABLE ? ADD PARTITION (PARTITION ? VALUES LESS THAN (?))');
PREPARE stmt FROM #ddl;
EXECUTE stmt USING #p_table, #par_no, #lt_value;
DEALLOCATE PREPARE stmt;
I am using this version of MySQL:
Server version: 8.0.25-15 Percona Server (GPL), Release 15, Revision a558ec2
Does anyone have any ideas what could be causing this, I must be missing something simple?
SET ddl = CONCAT('ALTER TABLE ', p_table, ' ADD PARTITION (PARTITION ', par_name, ' VALUES LESS THAN (', lt_value, '))');
PREPARE stmt FROM #ddl;
Please be aware that ddl and #ddl are two different variables.
The variables you declare with the local variable DECLARE statement have a scope within the body of one stored routine. They are never spelled with a # sigil.
The user-defined variables with the # sigil have a scope of a MySQL session. You don't need to declare these kinds of variables. Just setting the variable to a value implicitly creates the variable.
You cannot SET ddl = ... and expect that string to be read from the #ddl variable. Nor vice-versa.
The PREPARE statement only supports preparing an SQL from a user-defined variable. Which means you must set the #ddl variable to your SQL statement:
SET #ddl = CONCAT('ALTER TABLE ', p_table, ' ADD PARTITION (PARTITION ', par_name, ' VALUES LESS THAN (', lt_value, '))');
Then you don't need to DECLARE ddl at all, since there's no use for that variable.

MySQL - Is it possible to pick a value from a field as an table for "from" or "join" queries? [duplicate]

I am trying to executing a mysql query like this
SET #id := '47';
SET #table := #id+'_2013_2014_voucher';
SELECT * FROM #table;
Delete FROM #table where id=#id
It showing error like this
[Err] 1064 - 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 '#table' at line 1
How I can achieve that?
The usage of dynamic table names within the query is best with Prepared Staments,
also in mysql for concatenation the function is concat
SET #id := '47';
SET #table := concat(#id,'_2013_2014_voucher');
set #qry1:= concat('select * from ',#table);
prepare stmt from #qry1 ;
execute stmt ;
You can do it for the delete query as well
You need to use prepared statements for dynamic table name. Prepared statements support parameters, but you can't use them for table names.
Also to put strings together you have to use CONCAT().
Oh, and you have to do all this in a stored procedure.
Create one like this:
DELIMITER $$
CREATE PROCEDURE sp_exec_dynStmt()
BEGIN
SET #id := 47; /*The single-quotes made it a string, that's not necessary*/
SET #table := CONCAT(#id, '_2013_2014_voucher');
SET #sql := CONCAT('SELECT * FROM ', #table, ';');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET #sql = CONCAT('DELETE FROM ', #table, ' WHERE id = ?;'); /*The id can be a parameter, but not the table name*/
PREPARE stmt FROM #sql;
EXECUTE stmt USING #id;
END $$
DELIMITER ;
Then execute it like this:
CALL sp_exec_dynStmt();
try changing that line with
SET #table = '`' + #id+'_2013_2014_voucher`';
usually I declare variable in this way
SET #id = '47'; (without :)
you should use only : with SET, := to assigning variable in a SELECT

Dynamic table name variable in stored procedure

I want to create a function that will create a unique random id. The parameters will simply be min (the minimum number), max (the maximum number), and tablename (the name of the table to check to see if the id produced by the rand() function already exists).
I have discovered through other posts that you can't pass table names into functions, because functions can't execute dynamic SQL, but you can pass them into stored procedures. I have found numerous examples on StackOverflow of how to pass table names into stored procedures, and they all boil down to using prepared statements.
I have created a stored procedure as shown below:
DELIMITER $$
CREATE DEFINER=`user`#`localhost` PROCEDURE `rand_id`(IN `min` INT, IN `max` INT, IN `tablename` VARCHAR(20) CHARSET utf8, OUT `uid` INT)
BEGIN
DECLARE count_id int;
SET count_id = 1;
SET #s = CONCAT('COUNT(`id`) INTO count_id FROM `', tablename, '` WHERE `id` = ', uid);
WHILE count_id > 0 DO
SET uid = FLOOR(rand() * max + min);
PREPARE stmt from #s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END WHILE;
END$$
DELIMITER ;
Whenever I run the following code:
CALL rand_id(1000000000, 9999999999, 'test', #id);
SELECT #id;
I get this error:
#1064 - 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 'NULL' at line 1
I'm at a loss for what's wrong. I saw somewhere that you can't use user variables inside a stored procedure, but that seems to be incorrect because there are a lot of examples on StackOverflow where the correct solutions do just that.
Sorry for my low level of MySQL understanding. I'm sure my code is fraught with syntax errors and poor design. I appreciate any help I can get. I researched this for quite a while and tried many things but to no avail. The above portion of code is the closest I've been able to get, and yields the least errors, but it's still not working.
Thank you.
EDIT: As per the second example in #Barmar's answer, I changed my code to look like this:
BEGIN
DECLARE count_id int;
SET count_id = 1;
SET #s = CONCAT('SELECT COUNT(`id`) INTO count_id FROM `', tablename, '` WHERE `id` = ?');
PREPARE stmt from #s;
WHILE count_id > 0 DO
SET #uid = FLOOR(rand() * max + min);
EXECUTE stmt USING #uid;
END WHILE;
DEALLOCATE PREPARE stmt;
SET uid = #uid;
END
It seems to have fixed my initial problem but now I get this error:
#1327 - Undeclared variable: count_id
EDIT: Here is my code changed to fit #slaakso's answer, and add in what #Barmar said about using #count_id:
DELIMITER $$
CREATE DEFINER=`mjrinker`#`localhost` PROCEDURE `rand_id`(IN `min` BIGINT, IN `max` BIGINT, IN `tablename` VARCHAR(128) CHARSET utf8, OUT `uid` BIGINT)
BEGIN
SET #count_id = 1;
SET uid = 0;
SET #s = CONCAT('SELECT COUNT(`id`) INTO #count_id FROM `', tablename, '` WHERE `id` = ?');
PREPARE stmt from #s;
WHILE #count_id > 0 DO
SET #uid = FLOOR(rand() * max + min);
EXECUTE stmt USING #uid;
END WHILE;
DEALLOCATE PREPARE stmt;
SET uid = #uid;
END$$
DELIMITER ;
You need to assign #s after you assign the uid variable.
You're also missing the SELECT keyword in your query.
SET #count_id = 1
WHILE #count_id > 0 DO
SET uid = FLOOR(rand() * max + min);
SET #s = CONCAT('SELECT COUNT(`id`) INTO #count_id FROM `', tablename, '` WHERE `id` = ', uid);
PREPARE stmt from #s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END WHILE;
But you should actually just prepare the statement once, using a placeholder, which you fill in when using EXECUTE.
SET #count_id = 1
SET #s = CONCAT('SELECT COUNT(`id`) INTO #count_id FROM `', tablename, '` WHERE `id` = ?');
PREPARE stmt from #s;
WHILE #count_id > 0 DO
SET #uid = FLOOR(rand() * max + min);
EXECUTE stmt USING #uid;
END WHILE;
DEALLOCATE PREPARE stmt;
SET uid = #uid;
Note that the parameters to EXECUTE have to be user variables, that's why I changed uid to #uid there. Then we set the output parameter at the end of the loop.
You also need to use a user variable for INTO #count_id.
First of all it is highly unusual to use random numbers as ID's for tables. Mabye you should consider using AUTO_INCREMENT columns.
If you really want to use random numbers, couple of fixes for the code:
You should use value for uid for the first time you run the query
(without it it will be NULL, therefore the error).
You are missing SELECT in your dynamic query
The "INTO count_id" syntax will not work as count_id is not visible inside the dynamic SQL (use #var variable instead)
Your min and max values are declared as INT's, but your passed parameters exceed the INT range (-2147483648 - 2147483647)

MySQL: IF EXISTS ... TRUNCATE

Want to truncate a table if it exists:
IF EXISTS(SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'mytable') TRUNCATE mytable
Error:
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 'IF EXISTS(SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE
TABLE_NAME = 'mytable') ' at line 1
I tried also to add THEN after ) but the problems seems to be at IF.
You need the two statements below to do that:
create table if not exists <mytable>;
truncate table <mytable>;
So I had a similar issue, and to resolve it, I created this procedure:
DELIMITER $$
DROP PROCEDURE IF EXISTS `truncate_if_exist`$$
CREATE PROCEDURE `truncate_if_exist`(IN tbl_name VARCHAR(150) )
BEGIN
IF EXISTS( SELECT 1 FROM information_schema.TABLES WHERE table_name = tbl_name AND table_schema = DATABASE()) THEN
SET #query = CONCAT('TRUNCATE ', tbl_name);
PREPARE stmt FROM #query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END IF;
END $$
DELIMITER ;
And then called it for each table that I wanted to truncate
for example:
CALL truncate_if_exist('users');
CALL truncate_if_exist('random_tmp_table');
Obviously, if the table does not exist, it will not run the TRUNCATE command.

How to convert an SQL select query into a formatted HTML table within a MySQL function

I'm looking for a way to generate valid HTML code within MySQL (without PHP) by converting any query output into an HTML table.
Here's my progress so far and evidently, I'm stuck. I hope I can get some help, thanks.
1. "dynSQL" - A procedure to take any Select query and create a named table out of it
Since MySQL doesn't allow dynamic queries in functions, I'm calling a procedure that creates a named table, tmp. I can't use a temporary table because info about temporary tables is not available in information_schema (in mysql 5.6)
CREATE DEFINER=`root`#`%` PROCEDURE `dynSQL`(SQL_QUERY TEXT)
BEGIN
set #SQLQ := 'Drop table if exists tmp;';
PREPARE stmt from #SQLQ;
Execute stmt;
SET #SQLQ := concat('create table tmp as ',SQL_QUERY);
PREPARE stmt from #SQLQ;
Execute stmt;
-- I'm adding a auto increment ID column to be able to loop through the rows later
SET #SQLQ := "ALTER TABLE tmp add column CustColHTML_ID INT NOT NULL AUTO_INCREMENT FIRST, ADD primary KEY Id(CustColHTML_ID)";
PREPARE stmt from #SQLQ;
Execute stmt;
DEALLOCATE PREPARE stmt;
END
2. "MakeHTML" - Function to read from the table tmp and return a formatted HTML table
CREATE DEFINER=`root`#`%` FUNCTION `MakeHTML`() RETURNS text CHARSET utf8
DETERMINISTIC
BEGIN
DECLARE HTML text default "<TABLE><TR>";
DECLARE rowCount int default 0;
DECLARE i int default 0;
select concat('<TR>',group_concat('<TD>',column_name,'</TD>' separator ''),'</TR>') into html from information_Schema.`columns` where table_name='tmp';
Select max(CustColHTML_ID) into rowCount from `tmp`; -- Set the row counter
WHILE i<=rowCount DO
-- What do I do here? How do I loop through the columns of table tmp?
set i:=i+1;
END WHILE;
RETURN HTML;
END
As you can see, I'm stuck at looping through the unknown and dynamic columns of table tmp. I read about how a cursor can be used here, but all the examples I saw make use of known columns and assign those into named variables. However, since the query itself is dynamic, I wouldn't know the names of the columns.
I'd really appreciate your time and assistance, thanks!
p.s. I've posted this as a new question because my earlier question was marked as closed as being too broad. I subsequently edited my question but it was still showing as Closed. I've therefore deleted the older question and replaced it with this one.
With a sample table as such:
CREATE TABLE tmp (ID INT, Col1 INT, Col2 INT);
The SQL you would need to generate your HTML is:
SELECT CONCAT('<table>', GROUP_CONCAT(CONCAT('<tr><td>',ID,'</td><td>',Col1,'</td><td>',Col2,'</td><tr>')), '</table>')
FROM tmp;
You can generate this using the INFORMATION_SCHEMA:
SELECT CONCAT
(
'SELECT CONCAT(''<table>'', GROUP_CONCAT(CONCAT(''<tr>'', ',
GROUP_CONCAT(CONCAT('''<td>'',', COLUMN_NAME, ',''</td>''')),
', ''</tr>'')), ''</table>'') FROM tmp'
)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'tmp';
It is then just a case of executing this:
SET #SQL = (
SELECT CONCAT
(
'SELECT CONCAT(''<table>'', GROUP_CONCAT(CONCAT(''<tr>'', ',
GROUP_CONCAT(CONCAT('''<td>'',', COLUMN_NAME, ',''</td>''')),
', ''</tr>'')), ''</table>'') FROM tmp'
)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'tmp'
);
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Example on SQL Fiddle
ADDENDEUM
Forgot to include table headers:
SET #SQL = (
SELECT CONCAT
(
'SELECT CONCAT(''<table><tr>'',',
GROUP_CONCAT(CONCAT('''<th>'',''', COLUMN_NAME, ''',''</th>''')),
', ''</tr>'', GROUP_CONCAT(CONCAT(''<tr>'', ',
GROUP_CONCAT(CONCAT('''<td>'',', COLUMN_NAME, ',''</td>''')),
', ''</tr>'')), ''</table>'') FROM tmp'
)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'tmp'
);
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Example on SQL Fiddle