점검 제한 조건이 작동하지 않습니까?


23

다음 표가 있습니다.

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

문제는 CHECK제약 조건이 연령 열에서 작동하지 않는다는 것입니다. 예를 들어 연령 필드에 222를 삽입하면 MySQL이 허용합니다.

답변:


16

유효하지 않은 연령 조건을 잡기 위해 두 가지 방아쇠가 필요합니다.

  • 삽입하기 전에
  • 업데이트하기 전에

다음은 '트리거를 사용하여 데이터 유효성 검사' 하위 제목 아래의 MySQL Stored Procedure Programming 책의 11 장, 페이지 254-256에있는 MySQL 트리거에 대해 jerry-rigged 오류 트래핑 방법을 기반으로합니다 .

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 절.“CREATE TABLE 구문”을 참조하십시오. 구문 절을 수락하지만 무시하는 이유는 호환성, 다른 SQL Server에서 코드를 쉽게 이식하고 참조가있는 테이블을 만드는 응용 프로그램을 실행하기 위해서입니다. 1.8.5 절.“표준 SQL과의 MySQL 차이점”을 참조하십시오.

또한 거의 8 년 동안 보고 된 버그 였습니다.


13

@Rolando의 멋진 트리거 솔루션 외에도 MySQL에는이 문제에 대한 또 다른 해결 방법이 있습니다 ( CHECK제약 조건이 구현 될 때까지 ).

CHECKMySQL에서 일부 제약 조건 을 에뮬레이트하는 방법

따라서 참조 무결성 제약 조건을 선호하고 두 테이블에 모두있을 때 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.


CHECKMySQL (5.7) 및 MariaDB (5.2에서 10.1까지)에서 임의의 제약 조건 을 에뮬레이트하는 방법

이후 MariaDB는 5.2 버전에서 계산 된 열을 추가 : (GA 릴리스 2010-11-10 등) 5.7에서 MySQL은 : (GA 릴리스 2015년 10월 21일 그들이 전화 -) VIRTUALGENERATED각각 - 지속 할 수있다, 즉, 저장을 표-그것들 PERSISTENTSTORED각각 호출하고 -우리는 그것들을 사용하여 위의 솔루션을 단순화하고 더 나은 방법으로 확장하여 임의의 CHECK제약 조건 을 에뮬레이션 / 시행 할 수 있습니다 )

위와 같이 이번에는 "앵커 (anchor)"테이블 역할을하는 단일 행이있는 도움말 테이블이 필요합니다. 더 좋은 점은이 테이블을 여러 CHECK제약 조건에 사용할 수 있다는 것 입니다.

그런 다음 제약 조건 과 동일하게 TRUE/ FALSE/로 평가되는 계산 열을 추가 하지만이 열에는 앵커 테이블에 대한 제약 조건이 있습니다. 조건 / 열 이 일부 행 에 대해 평가 되면 FK로 인해 행이 거부됩니다.UNKNOWNCHECKFOREIGN KEYFALSE

조건 / 열이 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 에서는 버전 10.2.1 (알파 릴리스 : 2016-Jul-04)에서 제약 조건이 구현되었으므로 이러한 모든 곡예를 더 이상 수행 할 필요가 없습니다 !CHECK

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

이전에는 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;

8.0.16 이전의 MySQL 버전에 대한 데이터베이스 트리거를 사용하여 CHECK 제한 조건을 에뮬레이트하는 방법에 대한 자세한 내용은 이 기사 를 확인 하십시오 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.