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.

Você vai aprender
  • 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 que 0.1 + 0.2 não é 0.3.
  • Os textos: CHAR(n), VARCHAR(n) e TEXT.
  • 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 cabeNum banco rígido como o PostgreSQL, um texto numa coluna de número dá erro
Calcular e comparar certoComo número, 9 vem antes de 10; como texto, '10' vem antes de '9'
Guardar com economiaUm 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:

TipoFaixa (no PostgreSQL)EspaçoBom para
SMALLINT-32.768 a 32.7672 bytesNúmeros pequenos: nota de 1 a 5, ano
INTEGER (ou INT)cerca de -2,1 bilhões a 2,1 bilhões4 bytesO dia a dia: id, estoque, quantidade
BIGINTcerca de -9,2 a 9,2 quintilhões8 bytesNú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.5 de 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íliaTiposComo guardaBom para
ExatosDECIMAL(p, s), NUMERIC(p, s)Algarismo por algarismo, como você escreveria no papelDinheiro, e tudo que precisa bater no centavo
Aproximados (de "ponto flutuante")REAL, DOUBLE PRECISION, FLOATEm binário, com uma aproximação muito boa, mas aproximaçãoMedidas 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.

Vírgula ou ponto?

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.3 dá 0, "falso": por dentro, a soma não é exatamente 0,3.
diferenca
A sobra é minúscula: 5.55111512312578e-17, ou seja, 0,0000000000000000555… (o e-17 quer dizer "vezes 10 elevado a -17"). Pequena, mas basta para um WHERE total = 0.3 nã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.

E no SQLite?

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:

TipoO 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 VARYINGnome VARCHAR(100)
TEXTTexto de qualquer tamanho. Não é do SQL padrão, mas o PostgreSQL, o MySQL e o SQLite têmcomentario 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)).

Por que tanta gente escreve 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:

TipoGuardaEscrita
DATESó a data'2026-10-14'
TIMESó a hora'14:30:00'
TIMESTAMPData 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:

BancoComo guarda o sim/não
PostgreSQLTem o boolean de verdade, com três estados: verdadeiro, falso e "desconhecido" (NULL). Mostra os valores como t e f
MySQLBOOLEAN é outro nome para TINYINT(1), um inteiro pequeno: 0 é falso, qualquer outro número é verdadeiro
SQL ServerNão tem BOOLEAN: usa o tipo bit, que guarda 0, 1 ou NULL
SQLiteNã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".

DadoSQL padrãoPostgreSQLMySQLSQL ServerSQLite
InteiroSMALLINT, INTEGER, BIGINTsmallint, integer, bigintSMALLINT, INT, BIGINTsmallint, int, bigintINTEGER (aceita todos os nomes)
Decimal exatoDECIMAL(p,s), NUMERIC(p,s)numeric, decimalDECIMAL (até 65 algarismos)decimal, numericaceita o nome, mas guarda aproximado
Decimal aproximadoREAL, DOUBLE PRECISIONreal, double precisionFLOAT, DOUBLEreal, floatREAL
Texto com limiteCHAR(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—textTEXTvarchar(max)TEXT
DataDATEdateDATEdatetexto 'AAAA-MM-DD'
Data e horaTIMESTAMPtimestampDATETIME, TIMESTAMPdatetime2texto 'AAAA-MM-DD HH:MM:SS'
Sim/nãoBOOLEANbooleanBOOLEAN (= TINYINT(1))bitINTEGER 0/1
Binário—byteaBLOBvarbinaryBLOB
Nos outros bancos: os tipos que vão sumir

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.

DadoNo MySQL, PostgreSQL, SQL ServerNo SQLite (Loja Aurora)Por quê
id, estoque, quantidadeINTEGERINTEGERContagens: sempre inteiras
preco, frete, salarioDECIMAL(10,2)REAL + round(), ou centavos em INTEGERDinheiro precisa bater no centavo
nome, email, cidadeVARCHAR(n)TEXTTexto curto, com um tamanho razoável
estado (UF)CHAR(2)TEXTSempre duas letras
CEP, telefone, CPFVARCHAR(n) ou CHAR(n)TEXTNão se faz conta, e o zero da frente importa
data, nascimentoDATETEXT no formato ISODatas no formato que ordena certo
vip, descontinuadoBOOLEAN (ou bit)INTEGER 0/1Sim ou não
comentarioTEXT (ou varchar(max))TEXTTexto 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.

Números guardados como texto

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.

CEP (ou telefone) como número

Você já viu: '04567000' numa coluna INTEGER vira 4567000. Como consertar: se não se faz conta com ele, é texto.

Comparar dinheiro com = 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.

Datas no formato brasileiro

'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).

Vírgula no lugar do ponto
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.

Divisão de inteiros

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

TipoGuardaExemplo de valor
SMALLINT / INTEGER / BIGINTInteiros (2, 4 e 8 bytes)430
DECIMAL(p,s) / NUMERIC(p,s)Decimal exato: p algarismos, s depois do ponto1.15
REAL / DOUBLE PRECISION / FLOATDecimal aproximado3.5
CHAR(n)Texto de tamanho fixo'PI'
VARCHAR(n)Texto de até n caracteres'Lápis Grafite HB'
TEXTTexto de qualquer tamanho (fora do padrão, mas comum)'Chegou rápido'
DATE / TIME / TIMESTAMPData / hora / data e hora'2026-10-14'
BOOLEANVerdadeiro ou falsoTRUE, FALSE
BLOB / bytea / varbinaryBytes de um arquivox'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:

preco (, )

2. A sigla do estado

Complete com o tipo de texto de tamanho fixo para a UF, que tem sempre 2 letras:

estado

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)/NUMERIC são exatos (dinheiro); REAL/DOUBLE PRECISION são aproximados (0.1 + 0.2 = 0.3 é falso). O SQLite aceita DECIMAL, mas guarda aproximado: use round() ou centavos.
  • Texto: CHAR(n) fixo, VARCHAR(n) até n, TEXT livre. O SQLite ignora o n. CEP e telefone são texto.
  • Datas: DATE, TIME, TIMESTAMP, sempre no formato AAAA-MM-DD. No SQLite, são texto ISO.
  • Sim/não: BOOLEAN no padrão e no PostgreSQL, TINYINT(1) no MySQL, bit no SQL Server, 0/1 no SQLite.