How do you build and use dynamic sql in a MySQL stored procedure?
After 5.0.13, in stored procedures, you can use dynamic SQL:
delimiter //
CREATE PROCEDURE dynamic(IN tbl CHAR(64), IN col CHAR(64))
BEGIN
SET #s = CONCAT('SELECT ',col,' FROM ',tbl );
PREPARE stmt FROM #s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
//
delimiter ;
Dynamic SQL does not work in functions or triggers. See the MySQL documentation for more uses.
I don't believe MySQL supports dynamic sql. You can do "prepared" statements which is similar, but different.
Here is an example:
mysql> PREPARE stmt FROM
-> 'select count(*)
-> from information_schema.schemata
-> where schema_name = ? or schema_name = ?'
;
Query OK, 0 rows affected (0.00 sec)
Statement prepared
mysql> EXECUTE stmt
-> USING #schema1,#schema2
+----------+
| count(*) |
+----------+
| 2 |
+----------+
1 row in set (0.00 sec)
mysql> DEALLOCATE PREPARE stmt;
The prepared statements are often used to see an execution plan for a given query. Since they are executed with the execute command and the sql can be assigned to a variable you can approximate the some of the same behavior as dynamic sql.
Here is a good link about this:
Don't forget to deallocate the stmt using the last line!
Good Luck!
You can pass thru outside the dynamic statement using User-Defined Variables
Server version: 5.6.25-log MySQL Community Server (GPL)
mysql> PREPARE stmt FROM 'select "AAAA" into #a';
Query OK, 0 rows affected (0.01 sec)
Statement prepared
mysql> EXECUTE stmt;
Query OK, 1 row affected (0.01 sec)
DEALLOCATE prepare stmt;
Query OK, 0 rows affected (0.01 sec)
mysql> select #a;
+------+
| #a |
+------+
|AAAA |
+------+
1 row in set (0.01 sec)
Related
I'm quite new with MySQL. I have to call a stored procedure with output param. I've searched a lot on internet but I've not found the correct solution to my problem. If I call the stored procedure with the #outputParamName it says that I have an error #1064 near NULL. If I call the procedure with the 'outputParamName' without the # it says thath it is not an OUT or INOUT correct param. Someone can help me please?
the stored procedure just have to check if surname and name in DB exists on the same row:
CREATE PROCEDURE InsertProc (INOUT existsInDb BOOLEAN,
IN dbName VARCHAR(50)
IN tableName VARCHAR(50)
IN surnameNew VARCHAR(50)
IN nameNew VARCHAR(50))
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
BEGIN
DECLARE rowSurnameName int;
SET #sqlSel = CONCAT('SELECT COUNT(*) INTO ', rowSurnameName, ' FROM ', dbName, '.', tableName, ' WHERE COGNOME=', surnameNew, ' AND NOME=', nameNew);
PREPARE stmtSel FROM #sqlSel;
EXECUTE stmtSel;
DEALLOCATE PREPARE stmtSel;
IF (rowSurnameName=0) THEN
SET #sqlIns = CONCAT('INSERT INTO ', dbName, '.', tableName, ' (NOME, COGNOME) VALUES (', nameNew, ', ', surnameNew,')');
PREPARE stmtIns FROM #sqlIns;
EXECUTE stmtIns;
DEALLOCATE PREPARE stmtIns;
SELECT false INTO existsInDb;
ELSE SELECT true INTO existsInDb;
END IF;
END
The CALL Statement is:
SET #dbName = 'DBNAME';
SET #tableName = 'DBTABLE';
SET #surname = 'SURNAME';
SET #name = 'NAME';
PREPARE s FROM 'CALL InsertProc(?,?,?,?,?)';
EXECUTE s USING #existsInDB, #dbName, #tableName, #surname, #name;
SELECT #existsInDB;
And the ERROR Line is:
#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
A couple of notes:
You can't use a local variable in a prepared statement.
C.1 Restrictions on Stored Programs
...
SELECT ... INTO local_var cannot be used as a prepared statement.
...
The error shown in your question occurs because the local variable rowSurnameName has the value NULL, see:
mysql> DROP PROCEDURE IF EXISTS `InsertProc`;
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> DELIMITER //
mysql> CREATE PROCEDURE `InsertProc`()
-> BEGIN
-> DECLARE `rowSurnameName` INT;
-> SELECT `rowSurnameName`;
-> SET #`sqlSel` := CONCAT('SELECT COUNT(*) INTO ', `rowSurnameName`);
-> SELECT #`sqlSel`;
-> END//
Query OK, 0 rows affected (0.00 sec)
mysql> DELIMITER ;
mysql> CALL `InsertProc`;
+------------------+
| `rowSurnameName` |
+------------------+
| NULL |
+------------------+
1 row in set (0.00 sec)
+-----------+
| #`sqlSel` |
+-----------+
| NULL |
+-----------+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
If you try to use the rowSurnameName local variable in the prepared statement, you will get the error:
mysql> DROP PROCEDURE IF EXISTS `InsertProc`;
Query OK, 0 rows affected (0.00 sec)
mysql> DELIMITER //
mysql> CREATE PROCEDURE `InsertProc`()
-> BEGIN
-> DECLARE `rowSurnameName` INT;
-> SET #`sqlSel` := CONCAT('SELECT 100 INTO `rowSurnameName`');
-> SELECT #`sqlSel`;
-> PREPARE `stmtSel` FROM #`sqlSel`;
-> EXECUTE `stmtSel`;
-> DEALLOCATE PREPARE `stmtSel`;
-> END//
Query OK, 0 rows affected (0.00 sec)
mysql> DELIMITER ;
mysql> CALL `InsertProc`;
+----------------------------------+
| #`sqlSel` |
+----------------------------------+
| SELECT 100 INTO `rowSurnameName` |
+----------------------------------+
1 row in set (0.00 sec)
ERROR 1327 (42000): Undeclared variable: rowSurnameName
You need to use 9.4 User-Defined Variables in your prepared statement:
mysql> DROP PROCEDURE IF EXISTS `InsertProc`;
Query OK, 0 rows affected (0.00 sec)
mysql> DELIMITER //
mysql> CREATE PROCEDURE `InsertProc`()
-> BEGIN
-> SET #`sqlSel` := CONCAT('SELECT 100 INTO #`rowSurnameName`');
-> SELECT #`sqlSel`;
-> PREPARE `stmtSel` FROM #`sqlSel`;
-> EXECUTE `stmtSel`;
-> DEALLOCATE PREPARE `stmtSel`;
-> IF (#`rowSurnameName` = 0) THEN
-> SELECT 'NotExistsInDbAndInsert';
-> ELSE
-> SELECT 'existsInDb';
-> END IF;
-> END//
Query OK, 0 rows affected (0.00 sec)
mysql> DELIMITER ;
mysql> CALL `InsertProc`;
+-----------------------------------+
| #`sqlSel` |
+-----------------------------------+
| SELECT 100 INTO #`rowSurnameName` |
+-----------------------------------+
1 row in set (0.00 sec)
+------------+
| existsInDb |
+------------+
| existsInDb |
+------------+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
I'm trying to write MySQL script dropping some tables selected by pattern but my procedure doesn't compile. Could anybody please advice what is wrong with it please?
delimiter #
drop procedure if exists drop_audit_tables #
create procedure drop_audit_tables()
begin
declare done int default false;
declare cmd varchar(4000);
declare cmds cursor for select 'drop table [' + table_name + ']' from information_schema.tables where table_name like '%_audit';
declare continue handler for not found set done = true;
open cmds;
tLoop: loop
fetch cmds into cmd;
if done then
leave tLoop;
end if;
PREPARE STMT FROM cmd;
EXECUTE STMT;
DEALLOCATE PREPARE STMT;
end loop tLoop;
close cmds;
end #
the error message:
[42000][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 'cmd; EXECUTE STMT; DEALLOCATE PREPARE STMT; end loop tLoop; close cm' at line 13
You can avoid the cursor:
mysql> DROP TABLE IF EXISTS `one_audit`;
Query OK, 0 rows affected (0.00 sec)
mysql> DROP TABLE IF EXISTS `two_audit`;
Query OK, 0 rows affected (0.01 sec)
mysql> DROP TABLE IF EXISTS `three_audit`;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE `one_audit`(`a` INT);
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE `two_audit`(`a` INT);
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE `three_audit`(`a` INT);
Query OK, 0 rows affected (0.00 sec)
mysql> SET #`drop_tables` := (
-> SELECT
-> CONCAT('DROP TABLE IF EXISTS ',
-> GROUP_CONCAT(CONCAT('`', `TABLE_NAME`, '`') SEPARATOR ', '))
-> FROM
-> `information_schema`.`TABLES`
-> WHERE
-> `TABLE_SCHEMA` = DATABASE() AND
-> `TABLE_TYPE` = 'BASE TABLE' AND
-> `TABLE_NAME` LIKE '%_audit'
-> );
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT #`drop_tables`;
+--------------------------------------------------------------+
| #`drop_tables` |
+--------------------------------------------------------------+
| DROP TABLE IF EXISTS `one_audit`, `three_audit`, `two_audit` |
+--------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> PREPARE `exec` FROM #`drop_tables`;
Query OK, 0 rows affected (0.00 sec)
Statement prepared
mysql> EXECUTE `exec`;
Query OK, 0 rows affected (0.00 sec)
mysql> DEALLOCATE PREPARE `exec`;
Query OK, 0 rows affected (0.00 sec)
You must be careful with the system variable group_concat_max_len.
UPDATE
Using cursor:
DELIMITER #
DROP PROCEDURE IF EXISTS `drop_audit_tables`#
CREATE PROCEDURE `drop_audit_tables`()
BEGIN
DECLARE `done` BOOL DEFAULT 0;
DECLARE `cmd` VARCHAR(4000);
DECLARE `cmds` CURSOR FOR
SELECT
CONCAT('DROP TABLE IF EXISTS `', `TABLE_NAME`, '`')
FROM
`information_schema`.`TABLES`
WHERE
`TABLE_SCHEMA` = DATABASE() AND
`TABLE_TYPE` = 'BASE TABLE' AND
`TABLE_NAME` LIKE '%_audit';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET `done` := 1;
OPEN `cmds`;
`tLoop`: LOOP
FETCH `cmds` INTO `cmd`;
IF `done` THEN
CLOSE `cmds`;
LEAVE `tLoop`;
END IF;
SET #`cmd` := `cmd`;
PREPARE `STMT` FROM #`cmd`;
EXECUTE `STMT`;
DEALLOCATE PREPARE `STMT`;
END LOOP `tLoop`;
SET #`cmd` := NULL;
END#
CALL `drop_audit_tables`#
DELIMITER ;
14.5.1 PREPARE
Syntax
PREPARE stmt_name FROM preparable_stmt
...
preparable_stmt is either a string literal or a user variable that
contains the text of the SQL statement.
...
Your line:
declare cmds cursor for select 'drop table [' + table_name + ']' from information_schema.tables where table_name like '%_audit';
.. uses table_name without defining it first.
Try defining it first with something like:
create procedure drop_audit_tables(IN table_name VARCHAR(64))
You may want to consider the security implications of taking a variable directly from the stored procedure and placing it into your ad-hoc query.
Still, define table_name somewhere. In this case table_name would be supplied as an argument to your stored procedure. Your task then is to gather an array of table names and run this code in a for/foreach loop.
Basic (non-robust) PHP (PDO)
/* Get the audit tables. */
$stmt = $pdo->query(`CALL get_audit_tables()`)
$tables = $stmt->fetch();
$stmt->close()
$stmt = $pdo->prepare('CALL drop_audit_tables(:table)')
/* Drop each audit table. */
foreach($tables as $table)
{
$stmt->bindParam(:table, $table, PDO::PARAM_STR)
$stmt->execute();
}
$stmt->close();
Something like that, anyway.
MySQL: CREATE PROCEDURE
Specifying a parameter as IN, OUT, or INOUT is valid only for a PROCEDURE. For a FUNCTION, parameters are always regarded as IN parameters.
PHP Manual: PDO::prepare
Prepares an SQL statement to be executed by the PDOStatement::execute() method. The SQL statement can contain zero or more named (:name) or question mark (?) parameter markers for which real values will be substituted when the statement is executed.
A solution like this would make your life easier. You only need to define a basic query that finds your audit tables. Less code. Simpler.
SET #s:='';
SELECT #s:= CONCAT('SHOW GRANTS FOR \'',user,'\'#\'',host,'\';') FROM mysql.user where user = 'root';
PREPARE stmt FROM #s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
I dont mind to achieve this using any stored proc. Let say I have 2 users with root
'root'#'%' and 'root'#'localhost'
What I want is to get result of multiple prepare statements. But the above only executes the last one (ie 'root'#'localhost'). The two challenges I faced
PREPARE / EXECUTE stmt can execute only one query at a time
Only Stored proc can use loop
Objective: All I want is to execute two 'SHOW GRANTS FOR' in one shot
This in also not the answer, as the question is more on the execution of multiple prepared statements, Another example can be like in case we need to OPTIMIZE all tables in a database, #wchiquito answer is accepted for that reason
Finally Percona already came up with pt-show-grants
One more way I tried myself to get it along with the database-name. But this may not work on Version 5.7, In a more readable format would be
(SELECT `GRANTEE`, `TABLE_SCHEMA`, (CASE
WHEN GROUP_CONCAT(`PRIVILEGE_TYPE`) = 'SELECT' THEN 'READ ONLY'
WHEN (LOCATE('DELETE',GROUP_CONCAT(`PRIVILEGE_TYPE`))
+ LOCATE('UPDATE',GROUP_CONCAT(`PRIVILEGE_TYPE`))
+ LOCATE('INSERT',GROUP_CONCAT(`PRIVILEGE_TYPE`))
+ LOCATE('SELECT',GROUP_CONCAT(`PRIVILEGE_TYPE`))) >= 4 THEN 'READ+WRITE'
ELSE GROUP_CONCAT(`PRIVILEGE_TYPE` ORDER BY `PRIVILEGE_TYPE`)
END) AS 'PRIVILEGE_TYPE'
FROM INFORMATION_SCHEMA.SCHEMA_PRIVILEGES
WHERE GRANTEE NOT REGEXP '^......$'
GROUP BY `GRANTEE`, `TABLE_SCHEMA`)
UNION
(SELECT `GRANTEE`, 'All Databases' AS `TABLE_SCHEMA`, (CASE
WHEN GROUP_CONCAT(`PRIVILEGE_TYPE`) = 'SELECT' THEN 'READ ONLY'
WHEN (LOCATE('DELETE',GROUP_CONCAT(`PRIVILEGE_TYPE`))
+ LOCATE('UPDATE',GROUP_CONCAT(`PRIVILEGE_TYPE`))
+ LOCATE('INSERT',GROUP_CONCAT(`PRIVILEGE_TYPE`))
+ LOCATE('SELECT',GROUP_CONCAT(`PRIVILEGE_TYPE`))) >= 4 THEN 'READ+WRITE'
ELSE GROUP_CONCAT(`PRIVILEGE_TYPE` ORDER BY `PRIVILEGE_TYPE`)
END) AS 'PRIVILEGE_TYPE'
FROM INFORMATION_SCHEMA.USER_PRIVILEGES
WHERE GRANTEE NOT REGEXP '^......$'
GROUP BY `GRANTEE`
HAVING GROUP_CONCAT(`PRIVILEGE_TYPE`) != 'USAGE')
DROP PROCEDURE IF EXISTS `db`.`SP_UserRolePriv`;
DELIMITER $$
CREATE PROCEDURE `db`.`SP_UserRolePriv`()
BEGIN
DECLARE done int(10) default 0;
DECLARE Var_user varchar(25);
DECLARE Var_host varchar(25);
DECLARE QST text;
DECLARE cur_urp CURSOR FOR SELECT user,host FROM mysql.user;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cur_urp;
tab_loop: LOOP
FETCH cur_urp INTO Var_user,Var_host;
IF done THEN
LEAVE tab_loop;
END IF;
-- SELECT CONCAT(Var_user,'#',Var_host) "User";
SET #QST = CONCAT('SHOW GRANTS FOR ',CONCAT('''',Var_user,'''#''',Var_host,''';'));
-- SELECT QST;
PREPARE stmt FROM #QST;
EXECUTE stmt ;
DEALLOCATE PREPARE stmt;
END LOOP;
CLOSE cur_urp;
END $$
DELIMITER ;
CALL `mysqlagent`.`SP_UserRolePriv`();
With the appropriate privileges, you can do something like:
mysql> system rm -f /tmp/get_show_grants.sql;
mysql> SELECT CONCAT('system rm -f /tmp/show_grants.sql;
SELECT CONCAT(\'SHOW GRANTS FOR \'\'\', `user`, \'\'\'#\'\'\', `host`,\'\'\';\')
INTO OUTFILE \'/tmp/show_grants.sql\'
FROM `mysql`.`user`
WHERE `user` = \'root\';
source /tmp/show_grants.sql;
system rm -f /tmp/show_grants.sql /tmp/get_show_grants.sql;
') INTO OUTFILE '/tmp/get_show_grants.sql';
Query OK, 1 row affected (0.00 sec)
mysql> source /tmp/get_show_grants.sql;
Query OK, 1 row affected (0.00 sec)
+---------------------------------------------------------------------+
| Grants for root#localhost |
+---------------------------------------------------------------------+
| GRANT ALL PRIVILEGES ON *.* TO 'root'#'localhost' WITH GRANT OPTION |
| GRANT PROXY ON ''#'' TO 'root'#'localhost' WITH GRANT OPTION |
+---------------------------------------------------------------------+
2 rows in set (0.00 sec)
I would like to make dynamic queries in within my procedure.
SQL Server has neat sp_executesql procedure for such tasks, is there anything in MySQL which can help me to achieve similar functionality?
AFAIK there is nothing exactly same. However, you can use a prepared statement, like:
mysql> PREPARE stmt1 FROM 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse';
mysql> SET #a = 3;
mysql> SET #b = 4;
mysql> EXECUTE stmt1 USING #a, #b;
+------------+
| hypotenuse |
+------------+
| 5 |
+------------+
mysql> DEALLOCATE PREPARE stmt1;
Copied from here:
http://dev.mysql.com/doc/refman/5.0/en/sql-syntax-prepared-statements.html
You will probably have to use prepared statements for that purpose using MySQL:
SET #query = 'INSERT INTO tbl (a) VALUES (?)';
PREPARE stmt FROM #query;
...
SET #param = 'hello';
EXECUTE stmt USING #param;
...
DEALLOCATE PREPARE stmt;
I have the table "mytable" that contains the "columnname" field wich is the name of a column in mytable2.
I use this one for the selection:
SET #DptScn = (SELECT columnname FROM mytable WHERE tablename = 'CustomTableName' AND fieldlabel = 'CustomField');
SET #identifiedid=144;
but, when I try:
SELECT #DptScn FROM mytable2 WHERE identifiedid = #identifiedid;
this give me NOT the content of the field but the name containted into variable #DptScn...
Any advice?
I can't use Prepared Statement because I'm in a Trigger...
UPDATE:
As suggested by spencer7593 I'm creating a procedure:
DROP PROCEDURE IF EXISTS p_t;
DELIMITER $$
CREATE PROCEDURE p_t (IN DptTcn VARCHAR(255), IN tid INT, OUT tT INT)
BEGIN
SET #DptTcn = DptTcn;
SET #tid = tid;
SET #sql = CONCAT('SELECT #DptTcn FROM mytable3 WHERE tid = #tid');
PREPARE stmt FROM #sql;
EXECUTE stmt;
END$$
DELIMITER ;
Then I try it:
SET #DptTcn = (SELECT columnname mytable WHERE tablename = 'CustomTableName' AND fieldlabel = 'CustomField');
SET #identifiedid=145;
CALL proc_ticket(#DptTcn, #identifiedid, #DptT);
But I receive a:
#2014 - Commands out of sync; you can't run this command now
One option to consider is creating a PROCEDURE that makes use of prepared statements, and then calling the the stored procedure from the trigger.
The SQL statement you execute to get the value from a particular column MUST have the column_name specified in the SQL text; this can't be derived "dynamically" in the execution of the statement.
To achieve something like this, you'll need to run two separate statements; one to get the column_name; the second to "SELECT column_name FROM". And the MySQL provided mechanism for executing that second query is a prepared statement.
Followup
Here's an example. I tried to get this built in SQLFiddle, but wasn't able to get it working (it just hung. So, here's the output from a mysql command line client instead.
(All of the statements below use the same delimiter // because we can't use a semicolon as a delimiter for the stored procedure. In SQLFiddle, we have to use the same delimiter on all statements, and the // just happens to be one of the options in SQLFiddle.)
mysql> DELIMITER //
mysql> CREATE PROCEDURE foo(IN colname VARCHAR(255), IN id INT, OUT val VARCHAR(255))
-> BEGIN
-> -- handler for "Unknown column" and "No data" exceptions
-> DECLARE EXIT HANDLER FOR 1054, 1329 BEGIN SET val = NULL; END;
-> SET #sql = CONCAT('SELECT ',colname,' INTO #val FROM t WHERE id = ',id,' LIMIT 1');
-> PREPARE stmt FROM #sql;
-> EXECUTE stmt;
-> SET val = #val;
-> END//
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE t (id INT, attr VARCHAR(4), ball VARCHAR(4))//
Query OK, 0 rows affected (0.11 sec)
mysql> INSERT INTO t VALUES (1, 'abcd','efgh'),(2,'ijkl','mnop')//
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> CALL foo('attr',1,#attr_1)//
Query OK, 0 rows affected (0.00 sec)
mysql> CALL foo('attr',2,#attr_2)//
Query OK, 0 rows affected (0.00 sec)
mysql> CALL foo('ball',1,#ball_1)//
Query OK, 0 rows affected (0.00 sec)
mysql> CALL foo('ball',2,#ball_2)//
Query OK, 0 rows affected (0.00 sec)
mysql> CALL foo('attr',777,#err_bad_id)//
Query OK, 0 rows affected (0.00 sec)
mysql> CALL foo('badcol',1,#err_badcol)//
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT #attr_1
-> , #attr_2
-> , #ball_1
-> , #ball_2
-> , #err_bad_id
-> , #err_badcol//
+---------+---------+---------+---------+-------------+-------------+
| #attr_1 | #attr_2 | #ball_1 | #ball_2 | #err_bad_id | #err_badcol |
+---------+---------+---------+---------+-------------+-------------+
| abcd | ijkl | efgh | mnop | NULL | NULL |
+---------+---------+---------+---------+-------------+-------------+
1 row in set (0.00 sec)
mysql> DELIMITER ;
you should create a SP and give the column name.
create proc dbo.TestGetData(#DptScn nvarchar(256))
as
begin
set nocount on
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = 'SELECT #DptScn FROM mytable2 WHERE identifiedid = 144'
exec sp_executesql #SQL, N'#DptScn nvarchar(256)', #DptScn =#DptScn
end
Then
exec dbo.TestGetData 'Column1'