MariaDB 10.1.1:复合语句
有时需要有条件地执行某些 SQL 语句。如果您从 PHP(或 Java 或其他任何语言)应用程序中执行此操作,这很容易。但如果只有纯 SQL 呢?MariaDB 和 MySQL 在 mysql_fix_privilege_tables.sql
脚本中(由 mysql_upgrade 工具应用)使用了两种技术。
- 创建一个包含
IF
语句的存储过程,调用一次然后将其丢弃。这要求用户拥有CREATE ROUTINE
权限,并且mysql.proc
表必须存在且可用(这不一定为真——我们正在从 mysql_upgrade 中执行,对吧?)。 - 使用动态 SQL,例如
SET @str = IF (@have_csv = 'YES', 'CREATE TABLE IF NOT EXISTS general_log ( event_time TIMESTAMP(6) NOT NULL, user_host MEDIUMTEXT NOT NULL, thread_id BIGINT(21) UNSIGNED NOT NULL, server_id INTEGER UNSIGNED NOT NULL, command_type VARCHAR(64) NOT NULL, argument MEDIUMTEXT NOT NULL ) engine=CSV CHARACTER SET utf8 comment="General log"', 'SET @dummy = 0'); PREPARE stmt FROM @str; EXECUTE stmt; DROP PREPARE stmt;
这种方式可读性不高,如果您需要有条件地执行许多语句,则效果不佳。
这可能是标准做法,但我从未理解为什么不能直接在 mysql 命令行工具提示符下简单地使用 SQL 控制语句(除了 CALL
)。想象一个 bash 变体,它只在脚本中支持 if
和 while
,但在命令行交互模式下不支持——您会喜欢它吗?
也许 Antony Curtis 在为 MDEV-5317 中的复合语句贡献补丁时,也问过自己类似的问题。无论如何,我们认为这是一个好主意,并基于 Antony 的贡献实现了此功能。
现在可以直接在 SQL 脚本和 mysql 命令行提示符下使用 BEGIN
, IF
, CASE
, WHILE
, LOOP
, REPEAT
语句——无需在存储程序中。例如,可以将上面的内容重写为
IF @have_csv = 'YES' THEN CREATE TABLE IF NOT EXISTS general_log ( event_time TIMESTAMP(6) NOT NULL, user_host MEDIUMTEXT NOT NULL, thread_id BIGINT(21) UNSIGNED NOT NULL, server_id INTEGER UNSIGNED NOT NULL, command_type VARCHAR(64) NOT NULL, argument MEDIUMTEXT NOT NULL ) engine=CSV CHARACTER SET utf8 comment="General log"; END IF
可以使用 BEGIN ... END
块和循环,而无需 CREATE ROUTINE
权限,即使 mysql.proc
表损坏(或丢失)也可以。这一切都如您所期望地那样工作,没有“标准规定如此”的人为限制。
不过,仍有一些需要注意的限制
- 您不能使用简单的
BEGIN
来启动块,这在历史上用于启动事务。请使用标准语法BEGIN NOT ATOMIC
。 - 来自 mysql 命令行提示符的复合语句不能以标签开头。
- 并非所有可以在存储程序中使用的语句都支持在 mysql 命令行提示符下使用,只有上面列出的语句支持。
不过,这些限制仅适用于顶级语句。例如,如果您需要带标签的循环或 SIGNAL
,则可以启动一个块并将语句放在其中
BEGIN NOT ATOMIC DECLARE v1 INT DEFAULT 500; label1: WHILE v1 > 0 DO INSERT t1 (v) VALUES (v1); SET v1 = v1 - 1; IF (SELECT COUNT(*) FROM t1) > 500 THEN LEAVE label1; END IF; END WHILE label1; END
享受吧!