it-swarm-eu.dev

Check Constraint funktioniert nicht?

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 Einschränkung CHECK in der Altersspalte nicht funktioniert. Wenn ich beispielsweise 222 für das Altersfeld einfüge, akzeptiert MySQL dies.

23
ALH

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

  • VOR EINFÜGEN
  • VOR DEM UPDATE

Das Folgende basiert auf einer von Jerry manipulierten Fehlerüberwachungsmethode für MySQL-Trigger aus Kapitel 11, Seiten 254-256 des Buches MySQL Stored Procedure Programming unter der Überschrift 'Daten mit Triggern validieren' :

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 automatische Inkrementierungswerte nicht verschwendet werden oder verloren gehen.

Versuche es !!!

16
RolandoMySQLDBA

CHECK-Einschränkungen sind in MySQL nicht implementiert. Von TABELLE ERSTELLEN

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

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

19
gbn

Neben der Nice-Trigger-Lösung von @Rolando gibt es eine weitere Problemumgehung für dieses Problem in MySQL (bis CHECK -Einschränkungen implementiert sind).

So emulieren Sie einige CHECK Einschränkungen in MySQL

Wenn Sie also Einschränkungen der referenziellen Integrität 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üllen Sie 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 Tabelle age_allowed entfernen, um ein versehentliches Hinzufügen oder Entfernen von Zeilen zu vermeiden.

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


So emulieren Sie beliebige CHECK Einschränkungen in MySQL (5.7) und MariaDB (von 5.2 bis 10.1)

Seit MariaDB hat in ihrer 5.2-Version berechnete Spalten hinzugefügt (GA-Version: 2010-11-10 ) und MySQL in 5.7 (GA release: 2015-10-21 ) - die sie VIRTUAL bzw. GENERATED nennen - das kann beibehalten werden, dh in der Tabelle gespeichert werden - sie nennen sie PERSISTENT bzw. STORED - wir können sie verwenden, um die obige Lösung zu vereinfachen und noch besser Erweitern Sie es, um beliebige CHECK Einschränkungen zu emulieren/durchzusetzen ):

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 CHECK Einschränkungen verwendet werden kann.

Wir fügen dann eine berechnete Spalte hinzu, die entweder TRUE/FALSE/UNKNOWN ergibt, genau wie eine CHECK -Einschränkung - aber diese Spalte hat eine FOREIGN KEY Einschränkung unserer Ankertabelle. Wenn die Bedingung/Spalte für einige Zeilen FALSE ergibt, werden die Zeilen aufgrund der FK abgelehnt.

Wenn die Bedingung/Spalte TRUE oder UNKNOWN (NULL) ergibt, werden die Zeilen nicht zurückgewiesen, genau wie es mit CHECK Einschrä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) müssen wir nur die Syntax ändern und die Spalte als PERSISTENT anstelle von STORED deklarieren. In Version 10.2 wurde auch das Schlüsselwort STORED hinzugefügt, sodass das obige Beispiel in beiden Varianten (MySQL und MariaDB) für die neuesten Versionen funktioniert.

Wenn wir viele CHECK Einschränkungen erzwingen möchten (was in vielen Designs üblich ist), müssen wir nur eine berechnete Spalte und einen Fremdschlüssel für jede von ihnen hinzufügen. Wir brauchen nur eine truth Tabelle in der Datenbank. Es sollte eine Zeile eingefügt und dann der gesamte Schreibzugriff entfernt werden.


In der neuesten MariaDB müssen wir jedoch nicht mehr alle diese Akrobaten ausführen, da CHECK Einschränkungen wurden implementiert in Version 10.2.1 (Alpha-Version: 04.07.2016)!

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.

13
ypercubeᵀᴹ

Wie ich in diesem Artikel erklärt habe, hat MySQL ab Version 8.0.16 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 verfügbar:

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 unter dieser Artikel .

0
Vlad Mihalcea