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
- Para cada coluna, pergunte o que o sistema faz com ela: compara por ordem, soma, busca por igualdade, só exibe.
- Pegue um exemplo real do maior valor que você espera e do formato mais estranho que já apareceu.
- Escolha o tipo pela operação, não pelo formato de exibição: data é
DATEmesmo que a tela mostre dia/mês/ano. - Defina o tamanho com folga honesta: nome de pessoa em
VARCHAR(120), não emVARCHAR(20)nem emTEXT. - Marque
NOT NULLo que é obrigatório, e deixe nulo só o que pode de fato faltar. - 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.DATEpara matrícula e nascimento, porque não há hora envolvida. Para o último acesso, a hora importa, entãoDATETIME. 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 UNSIGNEDpara aulas por semana vai de 0 a 255. Quantidade pequena não precisa deINT.BOOLEANé sinônimo deTINYINT(1)no MySQL e guarda 0 ou 1, em vez de umVARCHARcom "S" e "N" que um dia ganha "sim" e "s".TEXTsó 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
FLOATouDOUBLE. 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. EmWHERE cpf = 12345678901, sem aspas, o banco converte a coluna em número para comparar e deixa de usar o índice. TEXTouVARCHAR(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
DECIMALou em centavos num inteiro. - CPF, CEP e telefone estão em texto, só com dígitos.
- Cada
VARCHARtem tamanho baseado num exemplo real. - Colunas obrigatórias estão como
NOT NULL. - O modo SQL do servidor inclui
STRICT_TRANS_TABLES, conferido comSELECT @@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?
Referência: http://www.rcoli.com.br/2012/08/tipos-de-campos-no-mysql-saiba-como-escolher-o-tipo-correto/