Respuestas:
Lo que necesita son dos factores desencadenantes para detectar la condición de edad no válida
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 !!!
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 ...
Además de la buena solución de activación de @Rolando, hay otra solución alternativa para este problema en MySQL (hasta CHECK
que se implementen las restricciones).
Cómo emular algunas CHECK
restricciones 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_allowed
tabla, para evitar agregar o eliminar accidentalmente filas.
Este truco no funcionará con FLOAT
columnas de tipo de datos, desafortunadamente (demasiados valores entre 0.0
y 20.0
).
Cómo emular CHECK
restricciones 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 VIRTUAL
y GENERATED
respectivamente, eso puede ser persistente, es decir, almacenado en el tabla - los llaman PERSISTENT
y STORED
respectivamente - podemos usarlos para simplificar la solución anterior e incluso mejor, extenderla para emular / imponer CHECK
restricciones 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 CHECK
restricciones.
Luego agregamos una columna calculada que evalúa a TRUE
/ FALSE
/ UNKNOWN
, exactamente como lo CHECK
haría una restricción, pero esta columna tiene una FOREIGN KEY
restricción en nuestra tabla de anclaje. Si la condición / columna se evalúa FALSE
para algunas filas, las filas se rechazan, debido a la FK.
Si la condición / columna se evalúa como TRUE
o UNKNOWN
( NULL
), las filas no se rechazan, exactamente como debería suceder con CHECK
restricciones:
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 PERSISTENT
lugar de STORED
. En la versión 10.2 también STORED
se 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 CHECK
restricciones (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 truth
tabla 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 CHECK
restricciones 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.
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 .