SQL Tipos de Dados
Na aula CREATE TABLE, cada coluna ganhou um tipo: INTEGER, REAL, TEXT. O tipo diz que tipo de valor a coluna guarda: um número inteiro, um preço, um nome, uma data, um "sim ou não". Nesta aula você vai conhecer os tipos do SQL, com os nomes que eles têm no SQL padrão e em cada um dos bancos famosos, e vai ver por que escolher o tipo certo evita contas erradas, datas fora de ordem e CEPs que perdem o zero da frente.
- Para que serve o tipo de uma coluna: o que ele muda nas contas, na ordenação e na proteção dos dados.
- Os inteiros (
SMALLINT,INTEGER,BIGINT) e a divisão que "come" as casas decimais. - Os números com casas decimais: os exatos (
DECIMAL/NUMERIC) e os aproximados (REAL,DOUBLE PRECISION), e por que0.1 + 0.2não é0.3. - Os textos:
CHAR(n),VARCHAR(n)eTEXT. - As datas e horas (
DATE,TIME,TIMESTAMP), o verdadeiro/falso (BOOLEAN) e os binários. - Como cada banco chama cada tipo, e qual tipo escolher para cada dado da Loja Aurora.
Um aviso antes de começar: o SQLite, o banco que roda no editor do guia, é bem mais "relaxado" com os tipos do que os outros bancos. Ele aceita todos os nomes desta aula, mas trata vários deles do seu jeito. Nesta aula você vê o mapa geral; a próxima, Os Tipos no SQLite, mostra em detalhe o que o SQLite faz com cada um.
A ideia: os campos de um formulário
Pense na ficha de cadastro de cliente, em papel, que a Loja Aurora usava antes do computador:
Nome: ______________________________ Nascimento: __/__/____ CEP: [ ][ ][ ][ ][ ]-[ ][ ][ ] Quer receber ofertas? ( ) sim ( ) não Limite de crédito: R$ ________,__
Cada campo já diz o que cabe nele. Na data, três espaços: dia, mês, ano. No CEP, oito quadradinhos para algarismos. Nas ofertas, só duas respostas possíveis. No limite de crédito, reais e centavos. Ninguém escreve "abacaxi" no campo da data.
O tipo de uma coluna é esse "formato do campo". Ele serve para três coisas:
| Para quê | Exemplo |
|---|---|
| Proteger: recusar o valor que não cabe | Num banco rígido como o PostgreSQL, um texto numa coluna de número dá erro |
| Calcular e comparar certo | Como número, 9 vem antes de 10; como texto, '10' vem antes de '9' |
| Guardar com economia | Um inteiro pequeno ocupa menos espaço que um texto longo |
A segunda linha da tabela parece estranha? Veja com os seus olhos. O mesmo "10 é menor que 9?", uma vez com textos (entre aspas) e outra com números:
SELECT '10' < '9' AS como_texto,
10 < 9 AS como_numero;
Como texto, a resposta é 1 ("verdadeiro"): o banco compara letra por letra, como num dicionário, e o caractere 1 vem antes do 9. Como número, a resposta é 0 ("falso"), a resposta certa. O valor é o mesmo; quem muda o resultado é o tipo.
1. Números inteiros
Inteiro é número sem casas decimais: quantidade em estoque, número do pedido, idade. O SQL padrão tem três tamanhos:
| Tipo | Faixa (no PostgreSQL) | Espaço | Bom para |
|---|---|---|---|
SMALLINT | -32.768 a 32.767 | 2 bytes | Números pequenos: nota de 1 a 5, ano |
INTEGER (ou INT) | cerca de -2,1 bilhões a 2,1 bilhões | 4 bytes | O dia a dia: id, estoque, quantidade |
BIGINT | cerca de -9,2 a 9,2 quintilhões | 8 bytes | Números enormes: contadores de visitas de um site gigante |
Um número fora da faixa dá erro nesses bancos (a documentação do PostgreSQL diz: tentar guardar um valor fora da faixa resulta em erro). Na dúvida, use INTEGER: é o que a Loja Aurora usa em todos os id, no estoque e na quantidade.
A divisão de inteiros
Inteiro com inteiro dá inteiro, até na divisão. A Ana quer dividir 7 caixas entre 2 lojas:
SELECT 7 / 2 AS inteiros,
7 / 2.0 AS com_decimal,
7.0 / 2 AS tambem_com_decimal;
- 7 / 2
- Os dois são inteiros, então a conta é de inteiros: o resultado é
3, e o "meio" que sobrou é jogado fora (não é arredondado: é cortado). - 7 / 2.0
- Basta um dos dois ter casa decimal para a conta virar de número com decimais:
3.5. - 7.0 / 2
- Tanto faz qual dos dois:
3.5de novo.
Esse comportamento é o mesmo no PostgreSQL e no SQL Server (a documentação da Microsoft diz: inteiro dividido por inteiro dá um inteiro, com a parte decimal cortada). O MySQL é exceção: lá, 3/5 dá 0.60, e a divisão inteira tem um operador próprio, DIV. No SQLite, ao dividir duas colunas INTEGER (como estoque / quantidade), lembre-se de multiplicar uma delas por 1.0.
Experimente: a conta abaixo deveria dar a metade de 7, mas o resultado veio 3. Mude um dos números para que a resposta seja 3.5.
SELECT 7 / 2 AS metade;
SELECT 7 / 2.0 AS metade;
2. Números com casas decimais: exatos e aproximados
Preço, frete, salário, peso. Aqui o SQL tem duas famílias, e a diferença entre elas é importante:
| Família | Tipos | Como guarda | Bom para |
|---|---|---|---|
| Exatos | DECIMAL(p, s), NUMERIC(p, s) | Algarismo por algarismo, como você escreveria no papel | Dinheiro, e tudo que precisa bater no centavo |
| Aproximados (de "ponto flutuante") | REAL, DOUBLE PRECISION, FLOAT | Em binário, com uma aproximação muito boa, mas aproximação | Medidas científicas, coordenadas, médias: onde um erro na 15ª casa não importa |
DECIMAL(p, s): precisão e escala
Os dois números entre parênteses dizem o tamanho do número:
- DECIMAL
- O tipo exato.
NUMERICé outro nome para a mesma coisa (no PostgreSQL e no SQL Server, os dois são idênticos). - p
- A precisão: quantos algarismos o número tem no total, somando os de antes e os de depois da vírgula.
- s
- A escala: quantos desses algarismos ficam depois da vírgula.
O exemplo da documentação do PostgreSQL: o número 23.5141 tem precisão 6 (seis algarismos) e escala 4 (quatro depois do ponto). Para dinheiro, o comum é DECIMAL(10, 2): dez algarismos no total, dois de centavos, ou seja, até 99.999.999,99 reais. Nos bancos que levam o tipo a sério, um número com algarismos demais antes da vírgula dá erro, e casas a mais depois da vírgula são arredondadas.
No Brasil, escrevemos 1,15 com vírgula. No SQL, as casas decimais vão sempre com ponto: 1.15. A vírgula, no SQL, separa uma coisa da outra (colunas, valores). Escreva 1,15 num INSERT e o banco vai entender dois valores: 1 e 15.
O problema dos aproximados
Os tipos aproximados guardam o número em binário (zeros e uns), e alguns números que são "redondos" para nós, como 0,1, não têm uma escrita exata em binário, do mesmo jeito que 1/3 não tem uma escrita exata em decimal (0,3333…). O resultado é este:
SELECT 0.1 + 0.2 AS soma,
0.1 + 0.2 = 0.3 AS igual,
0.1 + 0.2 - 0.3 AS diferenca;
- soma
- Na tela aparece
0.3, porque o SQLite arredonda na hora de mostrar. - igual
- Mas a comparação com
0.3dá0, "falso": por dentro, a soma não é exatamente 0,3. - diferenca
- A sobra é minúscula:
5.55111512312578e-17, ou seja, 0,0000000000000000555… (oe-17quer dizer "vezes 10 elevado a -17"). Pequena, mas basta para umWHERE total = 0.3não achar nada.
Num relatório de vendas com milhares de linhas somadas, essas sobras se acumulam. É por isso que os bancos têm DECIMAL e que bancos e lojas o usam para dinheiro.
O SQLite aceita escrever DECIMAL(10, 2) numa coluna, mas não tem um tipo decimal exato: o valor é guardado como número aproximado do mesmo jeito. Veja: a coluna é DECIMAL(10,2) e a soma continua não batendo:
CREATE TABLE fretes (valor DECIMAL(10,2)); INSERT INTO fretes VALUES (0.1), (0.2); SELECT sum(valor) AS soma, sum(valor) = 0.3 AS igual FROM fretes;
Por isso a Loja Aurora usa REAL nos preços (é o que o SQLite faz de qualquer jeito) e arredonda os totais com round(…, 2) (aula Funções Numéricas). A outra saída, muito usada em sistemas de pagamento, é guardar os valores em centavos, numa coluna INTEGER: inteiros são sempre exatos.
CREATE TABLE fretes (valor_centavos INTEGER);
INSERT INTO fretes VALUES (10), (20);
SELECT sum(valor_centavos) AS centavos,
sum(valor_centavos) / 100.0 AS em_reais,
sum(valor_centavos) = 30 AS igual
FROM fretes;
3. Texto: CHAR, VARCHAR e TEXT
Nome, email, cidade, comentário. Três tipos, que diferem no tamanho:
| Tipo | O que é | Exemplo |
|---|---|---|
CHAR(n) | Texto de tamanho fixo: sempre n caracteres (os que faltam viram espaços) | estado CHAR(2): 'PI', 'CE' |
VARCHAR(n) | Texto de tamanho variável, com um máximo de n caracteres. Nome completo: CHARACTER VARYING | nome VARCHAR(100) |
TEXT | Texto de qualquer tamanho. Não é do SQL padrão, mas o PostgreSQL, o MySQL e o SQLite têm | comentario TEXT |
O n do VARCHAR(n) funciona como os quadradinhos do formulário: se o texto passar do limite, os bancos rígidos recusam. No PostgreSQL, o exemplo da documentação é este (ilustrativo, não roda no SQLite):
CREATE TABLE test2 (b varchar(5));
INSERT INTO test2 VALUES ('too long');
-- ERROR: value too long for type character varying(5)
"Valor longo demais para o tipo character varying(5)." O MySQL faz o mesmo no modo estrito (que faz parte do modo padrão do MySQL 8.4); fora do modo estrito, ele corta o texto e só dá um aviso.
Já o SQLite ignora o número entre parênteses. A Ana criou a coluna do estado com VARCHAR(2) e escreveu o nome inteiro, sem querer:
CREATE TABLE enderecos (uf VARCHAR(2));
INSERT INTO enderecos VALUES ('Piauí');
SELECT uf, length(uf) AS letras FROM enderecos;
Os cinco caracteres entraram sem reclamação. No SQLite, VARCHAR(2), VARCHAR(255) e TEXT dão no mesmo. Se você quer um limite de verdade no SQLite, ele se faz com a restrição CHECK (por exemplo, CHECK (length(uf) = 2)).
VARCHAR(255)?
Mesmo no SQLite, que ignora o número, vale escrever um tamanho que faça sentido (VARCHAR(100) para nome, CHAR(2) para UF): ele documenta a sua intenção e já deixa a tabela pronta se um dia ela for para o MySQL ou o PostgreSQL. O 255 é só um costume que passa de projeto em projeto; não precisa copiá-lo, escolha o tamanho pelo dado.
Número que não é número
CEP, telefone, CPF: são feitos de algarismos, mas não são números. Ninguém soma dois CEPs. E o pior: como número, o zero da frente desaparece. Veja o CEP 04567-000, de São Paulo, guardado numa coluna INTEGER:
CREATE TABLE enderecos (cliente_id INTEGER, cep INTEGER); INSERT INTO enderecos VALUES (5, '04567000'); SELECT * FROM enderecos;
Saiu 4567000: o zero sumiu, e o CEP agora tem sete algarismos. Com TEXT (ou CHAR(8)), ele fica intacto:
CREATE TABLE enderecos (cliente_id INTEGER, cep TEXT); INSERT INTO enderecos VALUES (5, '04567000'); SELECT * FROM enderecos;
A regra prática: se você não vai fazer conta com ele, é texto. Por isso o telefone dos fornecedores da Loja Aurora é TEXT: além do zero, ele tem parênteses, espaços, hífen e o +351 do fornecedor de Portugal.
4. Data e hora
Data do pedido, data de entrega, nascimento. O SQL padrão tem:
| Tipo | Guarda | Escrita |
|---|---|---|
DATE | Só a data | '2026-10-14' |
TIME | Só a hora | '14:30:00' |
TIMESTAMP | Data e hora juntas | '2026-10-14 14:30:00' |
Repare na ordem: ano-mês-dia (o formato ISO 8601), e não dia/mês/ano como escrevemos no Brasil. É o formato que todos os bancos entendem. Cada banco tem os seus nomes extras: o MySQL tem DATETIME; o SQL Server, datetime2; o PostgreSQL, timestamp with time zone (data e hora com fuso).
O SQLite, de novo, é diferente: ele não tem tipo de data. As datas ficam guardadas como texto, no formato ISO, e as funções de data (aula Funções de Data) sabem fazer conta com esse texto. Os pedidos da Loja Aurora são assim:
SELECT id, data, date(data, '+7 days') AS uma_semana_depois FROM pedidos ORDER BY data DESC LIMIT 3;
Por que o ISO e não o nosso formato? Porque o texto ISO se ordena como data: comparar '2026-10-02' com '2026-10-14' letra por letra dá a mesma resposta que comparar as datas. Com dia/mês/ano, não:
SELECT '2026-10-02' < '2026-10-14' AS iso,
'02/10/2026' < '14/09/2026' AS brasileiro;
As duas perguntas deram 1 ("verdadeiro"). A primeira está certa: 2 de outubro vem antes de 14 de outubro. A segunda está errada: 2 de outubro vem depois de 14 de setembro, mas como texto, '02…' vem antes de '14…'. Um ORDER BY numa coluna com datas brasileiras sairia todo embaralhado.
5. Verdadeiro ou falso: BOOLEAN
"É VIP?", "Foi entregue?", "Está descontinuado?". Perguntas de sim ou não. O tipo do SQL padrão para isso é o BOOLEAN ("booleano", em homenagem ao matemático George Boole), com os valores TRUE ("verdadeiro") e FALSE ("falso"). Mas cada banco faz de um jeito:
| Banco | Como guarda o sim/não |
|---|---|
| PostgreSQL | Tem o boolean de verdade, com três estados: verdadeiro, falso e "desconhecido" (NULL). Mostra os valores como t e f |
| MySQL | BOOLEAN é outro nome para TINYINT(1), um inteiro pequeno: 0 é falso, qualquer outro número é verdadeiro |
| SQL Server | Não tem BOOLEAN: usa o tipo bit, que guarda 0, 1 ou NULL |
| SQLite | Não tem um tipo próprio: guarda 0 e 1. TRUE e FALSE são só outro jeito de escrever 1 e 0 |
SELECT TRUE, FALSE;
É por isso que as colunas vip (em clientes) e descontinuado (em produtos) da Loja Aurora são INTEGER com 0 ou 1. E dá para usar TRUE na consulta, que o SQLite entende como 1:
SELECT nome, vip FROM clientes WHERE vip = TRUE;
6. Binários: arquivos dentro do banco
Por fim, há tipos para guardar bytes "crus": uma foto, um PDF, um arquivo qualquer. No SQLite e no MySQL é o BLOB (de binary large object, "objeto binário grande"); no PostgreSQL, bytea (de byte array, "sequência de bytes"); no SQL Server, varbinary. Na prática, quase sempre é melhor guardar o arquivo numa pasta e, no banco, só o caminho dele (um TEXT como 'fotos/produto-3.jpg').
O mapa: cada tipo em cada banco
Os nomes mais usados, lado a lado. Os da coluna "SQL padrão" estão na lista de tipos do padrão SQL (a documentação do PostgreSQL traz essa lista na seção de compatibilidade). A última coluna diz o que o SQLite faz com o nome; a próxima aula explica essa "afinidade".
| Dado | SQL padrão | PostgreSQL | MySQL | SQL Server | SQLite |
|---|---|---|---|---|---|
| Inteiro | SMALLINT, INTEGER, BIGINT | smallint, integer, bigint | SMALLINT, INT, BIGINT | smallint, int, bigint | INTEGER (aceita todos os nomes) |
| Decimal exato | DECIMAL(p,s), NUMERIC(p,s) | numeric, decimal | DECIMAL (até 65 algarismos) | decimal, numeric | aceita o nome, mas guarda aproximado |
| Decimal aproximado | REAL, DOUBLE PRECISION | real, double precision | FLOAT, DOUBLE | real, float | REAL |
| Texto com limite | CHAR(n), VARCHAR(n) | char(n), varchar(n) | CHAR(n), VARCHAR(n) | char(n), varchar(n) (n até 8.000 bytes) | TEXT (ignora o n) |
| Texto longo | — | text | TEXT | varchar(max) | TEXT |
| Data | DATE | date | DATE | date | texto 'AAAA-MM-DD' |
| Data e hora | TIMESTAMP | timestamp | DATETIME, TIMESTAMP | datetime2 | texto 'AAAA-MM-DD HH:MM:SS' |
| Sim/não | BOOLEAN | boolean | BOOLEAN (= TINYINT(1)) | bit | INTEGER 0/1 |
| Binário | — | bytea | BLOB | varbinary | BLOB |
No SQL Server existe um tipo chamado text, mas a documentação da Microsoft avisa que text, ntext e image serão removidos numa versão futura e pede para usar varchar(max), nvarchar(max) e varbinary(max). Um detalhe a mais do SQL Server: lá, o n do varchar(n) conta bytes, não letras; com acentos em UTF-8, cabem menos letras que o n.
Qual tipo usar para cada dado
A tabela abaixo é o "gabarito" para os dados da Loja Aurora. A coluna do meio vale para os bancos rígidos; a da direita é o que a Loja Aurora usa no SQLite.
| Dado | No MySQL, PostgreSQL, SQL Server | No SQLite (Loja Aurora) | Por quê |
|---|---|---|---|
id, estoque, quantidade | INTEGER | INTEGER | Contagens: sempre inteiras |
preco, frete, salario | DECIMAL(10,2) | REAL + round(), ou centavos em INTEGER | Dinheiro precisa bater no centavo |
nome, email, cidade | VARCHAR(n) | TEXT | Texto curto, com um tamanho razoável |
estado (UF) | CHAR(2) | TEXT | Sempre duas letras |
| CEP, telefone, CPF | VARCHAR(n) ou CHAR(n) | TEXT | Não se faz conta, e o zero da frente importa |
data, nascimento | DATE | TEXT no formato ISO | Datas no formato que ordena certo |
vip, descontinuado | BOOLEAN (ou bit) | INTEGER 0/1 | Sim ou não |
comentario | TEXT (ou varchar(max)) | TEXT | Texto sem tamanho previsível |
Quando dá errado
Escolher o tipo errado raramente dá erro na hora. O problema aparece depois, numa conta ou numa ordenação. Estes são os clássicos.
A Ana criou uma coluna de quantidades como TEXT e pediu para ordenar:
CREATE TABLE contagens (quantidade TEXT);
INSERT INTO contagens VALUES ('9'), ('10'), ('100');
SELECT quantidade FROM contagens ORDER BY quantidade;
O que acontece: 10, 100, 9. Ordem de dicionário: tudo que começa com "1" vem antes do "9". Nenhum erro, só um relatório errado. Como consertar: números que você vai comparar, somar ou ordenar vão numa coluna INTEGER ou REAL.
Você já viu: '04567000' numa coluna INTEGER vira 4567000. Como consertar: se não se faz conta com ele, é texto.
= em coluna aproximada
SELECT 0.1 + 0.2 = 0.3 AS igual,
round(0.1 + 0.2, 2) = 0.3 AS igual_arredondado;
O que acontece: sem arredondar, 0 (falso); arredondando para 2 casas, 1. Como consertar: nos bancos que têm, use DECIMAL para dinheiro. No SQLite, arredonde com round(…, 2) antes de comparar, ou guarde em centavos.
'02/10/2026' < '14/09/2026' deu verdadeiro, e está errado. Como consertar: guarde sempre 'AAAA-MM-DD'. Para mostrar no formato brasileiro, converta na consulta com strftime('%d/%m/%Y', data) (aula Funções de Data).
CREATE TABLE fretes (pedido_id INTEGER, valor REAL); INSERT INTO fretes VALUES (1, 12,50);
O que diz: table fretes has 2 columns but 3 values were supplied. O banco leu 12,50 como dois valores, 12 e 50, e agora são três valores para duas colunas. Como consertar: 12.50, com ponto.
O estoque médio por produto, com sum(estoque) / count(*), sai sem casas decimais, porque os dois lados são inteiros. Como consertar: sum(estoque) * 1.0 / count(*), ou use avg(estoque) (aula AVG()), que já devolve número com decimais.
Referência rápida
| Tipo | Guarda | Exemplo de valor |
|---|---|---|
SMALLINT / INTEGER / BIGINT | Inteiros (2, 4 e 8 bytes) | 430 |
DECIMAL(p,s) / NUMERIC(p,s) | Decimal exato: p algarismos, s depois do ponto | 1.15 |
REAL / DOUBLE PRECISION / FLOAT | Decimal aproximado | 3.5 |
CHAR(n) | Texto de tamanho fixo | 'PI' |
VARCHAR(n) | Texto de até n caracteres | 'Lápis Grafite HB' |
TEXT | Texto de qualquer tamanho (fora do padrão, mas comum) | 'Chegou rápido' |
DATE / TIME / TIMESTAMP | Data / hora / data e hora | '2026-10-14' |
BOOLEAN | Verdadeiro ou falso | TRUE, FALSE |
BLOB / bytea / varbinary | Bytes de um arquivo | x'CAFE' |
Exercícios
1. O tipo do preço
Num banco como o PostgreSQL, complete o tipo exato para um preço com até 8 algarismos, 2 deles de centavos:
2. A sigla do estado
Complete com o tipo de texto de tamanho fixo para a UF, que tem sempre 2 letras:
3. Qual é o resultado de SELECT 9 / 2;?
4. Qual tipo é o melhor para o telefone de um cliente?
5. Por que bancos e lojas preferem DECIMAL a REAL para guardar dinheiro?
6. Em qual banco BOOLEAN é só outro nome para TINYINT(1)?
Missão final: a tabela de entregas
Missão: a Ana criou a tabela das entregas com tudo TEXT. Troque o tipo de cada coluna pelo tipo do SQL padrão indicado aqui, escrito em maiúsculas e sem espaço dentro dos parênteses: transportadora é texto de até 60 letras (VARCHAR(60)); peso_kg tem até 6 algarismos, 3 deles depois do ponto (DECIMAL(6,3)); saiu_em é uma data (DATE); assinada é sim ou não (BOOLEAN). O PRAGMA no fim mostra os tipos que ficaram.
CREATE TABLE entregas (
id INTEGER,
pedido_id INTEGER,
transportadora TEXT,
peso_kg TEXT,
saiu_em TEXT,
assinada TEXT
);
INSERT INTO entregas VALUES (1, 86, 'Rápido Piauí', 1.25, '2026-10-02', FALSE);
PRAGMA table_info(entregas);
CREATE TABLE entregas (
id INTEGER,
pedido_id INTEGER,
transportadora VARCHAR(60),
peso_kg DECIMAL(6,3),
saiu_em DATE,
assinada BOOLEAN
);
INSERT INTO entregas VALUES (1, 86, 'Rápido Piauí', 1.25, '2026-10-02', FALSE);
PRAGMA table_info(entregas);
Depois de cumprir a missão, acrescente SELECT *, typeof(saiu_em), typeof(assinada) FROM entregas; no fim e rode. O SQLite aceitou os nomes DATE e BOOLEAN, mas o que ele guardou de verdade em cada coluna é assunto da próxima aula.
Resumo
- O tipo de uma coluna diz que valor ela guarda, e muda as contas, as comparações e a ordenação: como texto,
'10' < '9'. - Inteiros:
SMALLINT,INTEGER,BIGINT. Inteiro dividido por inteiro dá inteiro (7 / 2=3). - Decimais:
DECIMAL(p,s)/NUMERICsão exatos (dinheiro);REAL/DOUBLE PRECISIONsão aproximados (0.1 + 0.2 = 0.3é falso). O SQLite aceitaDECIMAL, mas guarda aproximado: useround()ou centavos. - Texto:
CHAR(n)fixo,VARCHAR(n)atén,TEXTlivre. O SQLite ignora on. CEP e telefone são texto. - Datas:
DATE,TIME,TIMESTAMP, sempre no formatoAAAA-MM-DD. No SQLite, são texto ISO. - Sim/não:
BOOLEANno padrão e no PostgreSQL,TINYINT(1)no MySQL,bitno SQL Server, 0/1 no SQLite.