controllare il vincolo non funziona?


23

Ho la seguente tabella.

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

Il problema è che il CHECKvincolo non funziona nella colonna Età. Ad esempio, quando inserisco 222 per il campo di età, MySQL lo accetta.

Risposte:


16

Sono necessari due trigger per rilevare la condizione di età non valida

  • PRIMA DI INSERIRE
  • PRIMA DI AGGIORNARE

Quanto segue si basa su un metodo di trappola degli errori jerry-rigged per i trigger MySQL del capitolo 11, pagine 254-256 del libro MySQL Stored Procedure Programming nella sottovoce "Convalida dei dati con trigger" :

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;

Ecco il risultato:

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>

Si noti inoltre che i valori di incremento automatico non vengono sprecati o persi.

Provaci !!!


19

I vincoli CHECK non sono implementati in MySQL. Da CREATE TABLE

La clausola CHECK viene analizzata ma ignorata da tutti i motori di archiviazione. Vedere la Sezione 12.1.17, "CREAZIONE della sintassi della TABELLA". Il motivo per accettare ma ignorare le clausole di sintassi è la compatibilità, per facilitare il porting del codice da altri server SQL e per eseguire applicazioni che creano tabelle con riferimenti. Vedi Sezione 1.8.5, "Differenze MySQL da SQL standard".

È stato anche segnalato un bug per quasi 8 anni ...


13

Oltre alla simpatica soluzione trigger di @Rolando, c'è un'altra soluzione a questo problema in MySQL (fino CHECKall'implementazione dei vincoli).

Come emulare alcuni CHECKvincoli in MySQL

Quindi, se preferisci i vincoli di integrità referenziale e vuoi evitare i trigger (a causa dei problemi in MySQL quando hai entrambi nelle tue tabelle), puoi usare un'altra piccola tabella di riferimento:

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

Riempilo con 20 righe:

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

Quindi il tuo tavolo sarebbe:

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 ;

Dovrai rimuovere l'accesso in scrittura alla age_allowedtabella, per evitare l'aggiunta o la rimozione accidentale di righe.

Questo trucco non funzionerà con le FLOATcolonne del tipo di dati, sfortunatamente (troppi valori tra 0.0e 20.0).


Come emulare CHECKvincoli arbitrari in MySQL (5.7) e MariaDB (da 5.2 a 10.1)

Da quando MariaDB ha aggiunto le colonne calcolate nella loro versione 5.2 ( versione GA 10-10-2010 ) e MySQL nella versione 5.7 (versione GA: 21-10-2015 ) - che le chiamano VIRTUALe GENERATEDrispettivamente - che possono essere mantenute, ovvero archiviate nella tabella - le chiamano PERSISTENTe STOREDrispettivamente - possiamo usarle per semplificare la soluzione di cui sopra e, meglio ancora, estenderla per emulare / applicare CHECKvincoli arbitrari ):

Come sopra, avremo bisogno di una tabella di aiuto ma questa volta con una sola riga che fungerà da tabella "di ancoraggio". Ancora meglio, questa tabella può essere utilizzata per qualsiasi numero di CHECKvincoli.

Aggiungiamo quindi una colonna calcolata che valuta in TRUE/ FALSE/ UNKNOWN, esattamente come CHECKfarebbe un vincolo, ma questa colonna ha un FOREIGN KEYvincolo alla nostra tabella di ancoraggio. Se la condizione / colonna viene valutata FALSEper alcune righe, le righe vengono rifiutate a causa dell'FK.

Se la condizione / colonna restituisce TRUEo UNKNOWN( NULL), le righe non vengono rifiutate, esattamente come dovrebbe accadere con i CHECKvincoli:

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 ;

L'esempio è per la versione di MySQL 5.7. In MariaDB (versioni 5.2+ fino alla 10.1), dobbiamo solo modificare la sintassi e dichiarare la colonna come PERSISTENTanziché STORED. Nella versione 10.2 è STOREDstata aggiunta anche la parola chiave, quindi l'esempio sopra funziona in entrambe le versioni (MySQL e MariaDB) per le ultime versioni.

Se vogliamo imporre molti CHECKvincoli (cosa comune in molti progetti), dobbiamo solo aggiungere una colonna calcolata e una chiave esterna per ognuno di essi. Abbiamo solo bisogno di una truthtabella nel database. Dovrebbe essere inserita una riga e quindi rimosso tutto l'accesso in scrittura.


Nell'ultima MariaDB, tuttavia, non dobbiamo più eseguire tutte queste acrobazie, poiché i CHECKvincoli sono stati implementati nella versione 10.2.1 (versione alfa: 2016-lug-04)!

L'attuale versione 10.2.2 è ancora una versione beta ma sembra che la funzionalità sarà disponibile nella prima versione stabile della serie MariaDB 10.2.


0

Come ho spiegato in questo articolo , a partire dalla versione 8.0.16, MySQL ha aggiunto il supporto per i vincoli CHECK personalizzati:

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

In precedenza, questo era disponibile solo utilizzando i trigger PRIMA INSERT e PRIMA AGGIORNAMENTO:

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;

Per maggiori dettagli sull'emulazione dei vincoli CHECK utilizzando i trigger di database per le versioni di MySQL precedenti alla 8.0.16, consulta questo articolo .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.