检查约束不起作用?


23

我有下表。

create table test (
   id smallint unsigned AUTO_INCREMENT,
   age tinyint not null,
   primary key(id),
   check (age<20)
);

问题在于该CHECK约束不适用于“年龄”列。例如,当我为年龄字段插入222时,MySQL接受它。

Answers:


16

您需要两个触发器来捕获无效的年龄条件

  • 插入之前
  • 更新之前

以下内容基于《MySQL存储过程编程》一书第11章第254-256页中针对MySQL触发器的杰里操纵的错误捕获方法,标题为“使用触发器验证数据”

drop table mytable; 
create table mytable ( 
    id smallint unsigned AUTO_INCREMENT, 
    age tinyint not null, 
    primary key(id) 
); 
DELIMITER $$  
CREATE TRIGGER checkage_bi BEFORE INSERT ON mytable FOR EACH ROW  
BEGIN  
    DECLARE dummy,baddata INT;  
    SET baddata = 0;  
    IF NEW.age > 20 THEN  
        SET baddata = 1;  
    END IF;  
    IF NEW.age < 1 THEN  
        SET baddata = 1;  
    END IF;  
    IF baddata = 1 THEN  
        SELECT CONCAT('Cannot Insert This Because Age ',NEW.age,' is Invalid')  
        INTO dummy FROM information_schema.tables;
    END IF;  
END; $$  
CREATE TRIGGER checkage_bu BEFORE UPDATE ON mytable FOR EACH ROW  
BEGIN  
    DECLARE dummy,baddata INT;  
    SET baddata = 0;  
    IF NEW.age > 20 THEN  
        SET baddata = 1;  
    END IF;  
    IF NEW.age < 1 THEN  
        SET baddata = 1;  
    END IF;  
    IF baddata = 1 THEN  
        SELECT CONCAT('Cannot Update This Because Age ',NEW.age,' is Invalid')  
        INTO dummy FROM information_schema.tables;
    END IF;  
END; $$  
DELIMITER ;  
insert into mytable (age) values (10);
insert into mytable (age) values (15);
insert into mytable (age) values (20);
insert into mytable (age) values (25);
insert into mytable (age) values (35);
select * from mytable;
insert into mytable (age) values (5);
select * from mytable;

结果如下:

mysql> drop table mytable;
Query OK, 0 rows affected (0.03 sec)

mysql> create table mytable (
    ->     id smallint unsigned AUTO_INCREMENT,
    ->     age tinyint not null,
    ->     primary key(id)
    -> );
Query OK, 0 rows affected (0.06 sec)

mysql> DELIMITER $$
mysql> CREATE TRIGGER checkage_bi BEFORE INSERT ON mytable FOR EACH ROW
    -> BEGIN
    ->     DECLARE dummy,baddata INT;
    ->     SET baddata = 0;
    ->     IF NEW.age > 20 THEN
    ->         SET baddata = 1;
    ->     END IF;
    ->     IF NEW.age < 1 THEN
    ->         SET baddata = 1;
    ->     END IF;
    ->     IF baddata = 1 THEN
    ->         SELECT CONCAT('Cannot Insert This Because Age ',NEW.age,' is Invalid')
    ->         INTO dummy FROM information_schema.tables;
    ->     END IF;
    -> END; $$
Query OK, 0 rows affected (0.08 sec)

mysql> CREATE TRIGGER checkage_bu BEFORE UPDATE ON mytable FOR EACH ROW
    -> BEGIN
    ->     DECLARE dummy,baddata INT;
    ->     SET baddata = 0;
    ->     IF NEW.age > 20 THEN
    ->         SET baddata = 1;
    ->     END IF;
    ->     IF NEW.age < 1 THEN
    ->         SET baddata = 1;
    ->     END IF;
    ->     IF baddata = 1 THEN
    ->         SELECT CONCAT('Cannot Update This Because Age ',NEW.age,' is Invalid')
    ->         INTO dummy FROM information_schema.tables;
    ->     END IF;
    -> END; $$
Query OK, 0 rows affected (0.07 sec)

mysql> DELIMITER ;
mysql> insert into mytable (age) values (10);
Query OK, 1 row affected (0.06 sec)

mysql> insert into mytable (age) values (15);
Query OK, 1 row affected (0.05 sec)

mysql> insert into mytable (age) values (20);
Query OK, 1 row affected (0.04 sec)

mysql> insert into mytable (age) values (25);
ERROR 1172 (42000): Result consisted of more than one row
mysql> insert into mytable (age) values (35);
ERROR 1172 (42000): Result consisted of more than one row
mysql> select * from mytable;
+----+-----+
| id | age |
+----+-----+
|  1 |  10 |
|  2 |  15 |
|  3 |  20 |
+----+-----+
3 rows in set (0.00 sec)

mysql> insert into mytable (age) values (5);
Query OK, 1 row affected (0.07 sec)

mysql> select * from mytable;
+----+-----+
| id | age |
+----+-----+
|  1 |  10 |
|  2 |  15 |
|  3 |  20 |
|  4 |   5 |
+----+-----+
4 rows in set (0.00 sec)

mysql>

另请注意,自动增量值不会浪费或丢失。

试试看 !!!


19

在MySQL中未实现CHECK约束。从创建表

CHECK子句被解析,但被所有存储引擎忽略。请参见第12.1.17节“创建表语法”。接受但忽略语法子句的原因是为了兼容性,使从其他SQL Server移植代码更容易,以及运行使用引用创建表的应用程序。请参见第1.8.5节“ MySQL与标准SQL的区别”。

报道,它已经有将近8年的错误了……


13

除了@Rolando提供的不错的触发器解决方案之外,MySQL中还有另一个解决此问题的方法(直到CHECK实现约束)。

如何CHECK在MySQL中模拟一些约束

因此,如果您更喜欢引用完整性约束并希望避免触发器(因为在表中同时包含两个表,这是由于MySQL中的问题),则可以使用另一个小引用表:

CREATE TABLE age_allowed
  ( age TINYINT UNSIGNED NOT NULL
  , PRIMARY KEY (age)
  ) ENGINE = InnoDB ;

用20行填充它:

INSERT INTO age_allowed
  (age)
VALUES
  (0), (1), (2), (3), ..., (19) ;

那么您的表将是:

CREATE TABLE test 
  ( id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT
  , age TINYINT UNSIGNED NOT NULL
  , PRIMARY KEY (id)
  , CONSTRAINT age_allowed__in__test 
      FOREIGN KEY (age)
        REFERENCES age_allowed (age)
  ) ENGINE = InnoDB ;

您必须删除对age_allowed表的写访问权限,以避免意外添加或删除行。

FLOAT不幸的是,此技巧不适用于数据类型列(0.0and 之间的值太多20.0)。


如何CHECK在MySQL(5.7)和MariaDB(从5.2到10.1)中模拟任意约束

由于MariaDB的添加计算列在他们的5.2版本(GA版本:2010-11-10),并在MySQL 5.7(GA版本:2015年10月21日) -他们称他们VIRTUALGENERATED分别-可以坚持,即存储在表-它们调用它们PERSISTENTSTORED分别-我们可以使用它们来简化上述溶液中,甚至更好,它延伸到模拟/执行任意CHECK约束):

如上所述,我们将需要一个帮助表,但是这次只有一行,它将作为“锚定”表。更好的是,该表可用于任何数量的CHECK约束。

然后,我们添加一个计算列,其计算结果完全等于TRUE/ FALSE/ UNKNOWN,这与CHECK约束条件完全相同-但此列FOREIGN KEY对锚表具有约束条件。如果FALSE某些行的条件/列的计算结果为,则由于FK而导致行被拒绝。

如果条件/列的计算结果为TRUEUNKNOWNNULL),则不拒绝行,这与在CHECK约束条件下发生的情况完全相同:

CREATE TABLE truth
  ( t BIT NOT NULL,
    PRIMARY KEY (t)
  ) ENGINE = InnoDB ;

-- Put a single row:

INSERT INTO truth (t)
VALUES (TRUE) ;

-- Then your table would be:
-- (notice the change to `FLOAT`, to prove that we don't need) 
-- (to restrict the solution to a small type)

CREATE TABLE test 
  ( id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
    age FLOAT NOT NULL,
    age_is_allowed BIT   -- GENERATED ALWAYS  
       AS (age >= 0 AND age < 20)             -- our CHECK constraint
       STORED,
    PRIMARY KEY (id),
    CONSTRAINT check_age_must_be_non_negative_and_less_than_20
      FOREIGN KEY (age_is_allowed)
        REFERENCES truth (t)
  ) ENGINE = InnoDB ;

该示例适用于MySQL 5.7版本。在MariaDB(版本5.2+到10.1版本)中,我们只需要修改语法并将该列声明PERSISTENT为即可STORED。在10.2版中,STORED也添加了关键字,因此上面的示例在最新版本的两种版本(MySQL和MariaDB)中均可使用。

如果要强制执行许多CHECK约束(这在许多设计中很常见),则只需为每个约束添加一个计算列和一个外键。我们truth在数据库中只需要一个表。它应该插入一行,然后删除所有写访问权限。


但是,在最新的MariaDB中,我们不再需要执行所有这些杂技,因为CHECK约束已在10.2.1版(alpha版本:2016-Jul-04)中实现!

当前的10.2.2版本仍是beta版,但该功能似乎将在MariaDB 10.2系列的第一个稳定版本中提供。


0

正如我在本文中所解释的,从8.0.16版本开始,MySQL添加了对自定义CHECK约束的支持:

ALTER TABLE topic
ADD CONSTRAINT post_content_check
CHECK (
    CASE
        WHEN DTYPE = 'Post'
        THEN
            CASE
                WHEN content IS NOT NULL
                THEN 1
                ELSE 0
            END
        ELSE 1
    END = 1
);

ALTER TABLE topic
ADD CONSTRAINT announcement_validUntil_check
CHECK (
    CASE
        WHEN DTYPE = 'Announcement'
        THEN
            CASE
                WHEN validUntil IS NOT NULL
                THEN 1
                ELSE 0
            END
        ELSE 1
    END = 1
);

以前,仅在使用BEFORE INSERT和BEFORE UPDATE触发器时才可用:

CREATE
TRIGGER post_content_check BEFORE INSERT
ON topic
FOR EACH ROW
BEGIN
   IF NEW.DTYPE = 'Post'
   THEN
       IF NEW.content IS NULL
       THEN
           signal sqlstate '45000'
           set message_text = 'Post content cannot be NULL';
       END IF;
   END IF;
END;

CREATE
TRIGGER post_content_update_check BEFORE UPDATE
ON topic
FOR EACH ROW
BEGIN
   IF NEW.DTYPE = 'Post'
   THEN
       IF NEW.content IS NULL
       THEN
           signal sqlstate '45000'
           set message_text = 'Post content cannot be NULL';
       END IF;
   END IF;
END;

CREATE
TRIGGER announcement_validUntil_check BEFORE INSERT
ON topic
FOR EACH ROW
BEGIN
   IF NEW.DTYPE = 'Announcement'
   THEN
       IF NEW.validUntil IS NULL
       THEN
           signal sqlstate '45000'
           set message_text = 'Announcement validUntil cannot be NULL';
       END IF;
   END IF;
END;

CREATE
TRIGGER announcement_validUntil_update_check BEFORE UPDATE
ON topic
FOR EACH ROW
BEGIN
   IF NEW.DTYPE = 'Announcement'
   THEN
       IF NEW.validUntil IS NULL
       THEN
           signal sqlstate '45000'
           set message_text = 'Announcement validUntil cannot be NULL';
       END IF;
   END IF;
END;

有关使用8.0.16之前的MySQL版本的数据库触发器模拟CHECK约束的更多详细信息,请参阅本文

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.