Ответы:
Вам нужны два триггера, чтобы поймать недопустимое возрастное состояние
Нижеследующее основано на методе перехвата ошибочных сообщений для триггеров 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>
Также обратите внимание, что значения автоинкремента не теряются и не теряются.
Попробуйте!
Ограничения CHECK не реализованы в MySQL. От CREATE TABLE
Предложение CHECK анализируется, но игнорируется всеми механизмами хранения. См. Раздел 12.1.17, «Синтаксис CREATE TABLE». Причиной принятия, но игнорирования синтаксических предложений является совместимость, упрощение переноса кода с других серверов SQL и запуск приложений, создающих таблицы со ссылками. См. Раздел 1.8.5, «Отличия MySQL от стандартного SQL».
Это также было зарегистрированной ошибкой в течение почти 8 лет ...
Помимо приятного триггерного решения @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.0
и 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 ) - как они их называют VIRTUAL
и GENERATED
соответственно - их можно сохранить, то есть сохранить в таблица - они их называют PERSISTENT
и STORED
соответственно - мы можем использовать их, чтобы упростить вышеуказанное решение и, что еще лучше, расширить его, чтобы эмулировать / применять произвольные CHECK
ограничения ):
Как и выше, нам понадобится справочная таблица, но на этот раз с одной строкой, которая будет выступать в роли «якорной» таблицы. Более того, эту таблицу можно использовать для любого количестваCHECK
ограничений.
Затем мы добавляем вычисляемый столбец, который оценивает либо TRUE
/ FALSE
/ UNKNOWN
, точно так же, как CHECK
ограничение - но этот столбец имеет FOREIGN KEY
ограничение для нашей таблицы привязки. Если условие / столбец оценивается FALSE
для некоторых строк, строки отклоняются из-за FK.
Если условие / столбец оценивается как TRUE
или 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 ;
Пример для версии MySQL 5.7. В MariaDB (версии 5.2+ до 10.1) нам просто нужно изменить синтаксис и объявить столбец PERSISTENT
вместо STORED
. В версии 10.2 также STORED
было добавлено ключевое слово, поэтому приведенный выше пример работает в обоих вариантах (MySQL и MariaDB) для последних версий.
Если мы хотим применить много CHECK
ограничений (что часто встречается во многих проектах), нам просто нужно добавить вычисляемый столбец и внешний ключ для каждого из них. Нам нужна только одна truth
таблица в базе данных. В него должна быть вставлена одна строка, а затем все права на запись удалены.
Однако в последнем MariaDB нам больше не нужно выполнять всю эту акробатику, так как CHECK
ограничения были реализованы. в версии 10.2.1 (альфа-версия: 2016-Jul-04)!
Текущая версия 10.2.2 по-прежнему является бета-версией, но, похоже, эта функция будет доступна в первом стабильном выпуске серии MariaDB 10.2.
Как я объяснил в этой статье , начиная с версии 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;
Более подробную информацию об эмуляции ограничений CHECK с использованием триггеров базы данных для версий MySQL до 8.0.16 можно найти в этой статье .