Відповіді:
Вам потрібні два тригери, щоб визначити недійсний віковий стан
Далі ґрунтується на методі відключення помилок для тригерів 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. З СТВОРИТИ ТАБЛИЦЮ
Становище CHECK аналізується, але всі ігрові системи зберігання ігнорують. Див. Розділ 12.1.17, «СТВОРИТЬСЯ Синтаксис СТАТИ ТАБЛИЦІ». Причиною прийняття, але ігнорування синтаксичних застережень є сумісність, полегшення порту коду з інших 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-липень-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
);
Раніше це було доступне лише за допомогою тригерів "ДО ВСТАВКИ" та "ПЕРЕД ОНОВЛЕННЯ":
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, перегляньте цю статтю .