comprobar restricción no funciona?

23

Tengo la siguiente tabla.

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

El problema es que la CHECKrestricción no funciona en la columna de edad. Por ejemplo, cuando inserto 222 para el campo de edad, MySQL lo acepta.

ALH
fuente

Respuestas:

16

Lo que necesita son dos factores desencadenantes para detectar la condición de edad no válida

  • ANTES DE INSERTAR
  • ANTES DE ACTUALIZAR

Lo siguiente se basa en un método de captura de errores manipulado por Jerry para Disparadores MySQL del Capítulo 11, Páginas 254-256 del libro Programación de Procedimiento Almacenado MySQL bajo el subtítulo 'Validación de Datos con Disparadores' :

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;

Aquí está el resultado:

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>

Tenga en cuenta también que los valores de incremento automático no se desperdician ni se pierden.

Darle una oportunidad !!!

RolandoMySQLDBA
fuente
19

Las restricciones CHECK no se implementan en MySQL. De CREAR TABLA

La cláusula CHECK es analizada pero ignorada por todos los motores de almacenamiento. Consulte la Sección 12.1.17, "Sintaxis CREAR TABLA". La razón para aceptar pero ignorar las cláusulas de sintaxis es la compatibilidad, para facilitar la transferencia de código desde otros servidores SQL y para ejecutar aplicaciones que crean tablas con referencias. Consulte la Sección 1.8.5, “Diferencias de MySQL con respecto al SQL estándar”.

También ha sido un error reportado durante casi 8 años ...

gbn
fuente
13

Además de la buena solución de activación de @Rolando, hay otra solución alternativa para este problema en MySQL (hasta CHECKque se implementen las restricciones).

Cómo emular algunas CHECKrestricciones en MySQL

Por lo tanto, si prefiere restricciones de integridad referencial y desea evitar los desencadenantes (debido a los problemas en MySQL cuando tiene ambos en sus tablas), puede usar otra pequeña tabla de referencia:

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

Llénalo con 20 filas:

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

Entonces tu mesa sería:

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 ;

Tendrá que eliminar el acceso de escritura a la age_allowedtabla, para evitar agregar o eliminar accidentalmente filas.

Este truco no funcionará con FLOATcolumnas de tipo de datos, desafortunadamente (demasiados valores entre 0.0y 20.0).


Cómo emular CHECKrestricciones arbitrarias en MySQL (5.7) y MariaDB (desde 5.2 hasta 10.1)

Dado que MariaDB agregó columnas calculadas en su versión 5.2 ( versión GA: 2010-11-10 ) y MySQL en 5.7 (versión GA: 2015-10-21 ), que las llaman VIRTUALy GENERATEDrespectivamente, eso puede ser persistente, es decir, almacenado en el tabla - los llaman PERSISTENTy STOREDrespectivamente - podemos usarlos para simplificar la solución anterior e incluso mejor, extenderla para emular / imponer CHECKrestricciones arbitrarias ):

Como se indicó anteriormente, necesitaremos una tabla de ayuda, pero esta vez con una sola fila que actuará como una tabla "ancla". Aún mejor, esta tabla se puede usar para cualquier cantidad de CHECKrestricciones.

Luego agregamos una columna calculada que evalúa a TRUE/ FALSE/ UNKNOWN, exactamente como lo CHECKharía una restricción, pero esta columna tiene una FOREIGN KEYrestricción en nuestra tabla de anclaje. Si la condición / columna se evalúa FALSEpara algunas filas, las filas se rechazan, debido a la FK.

Si la condición / columna se evalúa como TRUEo UNKNOWN( NULL), las filas no se rechazan, exactamente como debería suceder con CHECKrestricciones:

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 ;

El ejemplo es para la versión MySQL 5.7. En MariaDB (versiones 5.2+ hasta 10.1), solo necesitamos modificar la sintaxis y declarar la columna como en PERSISTENTlugar de STORED. En la versión 10.2 también STOREDse agregó la palabra clave, por lo que el ejemplo anterior funciona en ambos tipos (MySQL y MariaDB) para las últimas versiones.

Si queremos imponer muchas CHECKrestricciones (lo cual es común en muchos diseños), solo tenemos que agregar una columna calculada y una clave foránea para cada una de ellas. Solo necesitamos una truthtabla en la base de datos. Debe tener una fila insertada y luego todo acceso de escritura eliminado.


Sin embargo, en el último MariaDB, ya no tenemos que realizar todas estas acrobacias, ya que las CHECKrestricciones se han implementado en la versión 10.2.1 (versión alfa: 2016-Jul-04).

La versión actual 10.2.2 sigue siendo una versión beta, pero parece que la característica estará disponible en la primera versión estable de la serie MariaDB 10.2.

ypercubeᵀᴹ
fuente
0

Como expliqué en este artículo , comenzando con la versión 8.0.16, MySQL ha agregado soporte para restricciones CHECK personalizadas:

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

Anteriormente, esto solo estaba disponible usando los disparadores ANTES DE INSERTAR y ANTES DE ACTUALIZAR:

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;

Para obtener más detalles sobre la emulación de restricciones CHECK utilizando activadores de bases de datos para versiones de MySQL anteriores a 8.0.16, consulte este artículo .

Vlad Mihalcea
fuente