перевірка обмеження не працює?


23

У мене є наступна таблиця.

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

Проблема полягає в тому, що CHECKобмеження не працює на віковій колоні. Наприклад, коли я вставляю 222 для вікового поля, MySQL приймає його.

Відповіді:


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

Обмеження CHECK не реалізовані в MySQL. З СТВОРИТИ ТАБЛИЦЮ

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

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.


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
);

Раніше це було доступне лише за допомогою тригерів "ДО ВСТАВКИ" та "ПЕРЕД ОНОВЛЕННЯ":

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, перегляньте цю статтю .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.