Una restriccion CHECK es una condicion booleana que la base de datos evalua en cada insercion o actualizacion de fila. Si la expresion devuelve FALSE, la operacion falla. Asi fijas una regla de negocio en el esquema en lugar de dejarla en el codigo de la aplicacion, que cualquier otro cliente puede saltarse.
Por que guardar invariantes en la base
Tu aplicacion puede olvidar validar. En produccion conviven varios servicios, y las migraciones o los UPDATE manuales esquivan tu codigo por completo. Un CHECK garantiza que una fila incorrecta simplemente no se grabe, venga de donde venga la consulta.
CREATE TABLE orders (
id bigint PRIMARY KEY,
user_id bigint NOT NULL,
amount numeric(12,2) NOT NULL CHECK (amount > 0),
status text NOT NULL CHECK (status IN ('new', 'paid', 'shipped', 'cancelled')),
created_at timestamptz NOT NULL DEFAULT now()
);
Aqui hay dos reglas: el importe es estrictamente positivo y el estado debe pertenecer a un conjunto permitido. Insertar amount = 0 o status = 'refunded' lanza un error.
Restricciones con nombre
Un CHECK anonimo recibe un nombre autogenerado como orders_amount_check. Es mejor nombrarlo de forma explicita: el nombre aparece en los mensajes de error y te da un identificador limpio para DROP CONSTRAINT.
ALTER TABLE products
ADD CONSTRAINT price_positive CHECK (price > 0);
ALTER TABLE products DROP CONSTRAINT price_positive;
- El nombre debe ser unico dentro de la tabla.
- Un nombre descriptivo (
price_positive, salary_nonneg) se lee mucho mejor en los logs que uno autogenerado.
Comprobaciones multicolumna y rangos
Una expresion CHECK puede referirse a varias columnas de la misma fila, ideal para rangos y coherencia entre campos.
ALTER TABLE employees
ADD CONSTRAINT salary_band CHECK (salary BETWEEN 30000 AND 500000),
ADD CONSTRAINT not_self_manager CHECK (manager_id IS NULL OR manager_id <> id);
ALTER TABLE users
ADD CONSTRAINT created_not_future CHECK (created_at <= now());
Limite clave: un CHECK solo ve la fila actual. No puede leer otras filas ni otras tablas (para eso estan las claves foraneas o los triggers). En PostgreSQL la expresion ademas debe ser determinista; now() se admite tecnicamente, pero atar una comprobacion a "la hora actual" es fragil porque solo se evalua al escribir.
NULL y la logica trivaluada
El gran tropiezo. Si la expresion da NULL (ni TRUE ni FALSE), el CHECK considera la fila valida. Dicho de otro modo, un CHECK solo bloquea valores inequivocamente falsos.
ALTER TABLE users
ADD CONSTRAINT country_iso CHECK (country IN ('US', 'GB', 'DE', 'BR'));
INSERT INTO users (id, email, country) VALUES (1, 'a@x.io', NULL);
Para prohibir NULL, anade un NOT NULL aparte o incluye la prueba en la condicion:
ALTER TABLE users
ADD CONSTRAINT country_required
CHECK (country IS NOT NULL AND country IN ('US', 'GB', 'DE', 'BR'));
Anadir en tablas grandes: NOT VALID
ADD CONSTRAINT ... CHECK escanea toda la tabla mientras mantiene un bloqueo; con millones de filas eso es una parada larga. PostgreSQL permite anadir la restriccion como NOT VALID: surte efecto de inmediato para las filas nuevas y modificadas, pero las existentes no se comprueban. Despues las validas en un paso aparte, mas ligero.
ALTER TABLE orders
ADD CONSTRAINT amount_positive CHECK (amount > 0) NOT VALID;
ALTER TABLE orders VALIDATE CONSTRAINT amount_positive;
Diferencias entre motores:
- MySQL soporta CHECK desde la 8.0.16; las versiones anteriores aceptaban la sintaxis pero la ignoraban en silencio. Aqui no existe
NOT VALID.
- ClickHouse tiene
CONSTRAINT ... CHECK, pero se evalua en la insercion y esta optimizado para analitica de otra forma; no te apoyes en el como invariante estricto de OLTP.
En resumen: usa CHECK para invariantes simples y locales a la fila, nombralos siempre, ten presente la semantica de NULL y recurre a NOT VALID para desplegar con seguridad en produccion.
Una restriccion CHECK es una condicion booleana que la base de datos evalua en cada insercion o actualizacion de fila. Si la expresion devuelve FALSE, la operacion falla. Asi fijas una regla de negocio en el esquema en lugar de dejarla en el codigo de la aplicacion, que cualquier otro cliente puede saltarse.
Por que guardar invariantes en la base
Tu aplicacion puede olvidar validar. En produccion conviven varios servicios, y las migraciones o los
UPDATEmanuales esquivan tu codigo por completo. Un CHECK garantiza que una fila incorrecta simplemente no se grabe, venga de donde venga la consulta.CREATE TABLE orders ( id bigint PRIMARY KEY, user_id bigint NOT NULL, amount numeric(12,2) NOT NULL CHECK (amount > 0), status text NOT NULL CHECK (status IN ('new', 'paid', 'shipped', 'cancelled')), created_at timestamptz NOT NULL DEFAULT now() );Aqui hay dos reglas: el importe es estrictamente positivo y el estado debe pertenecer a un conjunto permitido. Insertar
amount = 0ostatus = 'refunded'lanza un error.Restricciones con nombre
Un CHECK anonimo recibe un nombre autogenerado como
orders_amount_check. Es mejor nombrarlo de forma explicita: el nombre aparece en los mensajes de error y te da un identificador limpio paraDROP CONSTRAINT.ALTER TABLE products ADD CONSTRAINT price_positive CHECK (price > 0); -- Later, to change the rule: ALTER TABLE products DROP CONSTRAINT price_positive;price_positive,salary_nonneg) se lee mucho mejor en los logs que uno autogenerado.Comprobaciones multicolumna y rangos
Una expresion CHECK puede referirse a varias columnas de la misma fila, ideal para rangos y coherencia entre campos.
ALTER TABLE employees ADD CONSTRAINT salary_band CHECK (salary BETWEEN 30000 AND 500000), ADD CONSTRAINT not_self_manager CHECK (manager_id IS NULL OR manager_id <> id); ALTER TABLE users ADD CONSTRAINT created_not_future CHECK (created_at <= now());Limite clave: un CHECK solo ve la fila actual. No puede leer otras filas ni otras tablas (para eso estan las claves foraneas o los triggers). En PostgreSQL la expresion ademas debe ser determinista;
now()se admite tecnicamente, pero atar una comprobacion a "la hora actual" es fragil porque solo se evalua al escribir.NULL y la logica trivaluada
El gran tropiezo. Si la expresion da NULL (ni TRUE ni FALSE), el CHECK considera la fila valida. Dicho de otro modo, un CHECK solo bloquea valores inequivocamente falsos.
-- country may be NULL, and that PASSES the check: ALTER TABLE users ADD CONSTRAINT country_iso CHECK (country IN ('US', 'GB', 'DE', 'BR')); INSERT INTO users (id, email, country) VALUES (1, 'a@x.io', NULL); -- OK!Para prohibir NULL, anade un
NOT NULLaparte o incluye la prueba en la condicion:ALTER TABLE users ADD CONSTRAINT country_required CHECK (country IS NOT NULL AND country IN ('US', 'GB', 'DE', 'BR'));Anadir en tablas grandes: NOT VALID
ADD CONSTRAINT ... CHECKescanea toda la tabla mientras mantiene un bloqueo; con millones de filas eso es una parada larga. PostgreSQL permite anadir la restriccion comoNOT VALID: surte efecto de inmediato para las filas nuevas y modificadas, pero las existentes no se comprueban. Despues las validas en un paso aparte, mas ligero.ALTER TABLE orders ADD CONSTRAINT amount_positive CHECK (amount > 0) NOT VALID; -- Validate existing rows without a long exclusive lock: ALTER TABLE orders VALIDATE CONSTRAINT amount_positive;Diferencias entre motores:
NOT VALID.CONSTRAINT ... CHECK, pero se evalua en la insercion y esta optimizado para analitica de otra forma; no te apoyes en el como invariante estricto de OLTP.En resumen: usa CHECK para invariantes simples y locales a la fila, nombralos siempre, ten presente la semantica de NULL y recurre a
NOT VALIDpara desplegar con seguridad en produccion.