我有下表。
create table test (
id smallint unsigned AUTO_INCREMENT,
age tinyint not null,
primary key(id),
check (age<20)
);
Run Code Online (Sandbox Code Playgroud)
问题是CHECK约束不适用于年龄列。例如,当我为年龄字段插入 222 时,MySQL 接受它。
Rol*_*DBA 17
你需要的是两个触发器来捕捉无效的年龄条件
以下内容基于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;
Run Code Online (Sandbox Code Playgroud)
结果如下:
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>
Run Code Online (Sandbox Code Playgroud)
另请注意,自动增量值不会浪费或丢失。
试一试 !!!
ype*_*eᵀᴹ 13
除了@Rolando 提供的不错的触发器解决方案之外,MySQL 中还有另一个解决此问题的方法(直到CHECK实现约束)。
如何模拟CHECKMySQL 中的一些约束
因此,如果您更喜欢引用完整性约束并希望避免触发器(因为当您的表中同时存在两个时 MySQL 中的问题),您可以使用另一个小引用表:
CREATE TABLE age_allowed
( age TINYINT UNSIGNED NOT NULL
, PRIMARY KEY (age)
) ENGINE = InnoDB ;
Run Code Online (Sandbox Code Playgroud)
用 20 行填充它:
INSERT INTO age_allowed
(age)
VALUES
(0), (1), (2), (3), ..., (19) ;
Run Code Online (Sandbox Code Playgroud)
那么你的表将是:
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 ;
Run Code Online (Sandbox Code Playgroud)
您必须删除对age_allowed表的写访问权限,以避免意外添加或删除行。
FLOAT不幸的是,这个技巧不适用于数据类型列(0.0和之间的值太多20.0)。
如何模拟CHECKMySQL (5.7) 和 MariaDB(从 5.2 到 10.1)中的任意约束
由于MariaDB的添加计算列在他们的5.2版本(GA版本:2010-11-10),并在MySQL 5.7(GA版本:2015年10月21日) -他们称他们VIRTUAL并GENERATED分别-可以坚持,即存储在表 - 他们分别称它们为PERSISTENT和STORED- 我们可以使用它们来简化上述解决方案,甚至更好的是,扩展它以模拟/强制执行任意CHECK约束):
如上所述,我们需要一个帮助表,但这次只有一行作为“锚”表。更好的是,该表可用于任意数量的CHECK约束。
然后,我们添加一个计算列计算结果为TRUE/ FALSE/ UNKNOWN,正好作为CHECK约束会-但此列有FOREIGN KEY约束我们的主播台。如果FALSE某些行的条件/列评估为 ,则由于 FK,这些行将被拒绝。
如果条件/列的计算结果为TRUEor UNKNOWN( NULL),则不会拒绝行,这与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 ;
Run Code Online (Sandbox Code Playgroud)
该示例适用于 MySQL 5.7 版本。在 MariaDB(版本 5.2+ 到 10.1)中,我们只需要修改语法并将列声明为PERSISTENT而不是STORED. 在版本 10.2 中STORED也添加了关键字,因此上面的示例适用于最新版本的两种风格(MySQL 和 MariaDB)。
如果我们想要强制执行许多CHECK约束(这在许多设计中很常见),我们只需要为每个约束添加一个计算列和一个外键。我们只需要truth数据库中的一张表。它应该插入一行,然后删除所有写访问。
然而,在最新的 MariaDB 中,我们不再需要执行所有这些杂技,因为10.2.1 版(alpha 版本:2016 年 7 月 4 日)中CHECK已经实现了约束!
当前的 10.2.2 版本仍然是测试版,但似乎该功能将在 MariaDB 10.2 系列的第一个稳定版本中可用。
| 归档时间: |
|
| 查看次数: |
34625 次 |
| 最近记录: |