A função
CREATE OR REPLACE FUNCTION valida_cpf(p_cpf TEXT) RETURNS BOOLEAN AS $$
DECLARE
v TEXT := regexp_replace(p_cpf, '\D', '', 'g');
s INT; d INT; k INT;
BEGIN
IF length(v) <> 11 OR v ~ '^(.)\1{10}$' THEN RETURN FALSE; END IF;
FOR k IN 9..10 LOOP
s := 0;
FOR i IN 1..k LOOP
s := s + (substr(v, i, 1))::INT * ((k + 2) - i);
END LOOP;
d := (s * 10 % 11) % 10;
IF d <> (substr(v, k + 1, 1))::INT THEN RETURN FALSE; END IF;
END LOOP;
RETURN TRUE;
END;
$$ LANGUAGE plpgsql IMMUTABLE;É o módulo 11 rodando dentro do banco: regexp_replace tira a pontuação, o teste ~ '^(.)\1{10}$' descarta sequências repetidas, e os dois laços conferem cada um dos dígitos verificadores. Por ser IMMUTABLE, dá para usar a função em índices e em CHECK constraints.
Uso típico:
SELECT valida_cpf('111.444.777-35'); -- true
SELECT valida_cpf('111.444.777-00'); -- false
-- como CHECK em uma tabela:
ALTER TABLE clientes ADD CONSTRAINT cpf_valido CHECK (valida_cpf(cpf));CHECK com esta função evita lixo no dado, mas mensagens de erro e UX ficam melhores validando antes de chegar ao banco.E o CNPJ em SQL?
Aqui a resposta honesta: não publicamos um validador de CNPJ em SQL — porque em SQL puro ele fica desajeitado, e fingir o contrário seria empurrar código ruim.
O CNPJ alfanumérico (vigente a partir de julho/2026) calcula o dígito verificador com a conversão valor = ASCII − 48, em que '0'→0 … 'Z'→42. Reproduzir isso em plpgsql exige percorrer as 12 posições da base aplicando ASCII() e um CASE por caractere para tratar as letras — muito código procedural, frágil e difícil de manter, para uma tarefa que o aplicativo resolve em poucas linhas.
O caminho limpo é validar o CNPJ na aplicação e deixar o banco apenas armazenar o número já limpo:
Continue
Perguntas frequentes
Dá para validar CNPJ em SQL puro?
ASCII() e tratar A–Z com um CASE por posição — muito código procedural para uma tarefa que o aplicativo resolve em poucas linhas. A recomendação é validar no app: em JavaScript ou em Python.Por que a função usa <code>FOR ... LOOP</code> em vez de só um SELECT?
generate_series, mas o ganho é mínimo e a leitura piora.