Prüfbedingung funktioniert nicht?

23

Ich habe die folgende Tabelle.

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

Das Problem ist, dass die CHECKEinschränkung für die Altersspalte nicht funktioniert. Zum Beispiel, wenn ich 222 für das Altersfeld einfüge, akzeptiert MySQL es.

ALH
quelle

Antworten:

16

Was Sie brauchen, sind zwei Auslöser, um die ungültige Altersbedingung zu erfassen

  • VOR EINSATZ
  • VOR DEM UPDATE

Das Folgende basiert auf einer Jerry-Rigged-Error-Trapping-Methode für MySQL-Trigger aus Kapitel 11, Seiten 254-256 des Buches MySQL Stored Procedure Programming unter der Überschrift 'Validieren von Daten mit Triggern' :

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;

Hier ist das Ergebnis:

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>

Bitte beachten Sie auch, dass Auto-Inkrement-Werte nicht verschwendet werden oder verloren gehen.

Versuche es !!!

RolandoMySQLDBA
quelle
19

CHECK-Einschränkungen sind in MySQL nicht implementiert. Aus CREATE TABLE

Die CHECK-Klausel wird analysiert, aber von allen Speicher-Engines ignoriert. Siehe auch Abschnitt 12.1.17, „CREATE TABLE-Syntax“. Der Grund für das Akzeptieren, aber Ignorieren von Syntaxklauseln liegt in der Kompatibilität, der Vereinfachung des Portierens von Code von anderen SQL-Servern und der Ausführung von Anwendungen, die Tabellen mit Verweisen erstellen. Siehe auch Abschnitt 1.8.5, „MySQL-Unterschiede zu Standard-SQL“.

Es ist auch ein gemeldeter Fehler seit fast 8 Jahren ...

gbn
quelle
13

Neben der netten Trigger-Lösung von @Rolando gibt es in MySQL eine weitere Problemumgehung (bis CHECKEinschränkungen implementiert sind).

So emulieren Sie einige CHECKEinschränkungen in MySQL

Wenn Sie also referenzielle Integritätsbeschränkungen bevorzugen und Trigger vermeiden möchten (aufgrund der Probleme in MySQL, wenn Sie beide in Ihren Tabellen haben), können Sie eine andere kleine Referenztabelle verwenden:

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

Fülle es mit 20 Zeilen:

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

Dann wäre Ihr Tisch:

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 ;

Sie müssen den Schreibzugriff auf die age_allowedTabelle entfernen, um ein versehentliches Hinzufügen oder Entfernen von Zeilen zu vermeiden.

Dieser Trick funktioniert FLOATleider nicht mit Datentypspalten (zu viele Werte zwischen 0.0und 20.0).


Wie man willkürliche CHECKEinschränkungen in MySQL (5.7) und MariaDB (von 5.2 bis 10.1) emuliert

Da MariaDB hinzugefügt berechnete Spalten in ihrer Version 5.2 (GA - Version: 2010-11-10 ) und MySQL in 5.7 (GA - Version: 2015.10.21 ) - was sie sie nennen VIRTUALund GENERATEDjeweils - , die beibehalten werden können, dh in dem gespeicherte Tisch - sie nennen sie PERSISTENTund STOREDjeweils - wir können sie die obige Lösung zu vereinfachen , verwenden und noch besser, erweitern sie zu emulieren / willkürliche Durchsetzung CHECKEinschränkungen ):

Wie oben benötigen wir eine Hilfetabelle, diesmal jedoch mit einer einzelnen Zeile, die als "Ankertabelle" fungiert. Noch besser ist, dass diese Tabelle für eine beliebige Anzahl von verwendet werden kannCHECK Einschränkungen verwendet werden kann.

Wir fügen dann eine berechnete Spalte , dass auswertet , um entweder TRUE/ FALSE/ UNKNOWN, genau wie ein CHECKZwang würde - aber diese Spalte hat eineFOREIGN KEY Einschränkung unserer Ankertabelle. Wenn die Bedingung / Spalte FALSEfür einige Zeilen mit " 0" bewertet wird , werden die Zeilen aufgrund der FK zurückgewiesen.

Wenn die Bedingung / Spalte als TRUEoder UNKNOWN( NULL) ausgewertet wird , werden die Zeilen nicht genau so abgelehnt, wie es mit CHECKEinschränkungen geschehen sollte :

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 ;

Das Beispiel ist für die MySQL 5.7-Version. In MariaDB (Versionen 5.2+ bis 10.1), wir müssen nur die Syntax ändern und erklären , die Spalte als PERSISTENTstattSTORED . In Version 10.2 wurde das STOREDSchlüsselwort ebenfalls hinzugefügt, sodass das obige Beispiel in beiden Varianten (MySQL und MariaDB) für die neuesten Versionen funktioniert.

Wenn wir viele CHECKEinschränkungen erzwingen möchten (was in vielen Entwürfen üblich ist), müssen wir nur eine berechnete Spalte und einen Fremdschlüssel für jeden von ihnen hinzufügen. Wir brauchen nur einentruth Tabelle in der Datenbank. Es sollte eine Zeile eingefügt und dann alle Schreibrechte entfernt werden.


In der neuesten MariaDB müssen wir diese Akrobatik jedoch nicht mehr ausführen, da CHECKEinschränkungen in Version 10.2.1 (Alpha-Release: 2016-Jul-04) implementiert wurden !

Die aktuelle 10.2.2-Version ist noch eine Beta-Version, aber es scheint, dass die Funktion in der ersten stabilen Version der MariaDB 10.2-Serie verfügbar sein wird.

ypercubeᵀᴹ
quelle
0

Wie ich in diesem Artikel erklärt habe, hat MySQL ab Version 8.0.16 die Unterstützung für benutzerdefinierte CHECK-Einschränkungen hinzugefügt:

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

Bisher war dies nur mit den Triggern BEFORE INSERT und BEFORE UPDATE möglich:

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;

Weitere Informationen zum Emulieren von CHECK-Einschränkungen mithilfe von Datenbank-Triggern für MySQL-Versionen vor 8.0.16 finden Sie in diesem Artikel .

Vlad Mihalcea
quelle