Programação Por Equipe DevBras

Capa do artigo: Tipo da coluna no MySQL: data, dinheiro, documento e texto no tipo certo desde o começo

Tipo da coluna no MySQL: data, dinheiro, documento e texto no tipo certo desde o começo

Escolha o tipo de cada coluna no MySQL pelo que ela precisa comparar e calcular, evite data em texto, dinheiro em float e CPF em número, e migre uma coluna errada sem perder dado.

O sistema da academia lista os alunos por data de matrícula e a ordem sai assim: 05/03/2026, 12/01/2026, 20/11/2025. Exatamente ao contrário. A coluna matricula é VARCHAR, e o banco ordenou texto, comparando caractere por caractere: "0" vem antes de "1", que vem antes de "2". No mesmo cadastro, o CPF de quem começa com zero aparece com dez dígitos, e a soma das mensalidades do mês difere do extrato do banco por alguns centavos. A resposta curta: o tipo da coluna define o que o banco sabe comparar, calcular e validar. Data vai em DATE ou DATETIME, dinheiro em DECIMAL, contagem e chave em inteiro, e documento com zero à esquerda, como CPF e CEP, em texto de tamanho fixo.

Escolher certo na criação custa um minuto. Corrigir depois, com a tabela cheia e o sistema no ar, custa uma migração.

Por que o tipo errado "funciona" por meses

Uma coluna de texto aceita qualquer coisa, então nada falha no cadastro. O problema aparece quando você pede algo que depende do significado do valor. Ordenar datas, filtrar "matrículas do último mês", somar valores ou buscar por CPF são operações que o banco faz pelo tipo da coluna, não pelo que você imagina que está escrito nela.

Com data em texto no formato dia/mês/ano, a ordenação alfabética compara primeiro o dia. Um filtro como WHERE matricula >= '01/01/2026' devolve qualquer data cujo texto seja "maior", inclusive de anos anteriores. Com DATE, a comparação é cronológica, e funções como DATE_SUB e YEAR funcionam direto.

Com dinheiro em FLOAT ou DOUBLE, o valor guardado é uma aproximação binária. Valores como 0,10 não têm representação exata, e somas de muitas linhas acumulam diferenças que aparecem no centavo. O DECIMAL(10,2) guarda o número exato com duas casas. O guia de dinheiro em centavos aprofunda o lado da aplicação.

Com CPF em BIGINT, o zero à esquerda some, porque para um número 01234567890 e 1234567890 são o mesmo valor. CPF, CEP e telefone são identificadores feitos de dígitos, não quantidades: ninguém soma CPFs.

Como escolher o tipo de cada coluna

  1. Para cada coluna, pergunte o que o sistema faz com ela: compara por ordem, soma, busca por igualdade, só exibe.
  2. Pegue um exemplo real do maior valor que você espera e do formato mais estranho que já apareceu.
  3. Escolha o tipo pela operação, não pelo formato de exibição: data é DATE mesmo que a tela mostre dia/mês/ano.
  4. Defina o tamanho com folga honesta: nome de pessoa em VARCHAR(120), não em VARCHAR(20) nem em TEXT.
  5. Marque NOT NULL o que é obrigatório, e deixe nulo só o que pode de fato faltar.
  6. Confira se o modo estrito está ativo, para valor inválido virar erro em vez de ser cortado ou convertido em silêncio.

A tabela da academia, refeita:

CREATE TABLE alunos (
  id             INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
  nome           VARCHAR(120) NOT NULL,
  cpf            CHAR(11) NOT NULL,
  cep            CHAR(8) NULL,
  nascimento     DATE NULL,
  matricula      DATE NOT NULL,
  ultimo_acesso  DATETIME NULL,
  mensalidade    DECIMAL(8,2) NOT NULL,
  aulas_semana   TINYINT UNSIGNED NOT NULL DEFAULT 3,
  ativo          BOOLEAN NOT NULL DEFAULT TRUE,
  observacoes    TEXT NULL,
  UNIQUE KEY uk_alunos_cpf (cpf)
) DEFAULT CHARSET = utf8mb4;

Algumas decisões dessa tabela merecem explicação:

  • CHAR(11) para CPF guarda só os dígitos, sem pontos e traço, com tamanho fixo e zero à esquerda preservado. A máscara é coisa da tela.
  • DATE para matrícula e nascimento, porque não há hora envolvida. Para o último acesso, a hora importa, então DATETIME. Fuso horário é um assunto à parte, tratado em fuso horário no PHP.
  • DECIMAL(8,2) para mensalidade vai até 999.999,99, folga de sobra para uma academia.
  • TINYINT UNSIGNED para aulas por semana vai de 0 a 255. Quantidade pequena não precisa de INT.
  • BOOLEAN é sinônimo de TINYINT(1) no MySQL e guarda 0 ou 1, em vez de um VARCHAR com "S" e "N" que um dia ganha "sim" e "s".
  • TEXT só para observações, que podem ser longas e não são usadas em busca por índice.

O utf8mb4 garante que acentos e qualquer caractere caibam; o post sobre acentos que quebram no banco cobre o resto da cadeia. Lembre que, no MySQL, o número do VARCHAR conta caracteres, não bytes.

DATETIME ou TIMESTAMP

Os dois guardam data e hora, mas se comportam diferente. TIMESTAMP converte o valor do fuso da sessão para UTC ao gravar e de volta ao ler, e tem limite em 2038. DATETIME guarda o valor como você mandou e vai até o ano 9999. Para datas de negócio, como vencimento, e qualquer coisa que possa passar de 2038, prefira DATETIME e trate o fuso de forma explícita na aplicação.

Como migrar uma coluna de data que hoje é texto

A academia já tem 4.000 alunos com matricula em texto. A troca é feita em etapas, sem apagar a coluna antiga antes de conferir a nova, no espírito da migração em dois passos.

-- 1. Encontre os valores que não convertem
SELECT id, matricula
FROM alunos
WHERE STR_TO_DATE(matricula, '%d/%m/%Y') IS NULL;

-- 2. Corrija esses registros à mão, então crie a coluna nova
ALTER TABLE alunos ADD COLUMN matricula_data DATE NULL;

-- 3. Preencha a partir do texto
UPDATE alunos
SET matricula_data = STR_TO_DATE(matricula, '%d/%m/%Y');

-- 4. Confira: nenhum nulo e a ordem faz sentido
SELECT COUNT(*) FROM alunos WHERE matricula_data IS NULL;
SELECT matricula, matricula_data FROM alunos ORDER BY matricula_data LIMIT 10;

Com tudo conferido, a aplicação passa a gravar e ler a coluna nova. Só depois de um ciclo em produção, você torna matricula_data obrigatória e remove a antiga. Faça backup antes de cada ALTER, como no guia de backup de MySQL que restaura.

Erros comuns

  • Data guardada em VARCHAR. Ordenação e filtros por período devolvem resultados errados sem nenhum erro.
  • Dinheiro em FLOAT ou DOUBLE. Somas e relatórios diferem do extrato por centavos, e a conciliação vira caça ao arredondamento.
  • CPF, CEP ou telefone em coluna numérica. O zero à esquerda some, e o documento passa a ter um dígito a menos.
  • Comparar coluna de texto com número no WHERE. Em WHERE cpf = 12345678901, sem aspas, o banco converte a coluna em número para comparar e deixa de usar o índice.
  • TEXT ou VARCHAR(255) em tudo, por preguiça. O banco deixa de validar o tamanho, índices ficam maiores, e nomes de 3.000 caracteres entram sem resistência.
  • Modo estrito desligado. Valor longo demais é cortado e data inválida vira zero, em silêncio, e o problema só aparece meses depois.

Checklist

  • Nenhuma data ou hora está em coluna de texto.
  • Valores em dinheiro estão em DECIMAL ou em centavos num inteiro.
  • CPF, CEP e telefone estão em texto, só com dígitos.
  • Cada VARCHAR tem tamanho baseado num exemplo real.
  • Colunas obrigatórias estão como NOT NULL.
  • O modo SQL do servidor inclui STRICT_TRANS_TABLES, conferido com SELECT @@sql_mode;.
  • A tabela usa utf8mb4.

Rode um SHOW CREATE TABLE na tabela principal do seu sistema e leia coluna por coluna. Qual delas guarda número ou data dentro de texto, e qual relatório já está mostrando a ordem errada sem ninguém perceber?

Avalie este artigo

Referência: http://www.rcoli.com.br/2012/08/tipos-de-campos-no-mysql-saiba-como-escolher-o-tipo-correto/