如何让 MySQL 在 SQL 中引发条件运行时异常

Posted

技术标签:

【中文标题】如何让 MySQL 在 SQL 中引发条件运行时异常【英文标题】:How do I get MySQL to throw a conditional runtime exception in SQL 【发布时间】:2013-06-28 16:54:10 【问题描述】:

我正在编写迁移测试以确保用户是由迁移创建的。如果用户不存在,测试应该抛出一个错误。起初,我以为我可以使用除以零错误来得到我想要的:

SET SESSION sql_mode = 'error_for_division_by_zero';
SELECT 1/COUNT(*) FROM mysql.user WHERE user = 'foo';

但是,如果foo 不存在,这不会引发错误。原来是error_for_division_by_zero affects only INSERT and UPDATE statements。

然后我想也许我可以用错误数量的参数调用一些函数:

SELECT IF(COUNT(*) = 1, 1, date_format(1, 2, 3))
  FROM mysql.user WHERE user = 'foo';

但是即使foo 确实存在,它也会死掉,大概是因为解析器注意到了不正确的参数计数。

我可以写一个a function that emulates raising an exception,但我试图避免这种情况。有没有办法强制 MySQL 有条件地抛出运行时异常?

【问题讨论】:

对我来说听起来像是中间层功能。我不会让数据库做这样的事情,因为它几乎不是“例外”。数据库中不存在的用户不是错误;他们是潜在的新用户。 检查this question @Vatev 我看不到如何让SIGNAL 仅在出现 SQL 条件(IF()CASE 等)时运行。你知道一个例子吗? 那里也有the manual的链接。 【参考方案1】:

不幸的是,没有存储过程或函数就无法完成。不过,我想出了如何在我的应用程序中支持一个功能。借鉴this answer的基本程序思路,我想出了这个:

DELIMITER |

CREATE FUNCTION checkit(doit INTEGER, message VARCHAR(256)) RETURNS INTEGER DETERMINISTIC
BEGIN
    IF doit IS NULL OR doit = 0 THEN
        SIGNAL SQLSTATE 'ERR0R' SET MESSAGE_TEXT = message;
    END IF;
    RETURN doit;
END;
|

这个想法是,该函数可用于触发器,如CHECK 约束,或内联 SQL 语句。回到我最初的需要,如果用户不存在则抛出错误,我现在使用 checkit() 函数,如下所示:

SELECT checkit(COUNT(*), 'User "foo" does not exist')
  FROM mysql.user WHERE user = 'foo';

如果用户foo 存在,则此查询返回一个整数。如果用户不存在,它会抛出一个错误,其中定义的消息。

也想将该函数用于检查约束吗?他是一个例子(模仿this answer),向@rouland-bouman 致敬:

CREATE TRIGGER mytabletriggerexample BEFORE INSERT FOR EACH ROW
BEGIN
    SET @dummy := checkit(
        NEW.important_value) >= (fancy * dancy * calculation),
        'Your meaningful error message goes here'
    );
END;

我宁愿使用DO,而不是设置一个虚拟变量,但是a MySQL bug 阻止了它的工作,唉。

【讨论】:

【参考方案2】:

您可以通过简单地运行返回多行但仅预期一行的子查询来引发没有函数/过程的异常。通常在数据库中已经有一个表,但在这里我为此目的包含了一个表:

create table t (a int);
insert into t values(1);
insert into t values(1);
select * from t;

下面的选择引发异常,因为外部选择期望一个值但内部选择返回多于一个值。

select (select a from t);

您可以将其应用于您的案例:

select case when 
    2*2 = 4 
    then 'OK' 
    else (select a from t)
    end;

select case when 
    2*2 = 5 
    then 'OK' 
    else (select a from t)
    end;

另请参阅:https://dba.stackexchange.com/questions/78594/how-to-conditionally-raise-an-error-in-mysql-without-stored-procedure

【讨论】:

【参考方案3】:

大卫,

为什么需要这个程序?您可以从函数中发出信号(您仍然可以在触发器中调用该函数)

delimiter go

create function f_raise(p_message_text varchar(255)) 
returns int 
begin 
  signal sqlstate '45000' set message_text = p_message_text; 
  return null; 
end;
go

select f_raise('bla');
go

ERROR 1644 (45000): bla

【讨论】:

根据我们的 Twitter 聊天,以及其他人的信息,触发器需要DO f_raise(),但DO 显然会吞下异常。似乎是a bug。如果您不需要在触发器中使用它,那就完美了,但是使用它来模仿 CHECK 约束会失败。 注解:在触发器中仍然可以使用该函数,只要它被分配给一个变量。例如: SET v_dummy = f_raise('bla'); MySQL 和“dummy”这个词是怎么回事? ;-P 更新了我的答案以消除该过程。谢谢!

以上是关于如何让 MySQL 在 SQL 中引发条件运行时异常的主要内容,如果未能解决你的问题,请参考以下文章

mysql'WHERE'条件中的列号

怎么让MySql的数据库在SqlServer中运行

在 SQL (Oracle) 中使用条件交换列

MySql 视图

在 MySQL 中使用“TYPE = InnoDB”会引发异常

MySQL中的条件判断函数,你会用吗?