Pular para o conteúdo
ValidadorCNPJ Gerar CNPJ

Modelagem de dados

Campo CNPJ no banco: varchar ou bigint?

Resposta curta: VARCHAR(14), sem máscara. Abaixo, o porquê e o script de migração para PostgreSQL, MySQL, SQL Server e Oracle — inclusive recuperando zeros à esquerda perdidos.

Por que tipo numérico deixou de funcionar

Comparação de tipos para a coluna de CNPJ
TipoGuarda letras?Problema
BIGINT / NUMBER / LongNãoimpossível armazenar o formato alfanumérico; já perdia zeros à esquerda antes disso
INTNãoestoura o limite: um CNPJ numérico não cabe em 32 bits
VARCHAR(18)Simsó serve se guardar a máscara — mistura formatos e quebra índice único
VARCHAR(14)Simrecomendado: guarda o valor limpo, aceita os dois formatos
CHAR(14)Simaceitável; cuidado com o preenchimento de espaços em alguns bancos
CNPJ é um identificador, não uma quantidade. Nada nele é somado, multiplicado ou ordenado numericamente. Isso já bastava para escolher texto; o formato alfanumérico só tornou a escolha obrigatória.

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:

  1. Expandir: criar a coluna nova em texto e passar a gravar nas duas (dual write).
  2. Migrar: preencher os registros antigos, recuperando os zeros à esquerda que o tipo numérico comeu.
  3. Contrair: mudar as leituras, criar o índice único na coluna nova e derrubar a antiga.
postgresql
-- 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, sql server e oracle
-- 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:

auditoria (postgresql)
-- 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

Continue por aqui

Dúvidas sobre a coluna de CNPJ

CNPJ no banco de dados deve ser varchar ou bigint?

VARCHAR(14), sem exceção. Com o formato alfanumérico, tipos numéricos deixam de conseguir armazenar o valor. Mesmo antes da mudança, BIGINT já era problemático por comer os zeros à esquerda e permitir operações aritméticas que não fazem sentido em um identificador.

Devo guardar o CNPJ com ou sem máscara?

Sem máscara, 14 caracteres. Guardar formatado desperdiça espaço, quebra índices únicos quando um registro entra com pontuação e outro sem, e obriga a limpar a string em toda comparação. Formate apenas na exibição.

Qual o tamanho correto da coluna?

VARCHAR(14) para o valor limpo. Use CHAR(14) apenas se o banco tratar bem o preenchimento fixo e você tiver certeza de que nunca haverá valor parcial. Evite VARCHAR(18), que só faz sentido se você guardar a máscara — o que não é recomendado.

Como migrar sem parar o sistema?

Em três fases: (1) adicione a nova coluna de texto e passe a escrever nas duas; (2) faça o backfill dos registros antigos com LPAD para recuperar zeros à esquerda; (3) depois de validar, troque as leituras, remova a coluna antiga e crie o índice único definitivo. O passo a passo em SQL está abaixo.

Preciso de collation especial?

Use uma collation case sensitive ou normalize tudo para maiúsculas na aplicação. Se a coluna for case insensitive e você guardar valores em caixas diferentes, o índice único vai tratar 12abc… e 12ABC… como iguais — o que até ajuda, mas mascara inconsistência de dados.