ตรวจสอบข้อ จำกัด ไม่ทำงาน?


23

ฉันมีตารางต่อไปนี้

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

ปัญหาคือCHECKข้อ จำกัด ไม่ทำงานในคอลัมน์อายุ ตัวอย่างเช่นเมื่อฉันใส่ 222 สำหรับเขตข้อมูลอายุ MySQL ยอมรับมัน

คำตอบ:


16

สิ่งที่คุณต้องมีคือสิ่งกระตุ้นสองประการเพื่อตรวจสอบสภาพอายุที่ไม่ถูกต้อง

  • ก่อนที่จะใส่
  • ก่อนที่จะอัพเดท

ต่อไปนี้เป็นไปตามวิธีการดักจับข้อผิดพลาด jerry-rigged สำหรับทริกเกอร์ MySQL จากบทที่ 11 หน้า 254-256 ของหนังสือMySQL Stored Procedure Programming Programmingภายใต้หัวข้อย่อย'การตรวจสอบข้อมูลกับทริกเกอร์' :

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 จากCREATE TABLE

ตรวจสอบข้อถูกแยกวิเคราะห์ แต่ละเว้นโดยเครื่องยนต์เก็บข้อมูลทั้งหมด ดูส่วนที่ 12.1.17“ สร้างตารางไวยากรณ์” เหตุผลในการยอมรับ แต่ไม่สนใจคำสั่งทางไวยากรณ์คือเพื่อความเข้ากันได้ทำให้รหัสพอร์ตจากเซิร์ฟเวอร์ SQL อื่น ๆ ง่ายขึ้นและเพื่อเรียกใช้แอปพลิเคชันที่สร้างตารางที่มีการอ้างอิง ดูหัวข้อ 1.8.5“ MySQL แตกต่างจาก SQL มาตรฐาน”

นี่เป็นรายงานข้อผิดพลาดมาเกือบ 8 ปีแล้ว ...


13

นอกจากโซลูชันทริกเกอร์ที่ดีโดย @Rolando ยังมีวิธีแก้ไขปัญหาอื่นใน MySQL (จนกว่าCHECKจะมีการบังคับใช้ข้อ จำกัด )

วิธีเลียนแบบCHECKข้อ จำกัดบางอย่างใน MySQL

ดังนั้นหากคุณต้องการข้อ จำกัด ของ Referential Integrity และต้องการหลีกเลี่ยงทริกเกอร์ (เนื่องจากปัญหาใน 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 ที่เสถียรรุ่นแรก


0

ตามที่ฉันอธิบายในบทความนี้เริ่มต้นด้วยรุ่น 8.0.16, MySQL ได้เพิ่มการสนับสนุนสำหรับข้อ จำกัด การตรวจสอบที่กำหนดเอง

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;

สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับการลอกเลียนแบบ จำกัด การตรวจสอบฐานข้อมูลโดยใช้ทริกเกอร์สำหรับ MySQL เวอร์ชันก่อนหน้า 8.0.16 แล้วตรวจสอบบทความนี้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.