Por que tipo numérico deixou de funcionar
| Tipo | Guarda letras? | Problema |
|---|---|---|
BIGINT / NUMBER / Long | Não | impossível armazenar o formato alfanumérico; já perdia zeros à esquerda antes disso |
INT | Não | estoura o limite: um CNPJ numérico não cabe em 32 bits |
VARCHAR(18) | Sim | só serve se guardar a máscara — mistura formatos e quebra índice único |
VARCHAR(14) | Sim | recomendado: guarda o valor limpo, aceita os dois formatos |
CHAR(14) | Sim | aceitável; cuidado com o preenchimento de espaços em alguns bancos |
Migração em três fases, sem parar o sistema
Alterar o tipo direto em produção trava a tabela e derruba tudo que estiver lendo. O caminho seguro:
- Expandir: criar a coluna nova em texto e passar a gravar nas duas (dual write).
- Migrar: preencher os registros antigos, recuperando os zeros à esquerda que o tipo numérico comeu.
- Contrair: mudar as leituras, criar o índice único na coluna nova e derrubar a antiga.
-- Fase 1: coluna nova
ALTER TABLE empresa ADD COLUMN cnpj_texto varchar(14);
-- Fase 2: backfill com LPAD para recuperar zeros à esquerda
UPDATE empresa
SET cnpj_texto = lpad(cnpj::text, 14, '0')
WHERE cnpj IS NOT NULL
AND cnpj_texto IS NULL;
-- Conferência: nada pode ter ficado fora de 14 caracteres
SELECT count(*) FROM empresa WHERE length(cnpj_texto) <> 14;
-- Fase 3: restrições e troca
ALTER TABLE empresa
ALTER COLUMN cnpj_texto SET NOT NULL,
ADD CONSTRAINT empresa_cnpj_formato
CHECK (cnpj_texto ~ '^[0-9A-Z]{12}[0-9]{2}$');
CREATE UNIQUE INDEX CONCURRENTLY empresa_cnpj_uk ON empresa (cnpj_texto);
ALTER TABLE empresa DROP COLUMN cnpj;
ALTER TABLE empresa RENAME COLUMN cnpj_texto TO cnpj;
-- MySQL 8
ALTER TABLE empresa ADD COLUMN cnpj_texto VARCHAR(14) NULL;
UPDATE empresa SET cnpj_texto = LPAD(CAST(cnpj AS CHAR), 14, '0') WHERE cnpj IS NOT NULL;
ALTER TABLE empresa MODIFY cnpj_texto VARCHAR(14) NOT NULL;
ALTER TABLE empresa ADD CONSTRAINT empresa_cnpj_formato
CHECK (cnpj_texto REGEXP '^[0-9A-Z]{12}[0-9]{2}$');
CREATE UNIQUE INDEX empresa_cnpj_uk ON empresa (cnpj_texto);
-- SQL Server
ALTER TABLE empresa ADD cnpj_texto varchar(14) NULL;
UPDATE empresa SET cnpj_texto = RIGHT('00000000000000' + CAST(cnpj AS varchar(14)), 14);
ALTER TABLE empresa ALTER COLUMN cnpj_texto varchar(14) NOT NULL;
ALTER TABLE empresa ADD CONSTRAINT empresa_cnpj_formato
CHECK (LEN(cnpj_texto) = 14 AND cnpj_texto NOT LIKE '%[^0-9A-Z]%'
AND SUBSTRING(cnpj_texto, 13, 2) NOT LIKE '%[^0-9]%');
-- Oracle
ALTER TABLE empresa ADD (cnpj_texto VARCHAR2(14));
UPDATE empresa SET cnpj_texto = LPAD(TO_CHAR(cnpj), 14, '0') WHERE cnpj IS NOT NULL;
ALTER TABLE empresa MODIFY (cnpj_texto VARCHAR2(14) NOT NULL);
ALTER TABLE empresa ADD CONSTRAINT empresa_cnpj_formato
CHECK (REGEXP_LIKE(cnpj_texto, '^[0-9A-Z]{12}[0-9]{2}$'));
Limpando dados sujos antes de criar a constraint
Bases antigas quase sempre têm CNPJ com máscara, com espaço, truncado ou com dígito verificador errado herdado de importação. Rode esta auditoria antes de aplicar qualquer restrição:
-- Normaliza para comparação, sem alterar os dados
WITH normalizado AS (
SELECT id,
cnpj AS original,
regexp_replace(upper(coalesce(cnpj, '')), '[^0-9A-Z]', '', 'g') AS limpo
FROM empresa
)
SELECT id,
original,
limpo,
CASE
WHEN limpo = '' THEN 'vazio'
WHEN length(limpo) <> 14 THEN 'tamanho ' || length(limpo)
WHEN limpo !~ '^[0-9A-Z]{12}[0-9]{2}$' THEN 'caractere inválido'
WHEN original <> limpo THEN 'contém máscara'
ELSE 'ok'
END AS problema
FROM normalizado
WHERE limpo = '' OR length(limpo) <> 14
OR limpo !~ '^[0-9A-Z]{12}[0-9]{2}$' OR original <> limpo
ORDER BY problema, id;
Depois de padronizar o formato, use a função cnpj_valido() da página de SQL para achar os registros com dígito verificador errado.
Decisões de modelagem que economizam dor
- Uma coluna, não duas. Não crie
cnpj_numericoecnpj_alfanumerico— os dois formatos convivem no mesmo campo de texto. - Índice único na coluna limpa. Se você precisar guardar o valor formatado por algum motivo legado, faça dele uma coluna gerada a partir da limpa, nunca o contrário.
- Normalize na escrita. Um trigger ou um hook da ORM que aplica
UPPERe remove pontuação elimina classes inteiras de bug. - Não use CNPJ como chave primária. Ele pode ser corrigido, e propagar essa correção por todas as tabelas filhas é caro. Use uma chave técnica e um índice único no CNPJ.
- Revise os layouts de integração. Arquivos posicionais (SPED, CNAB, EDI) e contratos de API costumam declarar o campo como numérico — é onde a migração costuma travar.