kiểm tra ràng buộc không hoạt động?


23

Tôi có bảng sau.

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

Vấn đề là các CHECKràng buộc không hoạt động trên cột tuổi. Ví dụ: khi tôi chèn 222 cho trường tuổi, MySQL chấp nhận nó.

Câu trả lời:


16

Những gì bạn cần là hai kích hoạt để nắm bắt tình trạng tuổi không hợp lệ

  • TRƯỚC KHI CHỨNG MINH
  • TRƯỚC KHI CẬP NHẬT

Sau đây là dựa trên phương pháp bẫy lỗi được xử lý bằng jerry cho Trình kích hoạt MySQL từ Chương 11, Trang 254-256 của cuốn sách Lập trình thủ tục lưu trữ MySQL dưới tiêu đề 'Xác thực dữ liệu với trình kích hoạt' :

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;

Đây là kết quả:

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>

Cũng xin lưu ý rằng các giá trị gia tăng tự động không bị lãng phí hoặc mất.

Hãy thử một lần !!!


19

Các ràng buộc KIỂM TRA không được thực hiện trong MySQL. Từ TẠO BẢNG

Mệnh đề CHECK được phân tích cú pháp nhưng bị bỏ qua bởi tất cả các công cụ lưu trữ. Xem Phần 12.1.17, Syntax BẢNG TẠO Cú pháp. Lý do chấp nhận nhưng bỏ qua các mệnh đề cú pháp là để tương thích, để dễ dàng chuyển mã từ các máy chủ SQL khác và để chạy các ứng dụng tạo bảng có tham chiếu. Xem Phần 1.8.5, Sự khác biệt của MySQL MySQL từ SQL SQL tiêu chuẩn.

Đây cũng là một lỗi được báo cáo trong gần 8 năm ...


13

Bên cạnh giải pháp kích hoạt tốt của @Rolando, còn có một cách giải quyết khác về vấn đề này trong MySQL (cho đến khi các CHECKràng buộc được thực thi).

Cách mô phỏng một số CHECKràng buộc trong MySQL

Vì vậy, nếu bạn thích các ràng buộc toàn vẹn tham chiếu và muốn tránh các kích hoạt (vì các vấn đề trong MySQL khi bạn có cả hai trong các bảng của mình), bạn có thể sử dụng một bảng tham chiếu nhỏ khác:

CREATE TABLE age_allowed
  ( age TINYINT UNSIGNED NOT NULL
  , PRIMARY KEY (age)
  ) ENGINE = InnoDB ;

Điền vào nó với 20 hàng:

INSERT INTO age_allowed
  (age)
VALUES
  (0), (1), (2), (3), ..., (19) ;

Sau đó, bảng của bạn sẽ là:

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 ;

Bạn sẽ phải xóa quyền truy cập ghi vào age_allowedbảng, để tránh việc vô tình thêm hoặc xóa hàng.

Thủ thuật này sẽ không hoạt động với FLOATcác cột kiểu dữ liệu, thật không may (quá nhiều giá trị giữa 0.020.0).


Cách mô phỏng các CHECKràng buộc tùy ý trong MySQL (5.7) và MariaDB (từ 5.2 đến 10.1)

Do MariaDB đã thêm các cột được tính toán trong phiên bản 5.2 của họ ( phiên bản GA: 2010-11-10 ) và MySQL trong 5.7 (phiên bản GA: 2015-10-21 ) - mà họ gọi chúng VIRTUALGENERATEDtương ứng - có thể được duy trì, tức là được lưu trữ trong bảng - họ gọi chúng PERSISTENTSTOREDtương ứng - chúng ta có thể sử dụng chúng để đơn giản hóa giải pháp trên và thậm chí tốt hơn, mở rộng nó để mô phỏng / thực thi các CHECKràng buộc tùy ý ):

Như trên, chúng ta sẽ cần một bảng trợ giúp nhưng với một hàng duy nhất lần này sẽ đóng vai trò là bảng "neo". Thậm chí tốt hơn, bảng này có thể được sử dụng cho bất kỳ số lượng CHECKràng buộc.

Sau đó, chúng tôi thêm một cột được tính toán để đánh giá TRUE/ FALSE/ UNKNOWN, chính xác như một CHECKràng buộc sẽ - nhưng cột này có một FOREIGN KEYràng buộc đối với bảng neo của chúng tôi. Nếu điều kiện / cột ước tính FALSEcho một số hàng, các hàng bị từ chối, do FK.

Nếu điều kiện / cột ước tính thành TRUEhoặc UNKNOWN( NULL), các hàng không bị từ chối, chính xác như điều đó sẽ xảy ra với các CHECKràng buộc:

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 ;

Ví dụ dành cho phiên bản MySQL 5.7. Trong MariaDB (phiên bản 5.2 trở lên đến 10.1), chúng ta chỉ cần sửa đổi cú pháp và khai báo cột PERSISTENTthay vì STORED. Trong phiên bản 10.2, STOREDtừ khóa cũng được thêm vào, vì vậy ví dụ trên hoạt động ở cả hai hương vị (MySQL và MariaDB) cho các phiên bản mới nhất.

Nếu chúng ta muốn thực thi nhiều CHECKràng buộc (phổ biến trong nhiều thiết kế), chúng ta chỉ cần thêm một cột được tính toán và một khóa ngoại cho mỗi một trong số chúng. Chúng ta chỉ cần một truthbảng trong cơ sở dữ liệu. Nó nên có một hàng được chèn và sau đó tất cả quyền truy cập ghi bị xóa.


Tuy nhiên, trong MariaDB mới nhất, chúng tôi không phải thực hiện tất cả các màn nhào lộn này nữa, vì các CHECKràng buộc đã được triển khai trong phiên bản 10.2.1 (phiên bản alpha: 2016-Jul-04)!

Phiên bản 10.2.2 hiện tại vẫn là phiên bản beta nhưng có vẻ như tính năng này sẽ có trong phiên bản ổn định đầu tiên của dòng MariaDB 10.2.


0

Như tôi đã giải thích trong bài viết này , bắt đầu với phiên bản 8.0.16, MySQL đã thêm hỗ trợ cho các ràng buộc CHECK tùy chỉnh:

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

Trước đây, điều này chỉ khả dụng khi sử dụng các trình kích hoạt TRƯỚC và TRƯỚC KHI CẬP NHẬT:

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;

Để biết thêm chi tiết về mô phỏng các ràng buộc CHECK bằng cách sử dụng kích hoạt cơ sở dữ liệu cho các phiên bản MySQL trước 8.0.16, sau đó xem bài viết này .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.