CPF no Banco de Dados: CHAR, VARCHAR ou BIGINT?
A escolha do tipo de coluna para CPF afeta armazenamento, indexação e integridade dos dados. As três opções mais comuns são CHAR(11), VARCHAR(11) e BIGINT, cada uma com trade-offs diferentes.
Comparação rápida
| Critério | CHAR(11) | VARCHAR(11) | BIGINT |
|---|---|---|---|
| Armazenamento | 11 bytes fixos | 12 a 13 bytes (1 a 2 de overhead) | 8 bytes |
| Zeros à esquerda | Preserva | Preserva | Perde |
| Indexação | Excelente (tamanho fixo) | Boa | Excelente |
| Validação de formato | Sim (CHECK) | Sim (CHECK) | Parcial |
| Legibilidade em queries | Alta | Alta | Baixa (precisa LPAD) |
CHAR(11) (recomendado)
CREATE TABLE pessoas (
id SERIAL PRIMARY KEY,
cpf CHAR(11) NOT NULL,
CONSTRAINT chk_cpf_formato CHECK (cpf ~ '^\d{11}$')
);
CREATE UNIQUE INDEX idx_cpf ON pessoas (cpf);CHAR(11) armazena exatamente 11 bytes, sem overhead de comprimento variável. Como todo CPF tem exatamente 11 dígitos, o tamanho fixo é uma vantagem: o banco sabe a largura de cada valor antes de ler, o que beneficia scans sequenciais e índices B-tree.
O CHECK com regex ^\d{11}$ garante na camada do banco que apenas 11 dígitos numéricos sejam aceitos, uma segunda barreira além da validação na aplicação.
Zeros à esquerda: CPFs como 001.002.003-00 são armazenados como 00100200300, preservando os zeros corretamente.
VARCHAR(11) (alternativa aceitável)
CREATE TABLE pessoas (
id SERIAL PRIMARY KEY,
cpf VARCHAR(11) NOT NULL,
CONSTRAINT chk_cpf_formato CHECK (cpf ~ '^\d{11}$')
);A diferença prática para CHAR(11) é pequena. VARCHAR adiciona 1 a 2 bytes de overhead para armazenar o comprimento, totalizando 12 a 13 bytes por valor. Em tabelas com milhões de registros, isso soma poucos megabytes.
A principal desvantagem: VARCHAR permite valores de comprimento variável, o que pode esconder bugs na aplicação (ex: CPF com 10 dígitos passando despercebido). O CHECK mitiga isso, mas CHAR(11) comunica melhor a intenção.
BIGINT (não recomendado)
CREATE TABLE pessoas (
id SERIAL PRIMARY KEY,
cpf BIGINT NOT NULL
);BIGINT usa 8 bytes (menos que CHAR/VARCHAR) e a comparação numérica é marginalmente mais rápida. Porém:
Problema crítico: zeros à esquerda. O CPF 001.002.003-00 armazenado como BIGINT vira 100200300, um número de 9 dígitos. Para exibir corretamente, toda query precisa de LPAD:
SELECT LPAD(cpf::TEXT, 11, '0') AS cpf_formatado FROM pessoas;Isso é fácil de esquecer e gera bugs silenciosos: o CPF parece válido com 9 ou 10 dígitos, mas está incompleto. Todo lugar que lê o CPF (relatórios, exports, APIs) precisa lembrar de aplicar o padding.
Validação parcial: BIGINT aceita qualquer número de 0 a 9.223.372.036.854.775.807. Sem CHECK, permite valores absurdos que um CPF nunca teria.
Com ou sem máscara?
Armazene apenas os 11 dígitos, sem pontos e traço. Motivos:
- Economia de espaço: 11 chars vs 14 (com pontuação)
- Indexação menor: índice B-tree sobre 11 bytes vs 14
- Busca simples:
WHERE cpf = '52998224725'sem precisar normalizar - Formatação na exibição: a máscara
XXX.XXX.XXX-XXé aplicada na camada de apresentação
-- Armazenar: apenas dígitos
INSERT INTO pessoas (cpf) VALUES ('52998224725');
-- Formatar na query quando necessário
SELECT
SUBSTR(cpf, 1, 3) || '.' ||
SUBSTR(cpf, 4, 3) || '.' ||
SUBSTR(cpf, 7, 3) || '-' ||
SUBSTR(cpf, 10, 2) AS cpf_formatado
FROM pessoas;Performance de indexação
Para tabelas grandes (milhões de registros), a escolha do tipo afeta o tamanho do índice:
| Tipo | Tamanho por entrada no índice | Índice para 10M registros |
|---|---|---|
| CHAR(11) | ~20 bytes (11 + overhead B-tree) | ~200 MB |
| VARCHAR(11) | ~21 bytes (11 + 1 len + overhead) | ~210 MB |
| BIGINT | ~16 bytes (8 + overhead) | ~160 MB |
A diferença de ~40 MB para 10 milhões de registros é irrelevante na prática. Priorize a correção dos dados (CHAR preserva zeros) sobre a micro-otimização.
Migração de BIGINT para CHAR
Se o banco já usa BIGINT e você precisa migrar:
-- Adicionar nova coluna
ALTER TABLE pessoas ADD COLUMN cpf_char CHAR(11);
-- Converter com padding de zeros
UPDATE pessoas SET cpf_char = LPAD(cpf::TEXT, 11, '0');
-- Verificar
SELECT COUNT(*) FROM pessoas WHERE LENGTH(cpf_char) != 11;
-- Trocar
ALTER TABLE pessoas DROP COLUMN cpf;
ALTER TABLE pessoas RENAME COLUMN cpf_char TO cpf;
ALTER TABLE pessoas ALTER COLUMN cpf SET NOT NULL;
ALTER TABLE pessoas ADD CONSTRAINT chk_cpf_formato CHECK (cpf ~ '^\d{11}$');
CREATE UNIQUE INDEX idx_cpf ON pessoas (cpf);Para gerar CPFs de teste e popular o banco em ambiente de desenvolvimento, use o gerador de CPF.
Veja também: CPF com zeros à esquerda e como armazenar CPF com segurança.