Validar no banco é útil como última linha de defesa (constraint) e para auditar bases legadas. Abaixo, a versão PostgreSQL completa e o equivalente em SQL Server.
0=0 … 9=9, A=17 … Z=42), os pesos vão de 2 a 9 da direita para a esquerda, soma-se tudo, divide-se por 11 e o dígito é 0 quando o resto é menor que 2, ou 11 menos o resto. Ver a conta detalhada.Código completo
-- PostgreSQL
CREATE OR REPLACE FUNCTION cnpj_limpar(valor text)
RETURNS text
LANGUAGE sql IMMUTABLE STRICT AS $$
SELECT regexp_replace(upper(valor), '[^0-9A-Z]', '', 'g');
$$;
CREATE OR REPLACE FUNCTION cnpj_digito(sequencia text)
RETURNS int
LANGUAGE plpgsql IMMUTABLE STRICT AS $$
DECLARE
tamanho int := length(sequencia);
soma int := 0;
i int;
peso int;
resto int;
BEGIN
FOR i IN 1..tamanho LOOP
peso := ((tamanho - i) % 8) + 2;
soma := soma + (ascii(substr(sequencia, i, 1)) - 48) * peso;
END LOOP;
resto := soma % 11;
IF resto < 2 THEN
RETURN 0;
END IF;
RETURN 11 - resto;
END;
$$;
CREATE OR REPLACE FUNCTION cnpj_valido(valor text)
RETURNS boolean
LANGUAGE plpgsql IMMUTABLE AS $$
DECLARE
cnpj text;
base text;
d1 int;
d2 int;
BEGIN
IF valor IS NULL THEN
RETURN false;
END IF;
cnpj := cnpj_limpar(valor);
IF cnpj !~ '^[0-9A-Z]{12}[0-9]{2}$' THEN
RETURN false;
END IF;
base := substr(cnpj, 1, 12);
d1 := cnpj_digito(base);
d2 := cnpj_digito(base || d1::text);
RETURN substr(cnpj, 13, 2) = d1::text || d2::text;
END;
$$;
-- Constraint na tabela
ALTER TABLE empresa
ALTER COLUMN cnpj TYPE varchar(14),
ADD CONSTRAINT empresa_cnpj_valido CHECK (cnpj_valido(cnpj));
-- Auditoria: quais registros da base atual reprovariam?
SELECT id, cnpj FROM empresa WHERE NOT cnpj_valido(cnpj);
Testes e integração com o framework
-- SQL Server
CREATE OR ALTER FUNCTION dbo.CnpjDigito (@Sequencia varchar(13))
RETURNS int
AS
BEGIN
DECLARE @Tamanho int = LEN(@Sequencia);
DECLARE @Soma int = 0, @i int = 1, @Peso int, @Resto int;
WHILE @i <= @Tamanho
BEGIN
SET @Peso = ((@Tamanho - @i) % 8) + 2;
SET @Soma = @Soma + (ASCII(SUBSTRING(@Sequencia, @i, 1)) - 48) * @Peso;
SET @i = @i + 1;
END
SET @Resto = @Soma % 11;
RETURN CASE WHEN @Resto < 2 THEN 0 ELSE 11 - @Resto END;
END;
GO
CREATE OR ALTER FUNCTION dbo.CnpjValido (@Valor varchar(30))
RETURNS bit
AS
BEGIN
DECLARE @Cnpj varchar(30) = UPPER(REPLACE(REPLACE(REPLACE(REPLACE(
ISNULL(@Valor, ''), '.', ''), '-', ''), '/', ''), ' ', ''));
IF LEN(@Cnpj) <> 14 RETURN 0;
IF @Cnpj LIKE '%[^0-9A-Z]%' RETURN 0;
IF SUBSTRING(@Cnpj, 13, 2) LIKE '%[^0-9]%' RETURN 0;
DECLARE @Base varchar(12) = LEFT(@Cnpj, 12);
DECLARE @D1 int = dbo.CnpjDigito(@Base);
DECLARE @D2 int = dbo.CnpjDigito(@Base + CAST(@D1 AS varchar(1)));
RETURN CASE
WHEN SUBSTRING(@Cnpj, 13, 2) = CAST(@D1 AS varchar(1)) + CAST(@D2 AS varchar(1))
THEN 1 ELSE 0
END;
END;
GO
Detalhes que costumam quebrar em SQL
- Em MySQL 8, troque
ascii()porASCII()e declare a função comoDETERMINISTIC; em Oracle, useASCII(SUBSTR(...))dentro de umaFUNCTIONPL/SQL com o mesmo laço. - Valide também na aplicação. A constraint no banco protege contra carga direta, mas a mensagem de erro que chega ao usuário é muito melhor vinda da camada de aplicação.
- Antes de criar a constraint, rode a consulta de auditoria: bases antigas quase sempre têm CNPJ com dígito errado herdado de importação.
Casos de teste recomendados
Cubra pelo menos estes cenários — eles pegam praticamente todos os erros de implementação:
| Entrada | Esperado | O que testa |
|---|---|---|
12.ABC.345/01DE-35 | válido | exemplo oficial, com máscara |
12abc34501de35 | válido | normalização para maiúsculas |
11.222.333/0001-81 | válido | compatibilidade com o formato numérico |
11.222.333/0001-82 | inválido | dígito verificador errado |
12ABC34501DEAB | inválido | letra no dígito verificador |
12ABC34501DE3 | inválido | tamanho incorreto |
00.000.000/0000-00 | inválido | sequência repetida |
"" / null | inválido | entrada vazia sem exceção |
Precisa de mais massa de teste? O gerador produz até 500 CNPJs alfanuméricos válidos de uma vez, com opção de baixar em .txt.