sqlpostgresqlconstraintscheck

Restricciones CHECK en SQL: invariantes en la base de datos

Usa CHECK para fijar reglas de negocio en el esquema: importes positivos, estados validos, rangos de fechas y validaciones multicolumna.

2 min de lecturaReferencesql · postgresql · constraints · check · data-integrity

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

-- Later, to change the rule:
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.

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

-- Validate existing rows without a long exclusive lock:
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.

Practica con ejercicios reales

Resuelve ejercicios en el entrenador de SQL con corrección instantánea y pistas.

Abrir el entrenador