Uma restricao CHECK e uma condicao booleana que o banco avalia a cada insercao ou atualizacao de linha. Se a expressao retornar FALSE, a operacao falha. Assim voce fixa uma regra de negocio no esquema em vez de deixa-la no codigo da aplicacao, que qualquer outro cliente pode contornar.
Por que manter invariantes no banco
Sua aplicacao pode esquecer de validar. Em producao convivem varios servicos, e migracoes ou UPDATE manuais passam totalmente por fora do seu codigo. Um CHECK garante que uma linha invalida simplesmente nao seja gravada, venha de onde vier a 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()
);
Sao duas regras: o valor e estritamente positivo e o status precisa estar em um conjunto permitido. Inserir amount = 0 ou status = 'refunded' gera um erro.
Restricoes nomeadas
Um CHECK anonimo recebe um nome gerado automaticamente, como orders_amount_check. Prefira nomear de forma explicita: o nome aparece nas mensagens de erro e da um identificador limpo para o DROP CONSTRAINT.
ALTER TABLE products
ADD CONSTRAINT price_positive CHECK (price > 0);
ALTER TABLE products DROP CONSTRAINT price_positive;
- O nome precisa ser unico dentro da tabela.
- Um nome descritivo (
price_positive, salary_nonneg) e bem mais legivel nos logs do que um gerado automaticamente.
Verificacoes multicoluna e intervalos
Uma expressao CHECK pode referenciar varias colunas da mesma linha, ideal para intervalos e coerencia 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 importante: um CHECK so enxerga a linha atual. Ele nao pode ler outras linhas nem outras tabelas (para isso use chaves estrangeiras ou triggers). No PostgreSQL a expressao tambem deve ser deterministica; now() e tecnicamente aceito, mas amarrar uma verificacao ao "horario atual" e fragil, porque ela so e avaliada na hora da gravacao.
NULL e a logica de tres valores
A grande pegadinha. Se a expressao resultar em NULL (nem TRUE nem FALSE), o CHECK considera a linha valida. Ou seja, um CHECK so bloqueia 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 proibir NULL, adicione um NOT NULL separado ou inclua o teste na condicao:
ALTER TABLE users
ADD CONSTRAINT country_required
CHECK (country IS NOT NULL AND country IN ('US', 'GB', 'DE', 'BR'));
Adicionando em tabelas grandes: NOT VALID
ADD CONSTRAINT ... CHECK varre a tabela inteira enquanto mantem um bloqueio; com milhoes de linhas isso e uma parada longa. O PostgreSQL permite adicionar a restricao como NOT VALID: ela passa a valer de imediato para linhas novas e alteradas, mas as existentes nao sao verificadas. Depois voce as valida em um passo separado e mais leve.
ALTER TABLE orders
ADD CONSTRAINT amount_positive CHECK (amount > 0) NOT VALID;
ALTER TABLE orders VALIDATE CONSTRAINT amount_positive;
Diferencas entre engines:
- MySQL suporta CHECK a partir da 8.0.16; versoes anteriores aceitavam a sintaxe mas a ignoravam em silencio. Aqui nao existe
NOT VALID.
- ClickHouse tem
CONSTRAINT ... CHECK, mas e avaliado na insercao e otimizado para analitica de outra forma; nao conte com ele como invariante estrito de OLTP.
Resumo: use CHECK para invariantes simples e locais a linha, nomeie sempre, lembre da semantica de NULL e recorra ao NOT VALID para subir com seguranca em producao.
Uma restricao CHECK e uma condicao booleana que o banco avalia a cada insercao ou atualizacao de linha. Se a expressao retornar FALSE, a operacao falha. Assim voce fixa uma regra de negocio no esquema em vez de deixa-la no codigo da aplicacao, que qualquer outro cliente pode contornar.
Por que manter invariantes no banco
Sua aplicacao pode esquecer de validar. Em producao convivem varios servicos, e migracoes ou
UPDATEmanuais passam totalmente por fora do seu codigo. Um CHECK garante que uma linha invalida simplesmente nao seja gravada, venha de onde vier a 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() );Sao duas regras: o valor e estritamente positivo e o status precisa estar em um conjunto permitido. Inserir
amount = 0oustatus = 'refunded'gera um erro.Restricoes nomeadas
Um CHECK anonimo recebe um nome gerado automaticamente, como
orders_amount_check. Prefira nomear de forma explicita: o nome aparece nas mensagens de erro e da um identificador limpo para oDROP 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) e bem mais legivel nos logs do que um gerado automaticamente.Verificacoes multicoluna e intervalos
Uma expressao CHECK pode referenciar varias colunas da mesma linha, ideal para intervalos e coerencia 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 importante: um CHECK so enxerga a linha atual. Ele nao pode ler outras linhas nem outras tabelas (para isso use chaves estrangeiras ou triggers). No PostgreSQL a expressao tambem deve ser deterministica;
now()e tecnicamente aceito, mas amarrar uma verificacao ao "horario atual" e fragil, porque ela so e avaliada na hora da gravacao.NULL e a logica de tres valores
A grande pegadinha. Se a expressao resultar em NULL (nem TRUE nem FALSE), o CHECK considera a linha valida. Ou seja, um CHECK so bloqueia 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 proibir NULL, adicione um
NOT NULLseparado ou inclua o teste na condicao:ALTER TABLE users ADD CONSTRAINT country_required CHECK (country IS NOT NULL AND country IN ('US', 'GB', 'DE', 'BR'));Adicionando em tabelas grandes: NOT VALID
ADD CONSTRAINT ... CHECKvarre a tabela inteira enquanto mantem um bloqueio; com milhoes de linhas isso e uma parada longa. O PostgreSQL permite adicionar a restricao comoNOT VALID: ela passa a valer de imediato para linhas novas e alteradas, mas as existentes nao sao verificadas. Depois voce as valida em um passo separado e mais leve.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;Diferencas entre engines:
NOT VALID.CONSTRAINT ... CHECK, mas e avaliado na insercao e otimizado para analitica de outra forma; nao conte com ele como invariante estrito de OLTP.Resumo: use CHECK para invariantes simples e locais a linha, nomeie sempre, lembre da semantica de NULL e recorra ao
NOT VALIDpara subir com seguranca em producao.