Referência Completa de Tipos de Dados SQL

Manual de Todos os Campos do Banco de Dados

Explore cada tipo de campo (VARCHAR, CHAR, INT, DECIMAL, TIMESTAMP, JSON, ENUM...) agrupado por funcionalidade, como configurar no DrawDB e como validar e gravar com PHP 8 + PDO.

⚡ Mapa Rápido de Decisão: Qual Tipo Escolher no DrawDB?

Resumo executivo dos campos mais utilizados no dia a dia em projetos PHP

Ir para o Simulador "O que você quer guardar?" →
Funcionalidade / Dado Tipo SQL Ideal Size e Flags no DrawDB Bind no PHP PDO NUNCA Use (Erro Comum)
Chave Primária (`id`) BIGINT UNSIGNED PK AI NN PDO::PARAM_INT VARCHAR sem necessidade
Nomes, E-mails, Títulos VARCHAR(120..150) Size: 150 | NN PDO::PARAM_STR CHAR(150) ou TEXT
CPF (`11`), CEP (`8`), UF (`2`) CHAR(11) / CHAR(8) / CHAR(2) Size fixo: 11, 8, 2 PDO::PARAM_STR INT (apaga zero à esquerda!)
Preços, Salários e Moeda DECIMAL(10, 2) Size: 10,2 | Default: 0.00 PDO::PARAM_STR FLOAT / DOUBLE (erro de centavos!)
Senha (`password_hash`) VARCHAR(255) Size: 255 | NN PDO::PARAM_STR VARCHAR(20) ou MD5
Data de Criação / Auditoria TIMESTAMP Default: CURRENT_TIMESTAMP PDO::PARAM_STR VARCHAR("dd/mm/aaaa")

1. Identificadores & Chaves 3 tipos

Campos responsáveis por identificar unicamente cada linha (Primary Keys) e conectar tabelas (Foreign Keys), incluindo auto-incremento e UUIDs.

INT UNSIGNED AUTO_INCREMENT

⭐ Essencial 💾 4 Bytes (32 bits)

Chave primária numérica sequencial padrão (1, 2, 3...) ideal para a maioria das tabelas relacionais.

O tipo INT UNSIGNED AUTO_INCREMENT (ou SERIAL no PostgreSQL) gera automaticamente um número inteiro único e sequencial a cada novo registro inserido. Por ocupar apenas 4 bytes e ser inserido em ordem crescente, o índice B-Tree da chave primária permanece extremamente rápido e sem fragmentação em disco.

Limites de Capacidade: De 1 até 4.294.967.295 (Unsigned) ou até 2.147.483.647 (Signed)

✅ Quando Usar (Casos Ideais)

  • • Chave primária (id) de tabelas com até ~2 bilhões de registros (usuários, produtos, categorias, clientes).
  • • Chaves estrangeiras (usuario_id, categoria_id) que apontam para tabelas com PK do tipo INT.
  • • Sistemas internos onde o ID sequencial rápido melhora a performance de JOINs.

🚫 Quando NÃO Usar (Armadilhas)

  • • Tabelas de altíssimo volume que podem ultrapassar 4 bilhões de linhas (logs, eventos de IoT, transações financeiras globais) — nesses casos, use BIGINT.
  • • URLs públicas sensíveis onde você não quer que concorrentes descubram quantos clientes ou pedidos você tem (ex: /pedido/105 vs /pedido/106) — combine com um UUID público.

📐 Como Configurar no DrawDB Abrir DrawDB ↗

Type no DrawDB: INT (ou INTEGER)
Size / Values: Deixe em branco (não use INT(11), pois no MySQL 8+ o display width foi depreciado)
Default sugerido: Vazio (o banco gera automaticamente via Auto Increment)
Equivalência nos Dialetos do DrawDB:
MySQL / MariaDB INT UNSIGNED AUTO_INCREMENT PRIMARY KEY
PostgreSQL SERIAL PRIMARY KEY (ou INT GENERATED ALWAYS AS IDENTITY)
SQLite INTEGER PRIMARY KEY AUTOINCREMENT

📋 Exemplos Reais de Colunas com INT:

id
INT UNSIGNED AUTO_INCREMENT PRIMARY KEY
Identificador único da tabela
categoria_id
INT UNSIGNED NOT NULL
Chave estrangeira (FK) apontando para categorias.id
🗄️ SQL DDL (Importar no DrawDB: Import > SQL)
CREATE TABLE categorias (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    nome VARCHAR(80) NOT NULL
);

CREATE TABLE produtos (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    categoria_id INT UNSIGNED NOT NULL,
    nome VARCHAR(120) NOT NULL,
    CONSTRAINT fk_produtos_categoria
        FOREIGN KEY (categoria_id) REFERENCES categorias(id)
        ON DELETE RESTRICT ON UPDATE CASCADE
);
🐘 Como Validar e Salvar no PHP 8 + PDO
<?php
declare(strict_types=1);

// 1. Inserindo registro sem informar o ID (o banco gera via AUTO_INCREMENT)
$stmt = $pdo->prepare('INSERT INTO categorias (nome) VALUES (:nome)');
$stmt->bindValue(':nome', 'Eletrônicos', PDO::PARAM_STR);
$stmt->execute();

// 2. Recuperando o ID gerado pelo banco convertendo para int do PHP
$novoId = (int) $pdo->lastInsertId();
echo "Categoria criada com ID: {$novoId}\n";

// 3. Buscando por ID com validação e PDO::PARAM_INT
$idBusca = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if ($idBusca === false || $idBusca === null || $idBusca <= 0) {
    throw new InvalidArgumentException('ID inválido.');
}

$stmt = $pdo->prepare('SELECT id, nome FROM categorias WHERE id = :id');
$stmt->bindValue(':id', $idBusca, PDO::PARAM_INT);
$stmt->execute();
$categoria = $stmt->fetch(PDO::FETCH_ASSOC);

BIGINT UNSIGNED AUTO_INCREMENT

⭐ Essencial 💾 8 Bytes (64 bits)

Chave primária de 64 bits para tabelas massivas (padrão em frameworks modernos como Laravel).

Ocupa o dobro do espaço de um INT (8 bytes em vez de 4), mas permite até 18 quintilhões de registros. Frameworks modernos como Laravel utilizam BIGINT UNSIGNED como padrão (`$table->id()`) para garantir que nenhuma tabela esgote seus IDs, mesmo em sistemas de grande escala.

Limites de Capacidade: De 0 até 18.446.744.073.709.551.615 (Unsigned)

✅ Quando Usar (Casos Ideais)

  • • Tabelas que crescem rapidamente: pedidos, itens_pedido, logs_acesso, mensagens_chat, notificações, movimentações.
  • • Como padrão geral de chave primária caso queira total padronização no banco inteiro.

🚫 Quando NÃO Usar (Armadilhas)

  • • Tabelas auxiliares estáticas minúsculas (ex: estados_brasileiros com 27 linhas), onde `TINYINT` ou `INT` economiza memória em milhões de FKs.
  • • ATENÇÃO NO DRAWDB: Nunca crie uma FK `INT` apontando para uma PK `BIGINT`! O MySQL recusará criar a Foreign Key (Erro 150 / 3780).

📐 Como Configurar no DrawDB Abrir DrawDB ↗

Type no DrawDB: BIGINT
Size / Values: Vazio
Default sugerido: Vazio
Equivalência nos Dialetos do DrawDB:
MySQL / MariaDB BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY
PostgreSQL BIGSERIAL PRIMARY KEY
SQLite INTEGER PRIMARY KEY AUTOINCREMENT

📋 Exemplos Reais de Colunas com BIGINT:

id
BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY
PK de tabela de alto volume
usuario_id
BIGINT UNSIGNED NOT NULL
FK compatível com usuarios.id (BIGINT)
🗄️ SQL DDL (Importar no DrawDB: Import > SQL)
CREATE TABLE usuarios (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(150) NOT NULL UNIQUE
);

CREATE TABLE logs_auditoria (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    usuario_id BIGINT UNSIGNED NOT NULL,
    acao VARCHAR(100) NOT NULL,
    criado_em TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT fk_logs_usuario
        FOREIGN KEY (usuario_id) REFERENCES usuarios(id)
        ON DELETE CASCADE
);
🐘 Como Validar e Salvar no PHP 8 + PDO
<?php
declare(strict_types=1);

// Em PHP de 64-bits (padrão atual), o tipo `int` suporta até 9 quintilhões (PHP_INT_MAX).
$usuarioId = 9876543210;

$stmt = $pdo->prepare('
    INSERT INTO logs_auditoria (usuario_id, acao)
    VALUES (:usuario_id, :acao)
');
$stmt->bindValue(':usuario_id', $usuarioId, PDO::PARAM_INT);
$stmt->bindValue(':acao', 'LOGIN_SUCESSO', PDO::PARAM_STR);
$stmt->execute();

$logId = (int) $pdo->lastInsertId();

UUID (CHAR(36) / UUID Nativo)

⭐ Essencial 💾 36 Bytes em CHAR(36) no MySQL | 16 Bytes no tipo nativo UUID do PostgreSQL

Identificador universalmente único de 36 caracteres (ex: 550e8400-e29b-41d4-a716-446655440000).

UUIDs permitem gerar identificadores únicos diretamente no PHP antes mesmo de consultar o banco de dados, além de ocultar a contagem real de registros em URLs e APIs REST. Dica de ouro: prefira UUID v7 (ordenado por tempo) em vez de UUID v4 (totalmente aleatório) para evitar fragmentação de índices B-Tree.

Limites de Capacidade: Exatamente 36 caracteres (32 dígitos hexadecimais + 4 hífens)

✅ Quando Usar (Casos Ideais)

  • • IDs públicos expostos em URLs, APIs, links de pagamento ou notas fiscais (`/faturas/0192a4b2-7c90-...`).
  • • Sistemas distribuídos ou aplicativos offline que geram o ID antes de sincronizar com o servidor.
  • • Como coluna secundária (`uuid CHAR(36) UNIQUE`) mantendo `id BIGINT` interno para JOINs rápidos (Padrão Híbrido Recomendado!).

🚫 Quando NÃO Usar (Armadilhas)

  • • Não use `VARCHAR(36)` para UUID — como todo UUID com hífens tem exatamente 36 caracteres, `CHAR(36)` é mais rápido e não gasta byte extra de prefixo.
  • • Evite UUID v4 como Primary Key clusterizada no MySQL InnoDB em tabelas com milhões de linhas (prefira UUID v7 ou o padrão híbrido ID numérico + coluna UUID).

📐 Como Configurar no DrawDB Abrir DrawDB ↗

Type no DrawDB: CHAR (no MySQL) ou UUID (no PostgreSQL)
Size / Values: 36 (quando usar CHAR)
Default sugerido: UUID() no MySQL 8+ ou gen_random_uuid() no PostgreSQL
Equivalência nos Dialetos do DrawDB:
MySQL / MariaDB CHAR(36) NOT NULL UNIQUE (MariaDB 10.7+ possui tipo UUID nativo)
PostgreSQL UUID NOT NULL DEFAULT gen_random_uuid()
SQLite TEXT NOT NULL UNIQUE

📋 Exemplos Reais de Colunas com CHAR(36):

uuid
CHAR(36) NOT NULL UNIQUE
Identificador público seguro para expor na URL/API
transacao_externa_id
CHAR(36) NOT NULL
Chave de idempotência para evitar cobrança duplicada
🗄️ SQL DDL (Importar no DrawDB: Import > SQL)
CREATE TABLE pedidos (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    uuid CHAR(36) NOT NULL UNIQUE,
    valor_total DECIMAL(10, 2) NOT NULL,
    criado_em TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
🐘 Como Validar e Salvar no PHP 8 + PDO
<?php
declare(strict_types=1);

/**
 * Gerador nativo de UUID v4 em PHP 8+ (sem precisar de bibliotecas externas)
 */
function gerarUuidV4(): string {
    $bytes = random_bytes(16);
    $bytes[6] = chr((ord($bytes[6]) & 0x0f) | 0x40); // Versão 4
    $bytes[8] = chr((ord($bytes[8]) & 0x3f) | 0x80); // Variante RFC 4122
    return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($bytes), 4));
}

/**
 * Validando se uma string recebida na URL é um UUID válido
 */
function validarUuid(string $uuid): bool {
    return (bool) preg_match(
        '/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i',
        $uuid
    );
}

$uuid = gerarUuidV4();
$stmt = $pdo->prepare('INSERT INTO pedidos (uuid, valor_total) VALUES (:uuid, :total)');
$stmt->bindValue(':uuid', $uuid, PDO::PARAM_STR);
$stmt->bindValue(':total', '149.90', PDO::PARAM_STR);
$stmt->execute();

2. Numéricos Inteiros 3 tipos

Números sem casas decimais para contagens, quantidades em estoque, idades, ordenação e códigos numéricos.

TINYINT / TINYINT UNSIGNED

⭐ Essencial 💾 1 Byte (8 bits)

Inteiro super compacto de 1 Byte (0 a 255 sem sinal, ou -128 a 127 com sinal).

É o menor tipo numérico inteiro do banco de dados, consumindo apenas 1 byte por linha. Excelente para armazenar números pequenos cujos limites são bem conhecidos, como idade de uma pessoa, dia do mês (1 a 31), nota de avaliação (1 a 5 estrelas), número de parcelas de cartão (1 a 24) ou códigos de status.

Limites de Capacidade: Unsigned: 0 a 255 | Signed: -128 a 127

✅ Quando Usar (Casos Ideais)

  • • Idade de usuários (0 a 150 anos cabe com folga em `TINYINT UNSIGNED`).
  • • Avaliações/Ratings de produtos (1 a 5 estrelas ou 0 a 10).
  • • Mês do ano (1 a 12), dia da semana (1 a 7), número de parcelas (1 a 48).
  • • Prioridade ou nível de permissão numérico simples.

🚫 Quando NÃO Usar (Armadilhas)

  • • Quantidades de estoque ou contadores de visualizações que podem facilmente passar de 255.
  • • No PostgreSQL não existe `TINYINT` nativo (use `SMALLINT` no Postgres).

📐 Como Configurar no DrawDB Abrir DrawDB ↗

Type no DrawDB: TINYINT (MySQL/MariaDB) ou SMALLINT (PostgreSQL)
Size / Values: Vazio
Default sugerido: Ex: 0 ou 1
Equivalência nos Dialetos do DrawDB:
MySQL / MariaDB TINYINT UNSIGNED
PostgreSQL SMALLINT (2 bytes)
SQLite INTEGER

📋 Exemplos Reais de Colunas com TINYINT:

idade
TINYINT UNSIGNED NULL
Idade do cliente (0 a 255)
estrelas
TINYINT UNSIGNED NOT NULL
Nota da avaliação de 1 a 5
parcelas
TINYINT UNSIGNED NOT NULL DEFAULT 1
Quantidade de parcelas do pagamento
🗄️ SQL DDL (Importar no DrawDB: Import > SQL)
CREATE TABLE avaliacoes_produto (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    produto_id INT UNSIGNED NOT NULL,
    estrelas TINYINT UNSIGNED NOT NULL COMMENT 'Nota de 1 a 5',
    comentario VARCHAR(500) NULL,
    CONSTRAINT chk_estrelas CHECK (estrelas BETWEEN 1 AND 5)
);
🐘 Como Validar e Salvar no PHP 8 + PDO
<?php
declare(strict_types=1);

$estrelas = filter_input(INPUT_POST, 'estrelas', FILTER_VALIDATE_INT, [
    'options' => ['min_range' => 1, 'max_range' => 5]
]);

if ($estrelas === false || $estrelas === null) {
    throw new DomainException('A nota deve ser um número inteiro entre 1 e 5.');
}

$stmt = $pdo->prepare('
    INSERT INTO avaliacoes_produto (produto_id, estrelas, comentario)
    VALUES (:produto_id, :estrelas, :comentario)
');
$stmt->bindValue(':produto_id', 42, PDO::PARAM_INT);
$stmt->bindValue(':estrelas', $estrelas, PDO::PARAM_INT);
$stmt->bindValue(':comentario', 'Excelente produto!', PDO::PARAM_STR);
$stmt->execute();

SMALLINT / SMALLINT UNSIGNED

💾 2 Bytes (16 bits)

Inteiro de 2 Bytes para faixas intermediárias (0 a 65.535 sem sinal).

Ocupa metade do espaço de um INT comum (2 bytes). É ideal para quantidades moderadas que passam de 255 mas nunca chegarão a 65 mil, como ano de fabricação de veículos, número de páginas de um livro, portas de rede TCP/IP (1 a 65535) ou ordem de exibição de itens em menus e galerias.

Limites de Capacidade: Unsigned: 0 a 65.535 | Signed: -32.768 a 32.767

✅ Quando Usar (Casos Ideais)

  • • Número de páginas de um livro, duração de um filme em minutos.
  • • Ano histórico ou ano modelo de veículo (ex: 1850 a 2150).
  • • Porta de servidor de rede (0 a 65535).
  • • Quantidade de itens por pedido no carrinho de compras.

🚫 Quando NÃO Usar (Armadilhas)

  • • Estoque de produtos em atacado ou visualizações de página que possam ultrapassar 65.535.

📐 Como Configurar no DrawDB Abrir DrawDB ↗

Type no DrawDB: SMALLINT
Size / Values: Vazio
Default sugerido: Ex: 0 ou 1
Equivalência nos Dialetos do DrawDB:
MySQL / MariaDB SMALLINT UNSIGNED
PostgreSQL SMALLINT
SQLite INTEGER

📋 Exemplos Reais de Colunas com SMALLINT:

ano_fabricacao
SMALLINT UNSIGNED NOT NULL
Ano do veículo (ex: 2026)
numero_paginas
SMALLINT UNSIGNED NULL
Total de páginas de um livro
ordem_exibicao
SMALLINT UNSIGNED NOT NULL DEFAULT 0
Ordenação de banners/menus
🗄️ SQL DDL (Importar no DrawDB: Import > SQL)
CREATE TABLE livros (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    titulo VARCHAR(180) NOT NULL,
    ano_publicacao SMALLINT UNSIGNED NOT NULL,
    numero_paginas SMALLINT UNSIGNED NOT NULL
);
🐘 Como Validar e Salvar no PHP 8 + PDO
<?php
declare(strict_types=1);

$paginas = 384;
$ano = 2026;

if ($paginas < 1 || $paginas > 65535) {
    throw new RangeException('Número de páginas fora do limite de SMALLINT.');
}

$stmt = $pdo->prepare('
    INSERT INTO livros (titulo, ano_publicacao, numero_paginas)
    VALUES (:titulo, :ano, :paginas)
');
$stmt->bindValue(':titulo', 'Arquitetura de Bancos de Dados em PHP', PDO::PARAM_STR);
$stmt->bindValue(':ano', $ano, PDO::PARAM_INT);
$stmt->bindValue(':paginas', $paginas, PDO::PARAM_INT);
$stmt->execute();

INT / INTEGER (Com e Sem Sinal)

⭐ Essencial 💾 4 Bytes (32 bits)

O tipo inteiro padrão de 4 Bytes para quantidades de estoque, contadores, pontos e durações em segundos.

Quando não usado como chave primária, o INT (ou INTEGER) é o cavalo de batalha para números inteiros do dia a dia. Se o valor nunca puder ser negativo (como visualizações de vídeo, tempo em segundos), marque como UNSIGNED no MySQL ou adicione uma restrição CHECK (valor >= 0). Se puder ficar negativo (como saldo de pontos de fidelidade ou ajuste de estoque), mantenha SIGNED.

Limites de Capacidade: Signed: -2.147.483.648 a +2.147.483.647 | Unsigned: 0 a 4.294.967.295

✅ Quando Usar (Casos Ideais)

  • • Quantidade de produtos em estoque (`estoque_atual INT NOT NULL DEFAULT 0`).
  • • Contador de acessos, curtidas, downloads ou duração de vídeo em segundos.
  • • Armazenar valores monetários em centavos caso a equipe adote o padrão Money Pattern (ex: R$ 19,90 = 1990 centavos).

🚫 Quando NÃO Usar (Armadilhas)

  • • NUNCA use INT para armazenar CPF, CNPJ, CEP ou Telefone! Números de documentos e telefones começam com zero (`01234567890`) e o INT apaga os zeros à esquerda, além de estourar o limite de 32 bits em CNPJs e telefones com DDI.

📐 Como Configurar no DrawDB Abrir DrawDB ↗

Type no DrawDB: INT ou INTEGER
Size / Values: Vazio
Default sugerido: 0
Equivalência nos Dialetos do DrawDB:
MySQL / MariaDB INT (ou INT UNSIGNED)
PostgreSQL INTEGER
SQLite INTEGER

📋 Exemplos Reais de Colunas com INT:

quantidade_estoque
INT NOT NULL DEFAULT 0
Saldo atual de unidades no estoque
duracao_segundos
INT UNSIGNED NOT NULL
Tempo de duração de uma aula em vídeo
🗄️ SQL DDL (Importar no DrawDB: Import > SQL)
CREATE TABLE estoque_produtos (
    produto_id INT UNSIGNED PRIMARY KEY,
    quantidade_disponivel INT NOT NULL DEFAULT 0,
    quantidade_reservada INT UNSIGNED NOT NULL DEFAULT 0,
    atualizado_em TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
🐘 Como Validar e Salvar no PHP 8 + PDO
<?php
declare(strict_types=1);

// Atualização atômica de estoque no banco via PDO
$produtoId = 10;
$qtdComprada = 3;

$stmt = $pdo->prepare('
    UPDATE estoque_produtos
    SET quantidade_disponivel = quantidade_disponivel - :qtd
    WHERE produto_id = :id AND quantidade_disponivel >= :qtd
');
$stmt->bindValue(':qtd', $qtdComprada, PDO::PARAM_INT);
$stmt->bindValue(':id', $produtoId, PDO::PARAM_INT);
$stmt->execute();

if ($stmt->rowCount() === 0) {
    throw new RuntimeException('Estoque insuficiente para concluir a reserva.');
}

3. Decimais & Financeiros 2 tipos

Números com casas decimais de precisão exata (dinheiro, taxas, pesos) ou ponto flutuante científico.

DECIMAL(M, D) / NUMERIC(M, D)

⭐ Essencial 💾 Variável (~4 Bytes a cada 9 dígitos). DECIMAL(10,2) ocupa 5 Bytes.

Precisão exata para DINHEIRO, preços, saldos bancários, impostos e pesos comerciais.

O tipo DECIMAL(M, D) armazena números exatamente como foram digitados (em base 10), sem erros de arredondamento binário. O parâmetro M (Precisão) é o total de dígitos (antes + depois da vírgula), e o parâmetro D (Escala) é a quantidade de dígitos após a vírgula. Exemplo: `DECIMAL(10, 2)` guarda até 10 dígitos no total, sendo 8 antes da vírgula e 2 depois (até 99.999.999,99).

Limites de Capacidade: Até 65 dígitos totais (M) e até 30 casas decimais (D) no MySQL

✅ Quando Usar (Casos Ideais)

  • • Preços de produtos, valores de pedidos, descontos, fretes e saldos financeiros (`DECIMAL(10, 2)` ou `DECIMAL(12, 2)`).
  • • Taxas de juros, alíquotas de impostos ou cotações de moedas/cripto (`DECIMAL(18, 8)`).
  • • Pesos e medidas de venda por quilo ou litro (`DECIMAL(8, 3)` — ex: 1,250 kg).

🚫 Quando NÃO Usar (Armadilhas)

  • • NUNCA troque `DECIMAL` por `FLOAT` ou `DOUBLE` para guardar dinheiro! Em ponto flutuante binário, `0.1 + 0.2` resulta em `0.30000000000000004`, gerando furos de centavos no caixa.
  • • No PHP, ao ler e gravar `DECIMAL` no PDO, trafegue como `string` (`PDO::PARAM_STR`) e use a extensão `bcmath` para cálculos financeiros exatos.

📐 Como Configurar no DrawDB Abrir DrawDB ↗

Type no DrawDB: DECIMAL (ou NUMERIC)
Size / Values: 10,2 (para moeda até R$ 99 milhões) ou 12,2 (até R$ 9 bilhões)
Default sugerido: 0.00
Equivalência nos Dialetos do DrawDB:
MySQL / MariaDB DECIMAL(10, 2)
PostgreSQL NUMERIC(10, 2) ou DECIMAL(10, 2)
SQLite NUMERIC(10, 2) (ou armazenar como INTEGER em centavos)

📋 Exemplos Reais de Colunas com DECIMAL:

preco_unitario
DECIMAL(10, 2) NOT NULL
Preço em reais (ex: 1499.90)
peso_kg
DECIMAL(8, 3) NOT NULL DEFAULT 0.000
Peso em kg com gramas (ex: 0.450)
taxa_cambio
DECIMAL(12, 6) NOT NULL
Cotação cambial com 6 casas decimais
🗄️ SQL DDL (Importar no DrawDB: Import > SQL)
CREATE TABLE itens_pedido (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    pedido_id BIGINT UNSIGNED NOT NULL,
    produto_id INT UNSIGNED NOT NULL,
    quantidade SMALLINT UNSIGNED NOT NULL DEFAULT 1,
    preco_unitario DECIMAL(10, 2) NOT NULL,
    desconto DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
    subtotal DECIMAL(12, 2) GENERATED ALWAYS AS ((quantidade * preco_unitario) - desconto) STORED
);
🐘 Como Validar e Salvar no PHP 8 + PDO
<?php
declare(strict_types=1);

// Em PHP, trate valores monetários como string numérica com BCMath ou centavos inteiros
// para jamais perder precisão em floats!
$precoUnitario = '129.90';
$desconto      = '15.00';

// Validação de formato decimal (até 8 inteiros e 2 casas decimais)
if (!preg_match('/^\d{1,8}(\.\d{1,2})?$/', $precoUnitario)) {
    throw new InvalidArgumentException('Formato de preço inválido.');
}

// Cálculo exato no PHP usando BCMath (2 casas decimais)
$precoFinal = bcsub($precoUnitario, $desconto, 2); // "114.90"

// No PDO, SEMPRE envie DECIMAL como PDO::PARAM_STR para preservar a precisão exata!
$stmt = $pdo->prepare('
    INSERT INTO itens_pedido (pedido_id, produto_id, quantidade, preco_unitario, desconto)
    VALUES (:pedido_id, :produto_id, :qtd, :preco, :desconto)
');
$stmt->bindValue(':pedido_id', 5001, PDO::PARAM_INT);
$stmt->bindValue(':produto_id', 99, PDO::PARAM_INT);
$stmt->bindValue(':qtd', 2, PDO::PARAM_INT);
$stmt->bindValue(':preco', $precoUnitario, PDO::PARAM_STR);
$stmt->bindValue(':desconto', $desconto, PDO::PARAM_STR);
$stmt->execute();

FLOAT / DOUBLE (Ponto Flutuante)

💾 FLOAT: 4 Bytes | DOUBLE: 8 Bytes

Números científicos aproximados de alta velocidade para sensores, física, estatística e telemetria.

FLOAT (precisão simples, 4 bytes) e DOUBLE (precisão dupla, 8 bytes) utilizam o padrão IEEE 754 de ponto flutuante binário. São processados diretamente pela unidade de ponto flutuante (FPU) do processador com extrema velocidade, mas são valores APROXIMADOS.

Limites de Capacidade: FLOAT: ~7 dígitos de precisão | DOUBLE: ~15-17 dígitos de precisão

✅ Quando Usar (Casos Ideais)

  • • Medições de sensores IoT (temperatura ambiente, umidade do ar, pressão atmosférica, velocidade do vento).
  • • Métricas estatísticas aproximadas, pesos de algoritmos de Machine Learning ou gráficos científicos.

🚫 Quando NÃO Usar (Armadilhas)

  • • PROIBIDO para valores financeiros, contábeis, preços ou saldos bancários (use `DECIMAL`).
  • • Evite fazer `WHERE valor_float = 19.99` em SQL, pois a representação binária interna pode ser `19.989999771118164`.

📐 Como Configurar no DrawDB Abrir DrawDB ↗

Type no DrawDB: DOUBLE (ou FLOAT / REAL)
Size / Values: Vazio (a sintaxe FLOAT(M,D) foi depreciada no MySQL 8)
Default sugerido: 0
Equivalência nos Dialetos do DrawDB:
MySQL / MariaDB DOUBLE ou FLOAT
PostgreSQL DOUBLE PRECISION ou REAL
SQLite REAL

📋 Exemplos Reais de Colunas com DOUBLE:

temperatura_celsius
FLOAT NOT NULL
Leitura de sensor térmico (ex: 23.45)
score_relevancia
DOUBLE NOT NULL DEFAULT 0
Pontuação estatística de busca
🗄️ SQL DDL (Importar no DrawDB: Import > SQL)
CREATE TABLE leituras_sensor (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    sensor_id INT UNSIGNED NOT NULL,
    temperatura_celsius FLOAT NOT NULL,
    umidade_relativa FLOAT NOT NULL,
    lido_em TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
🐘 Como Validar e Salvar no PHP 8 + PDO
<?php
declare(strict_types=1);

$temperatura = filter_var('24.65', FILTER_VALIDATE_FLOAT);
if ($temperatura === false) {
    throw new InvalidArgumentException('Temperatura inválida.');
}

$stmt = $pdo->prepare('
    INSERT INTO leituras_sensor (sensor_id, temperatura_celsius, umidade_relativa)
    VALUES (:sensor_id, :temp, :umidade)
');
$stmt->bindValue(':sensor_id', 1, PDO::PARAM_INT);
$stmt->bindValue(':temp', (string) $temperatura, PDO::PARAM_STR);
$stmt->bindValue(':umidade', '62.5', PDO::PARAM_STR);
$stmt->execute();

4. Textos Curtos & Variáveis 2 tipos

Campos de texto de tamanho fixo (CHAR) ou variável (VARCHAR) para nomes, e-mails, CPFs, telefones, siglas e hashes.

VARCHAR(n) (Texto de Tamanho Variável)

⭐ Essencial 💾 Tamanho real da string em bytes + 1 byte (até 255) ou + 2 bytes (> 255)

O campo de texto mais usado do banco de dados: armazena apenas os caracteres digitados + 1 ou 2 bytes de tamanho.

Diferente do CHAR, o VARCHAR(n) ocupa apenas o espaço real do texto armazenado mais 1 byte de controle (se n <= 255) ou 2 bytes (se n > 255). O número `n` define a quantidade MÁXIMA de caracteres permitidos (não de bytes!). Em `utf8mb4`, cada caractere pode usar de 1 a 4 bytes. Mesmo que o espaço em disco seja dinâmico, definir um limite `n` realista (ex: `VARCHAR(120)` em vez de `VARCHAR(255)` em tudo) economiza memória RAM, pois o MySQL aloca buffers em memória com base no tamanho máximo declarado durante ordenações (`ORDER BY`) e tabelas temporárias!

Limites de Capacidade: De 1 até 16.383 caracteres em utf8mb4 (limite de 65.535 bytes por linha no MySQL)

✅ Quando Usar (Casos Ideais)

  • • Nomes de pessoas, razões sociais, títulos de produtos (`VARCHAR(100)` a `VARCHAR(180)`).
  • • Endereços de e-mail (`VARCHAR(150)` ou `VARCHAR(254)` conforme RFC).
  • • Senhas criptografadas com `password_hash()` do PHP (Bcrypt/Argon2id) -> use `VARCHAR(255)`.
  • • Telefones com DDI/DDD (`VARCHAR(20)`), URLs (`VARCHAR(500)`), Slugs (`VARCHAR(160)`).

🚫 Quando NÃO Usar (Armadilhas)

  • • Códigos cujo tamanho é SEMPRE idêntico em 100% dos registros, como UF (`SP`, `RJ` -> 2 caracteres) ou CPF sem máscara (`11` dígitos) — nesses casos, prefira `CHAR`.
  • • Textos imensos sem limite previsível como corpo de matérias ou artigos completos — use `TEXT`.

📐 Como Configurar no DrawDB Abrir DrawDB ↗

Type no DrawDB: VARCHAR
Size / Values: Ex: 100 (nome), 150 (email), 255 (senha_hash), 20 (telefone)
Default sugerido: Vazio ou string padrão
Equivalência nos Dialetos do DrawDB:
MySQL / MariaDB VARCHAR(150)
PostgreSQL VARCHAR(150)
SQLite TEXT (SQLite trata VARCHAR como afinidade TEXT)

📋 Exemplos Reais de Colunas com VARCHAR:

nome_completo
VARCHAR(120) NOT NULL
Nome do cliente ou usuário
email
VARCHAR(150) NOT NULL UNIQUE
E-mail de login único
senha_hash
VARCHAR(255) NOT NULL
Hash gerado por password_hash() no PHP
telefone
VARCHAR(20) NULL
Telefone celular / WhatsApp (ex: +5511999998888)
🗄️ SQL DDL (Importar no DrawDB: Import > SQL)
CREATE TABLE clientes (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    nome_completo VARCHAR(120) NOT NULL,
    email VARCHAR(150) NOT NULL UNIQUE,
    senha_hash VARCHAR(255) NOT NULL,
    telefone VARCHAR(20) NULL,
    cidade VARCHAR(80) NOT NULL
);
🐘 Como Validar e Salvar no PHP 8 + PDO
<?php
declare(strict_types=1);

$nome  = trim($_POST['nome'] ?? '');
$email = trim($_POST['email'] ?? '');
$senha = $_POST['senha'] ?? '';

// 1. Validação do comprimento em caracteres UTF-8 com mb_strlen()
if ($nome === '' || mb_strlen($nome, 'UTF-8') > 120) {
    throw new InvalidArgumentException('O nome deve ter entre 1 e 120 caracteres.');
}

// 2. Validação nativa de e-mail e limite do VARCHAR(150)
if (!filter_var($email, FILTER_VALIDATE_EMAIL) || mb_strlen($email, 'UTF-8') > 150) {
    throw new InvalidArgumentException('E-mail inválido.');
}

// 3. Hash seguro de senha no PHP (sempre cabe em VARCHAR(255))
$senhaHash = password_hash($senha, PASSWORD_DEFAULT);

$stmt = $pdo->prepare('
    INSERT INTO clientes (nome_completo, email, senha_hash)
    VALUES (:nome, :email, :senha_hash)
');
$stmt->bindValue(':nome', $nome, PDO::PARAM_STR);
$stmt->bindValue(':email', mb_strtolower($email, 'UTF-8'), PDO::PARAM_STR);
$stmt->bindValue(':senha_hash', $senhaHash, PDO::PARAM_STR);
$stmt->execute();

CHAR(n) (Texto de Tamanho Fixo)

⭐ Essencial 💾 Fixo: n caracteres (em ASCII/latin1 = n bytes; em utf8mb4 = n até 4*n bytes)

Texto de comprimento EXATO e fixo para siglas (UF), CPF, CNPJ, CEP, códigos ISO e hashes.

O tipo CHAR(n) reserva espaço fixo para exatamente `n` caracteres. Não gasta byte extra para gravar o comprimento da string e oferece leitura e indexação ligeiramente mais rápidas quando todos os valores possuem sempre a mesma quantidade de caracteres.

Limites de Capacidade: De 0 até 255 caracteres

✅ Quando Usar (Casos Ideais)

  • • Sigla de Estado / UF (`CHAR(2)` -> `SP`, `RJ`, `MG`).
  • • Código ISO de País ou Moeda (`CHAR(2)` -> `BR`, `US` ou `CHAR(3)` -> `BRL`, `USD`).
  • • CPF sem pontuação (`CHAR(11)`), CNPJ sem pontuação (`CHAR(14)`), CEP sem traço (`CHAR(8)`).
  • • Hashes hexadecimais de tamanho fixo: SHA-256 (`CHAR(64)`), MD5 (`CHAR(32)`), UUID (`CHAR(36)`).

🚫 Quando NÃO Usar (Armadilhas)

  • • Nomes de pessoas, endereços ou textos cujo tamanho varia bastante (desperdiça espaço — use `VARCHAR`).

📐 Como Configurar no DrawDB Abrir DrawDB ↗

Type no DrawDB: CHAR
Size / Values: Ex: 2 (UF), 8 (CEP), 11 (CPF), 14 (CNPJ), 64 (SHA-256)
Default sugerido: Ex: BR (para país) ou BRL (para moeda)
Equivalência nos Dialetos do DrawDB:
MySQL / MariaDB CHAR(11)
PostgreSQL CHAR(11)
SQLite TEXT

📋 Exemplos Reais de Colunas com CHAR:

cpf
CHAR(11) NOT NULL UNIQUE
CPF apenas dígitos (ex: 01234567890)
uf
CHAR(2) NOT NULL
Sigla do estado (ex: SP)
cep
CHAR(8) NOT NULL
CEP apenas dígitos (ex: 01310100)
moeda
CHAR(3) NOT NULL DEFAULT 'BRL'
Código ISO 4217 da moeda
🗄️ SQL DDL (Importar no DrawDB: Import > SQL)
CREATE TABLE enderecos_faturamento (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    cliente_id BIGINT UNSIGNED NOT NULL,
    cpf CHAR(11) NOT NULL UNIQUE,
    cep CHAR(8) NOT NULL,
    uf CHAR(2) NOT NULL,
    pais CHAR(2) NOT NULL DEFAULT 'BR'
);
🐘 Como Validar e Salvar no PHP 8 + PDO
<?php
declare(strict_types=1);

// Higienizando CPF e CEP no PHP (removendo pontos e traços antes de salvar em CHAR)
$cpfBruto = '123.456.789-01';
$cepBruto = '01310-100';
$ufBruta  = 'sp';

$cpf = preg_replace('/\D/', '', $cpfBruto); // "12345678901" (11 dígitos)
$cep = preg_replace('/\D/', '', $cepBruto); // "01310100" (8 dígitos)
$uf  = strtoupper(trim($ufBruta));          // "SP" (2 letras)

if (strlen($cpf) !== 11 || strlen($cep) !== 8 || strlen($uf) !== 2) {
    throw new InvalidArgumentException('Dados com tamanho incorreto para os campos CHAR.');
}

$stmt = $pdo->prepare('
    INSERT INTO enderecos_faturamento (cliente_id, cpf, cep, uf)
    VALUES (:cliente_id, :cpf, :cep, :uf)
');
$stmt->bindValue(':cliente_id', 1, PDO::PARAM_INT);
$stmt->bindValue(':cpf', $cpf, PDO::PARAM_STR);
$stmt->bindValue(':cep', $cep, PDO::PARAM_STR);
$stmt->bindValue(':uf', $uf, PDO::PARAM_STR);
$stmt->execute();

5. Textos Longos & Conteúdo 1 tipo

Campos dimensionados para grandes volumes de texto como artigos de blog, descrições de produtos, HTML, Markdown e logs.

TEXT / MEDIUMTEXT / LONGTEXT

⭐ Essencial 💾 TEXT: até 64 KB | MEDIUMTEXT: até 16 MB | LONGTEXT: até 4 GB

Campos para textos extensos como descrições detalhadas, artigos de blog, contratos e logs.

Os tipos da família TEXT são armazenados fora da estrutura principal da linha no banco de dados (off-page storage no InnoDB), deixando na linha apenas um ponteiro de 20 bytes. Isso permite guardar textos imensos sem estourar o limite de 64 KB por linha das tabelas relacionais.

Limites de Capacidade: TINYTEXT: 255 B | TEXT: 65.535 B (~64 KB) | MEDIUMTEXT: 16,7 MB | LONGTEXT: 4,2 GB

✅ Quando Usar (Casos Ideais)

  • • `TEXT` (até 64 KB): Descrições de produtos, biografias, comentários de usuários, observações de pedidos.
  • • `MEDIUMTEXT` (até 16 MB): Artigos completos de blog em HTML/Markdown, termos de uso, templates de e-mail.
  • • `LONGTEXT` (até 4 GB): Livros inteiros, dumps extensos ou payloads massivos de integrações.

🚫 Quando NÃO Usar (Armadilhas)

  • • Não use `TEXT` para campos curtos que serão frequentemente filtrados com `=` ou `ORDER BY` (como nome, e-mail, cidade) — use `VARCHAR`.
  • • No MySQL, colunas `TEXT` não aceitam valor `DEFAULT` literal direto em versões antigas e exigem prefixo ao criar índices normais (ou índice `FULLTEXT`).

📐 Como Configurar no DrawDB Abrir DrawDB ↗

Type no DrawDB: TEXT (ou MEDIUMTEXT / LONGTEXT)
Size / Values: Vazio
Default sugerido: Deixe vazio (ou NULL se o campo for opcional)
Equivalência nos Dialetos do DrawDB:
MySQL / MariaDB TEXT, MEDIUMTEXT ou LONGTEXT
PostgreSQL TEXT (suporta até 1 GB automaticamente sem precisar de MEDIUM/LONG)
SQLite TEXT

📋 Exemplos Reais de Colunas com TEXT:

descricao_completa
TEXT NULL
Ficha técnica e descrição de um produto
conteudo_markdown
MEDIUMTEXT NOT NULL
Corpo completo de um artigo ou aula
🗄️ SQL DDL (Importar no DrawDB: Import > SQL)
CREATE TABLE artigos_blog (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    titulo VARCHAR(180) NOT NULL,
    slug VARCHAR(200) NOT NULL UNIQUE,
    resumo VARCHAR(300) NOT NULL,
    conteudo_html MEDIUMTEXT NOT NULL,
    FULLTEXT INDEX idx_busca_artigo (titulo, resumo, conteudo_html)
);
🐘 Como Validar e Salvar no PHP 8 + PDO
<?php
declare(strict_types=1);

$titulo   = 'Guia Definitivo de Tipos de Dados no MySQL e PostgreSQL';
$slug     = 'guia-definitivo-tipos-dados-mysql-postgresql';
$resumo   = 'Aprenda quando usar VARCHAR, CHAR, TEXT, INT e DECIMAL com exemplos em PHP.';
$conteudo = "<p>Conteúdo longo do artigo...</p>";

$stmt = $pdo->prepare('
    INSERT INTO artigos_blog (titulo, slug, resumo, conteudo_html)
    VALUES (:titulo, :slug, :resumo, :conteudo)
');
$stmt->bindValue(':titulo', $titulo, PDO::PARAM_STR);
$stmt->bindValue(':slug', $slug, PDO::PARAM_STR);
$stmt->bindValue(':resumo', $resumo, PDO::PARAM_STR);
$stmt->bindValue(':conteudo', $conteudo, PDO::PARAM_STR);
$stmt->execute();

6. Datas, Horários & Auditoria 3 tipos

Registro de datas de nascimento, agendamentos, horários de funcionamento e carimbos de auditoria (created_at, updated_at).

DATE (Apenas Data: AAAA-MM-DD)

⭐ Essencial 💾 3 Bytes (MySQL) | 4 Bytes (PostgreSQL)

Armazena somente ano, mês e dia (ex: 1995-08-24), sem horário nem fuso horário.

O tipo DATE ocupa apenas 3 bytes e utiliza o formato universal ISO 8601 (`YYYY-MM-DD`). É ideal para eventos civis que não dependem de hora nem sofrem alteração por fuso horário (uma pessoa nascida em 10/05/1990 continua tendo nascido em 10/05/1990 em qualquer país do mundo).

Limites de Capacidade: De 1000-01-01 até 9999-12-31

✅ Quando Usar (Casos Ideais)

  • • Data de nascimento (`data_nascimento DATE`).
  • • Data de vencimento de boleto/fatura (`data_vencimento DATE`).
  • • Data de admissão, feriados ou início de vigência de contrato.

🚫 Quando NÃO Usar (Armadilhas)

  • • Quando você precisa saber a hora e minuto exatos em que algo ocorreu (use `DATETIME` ou `TIMESTAMP`).
  • • Nunca salve datas como `VARCHAR(10)` no formato `DD/MM/AAAA`! Isso impede o banco de ordenar por data (`ORDER BY`) ou calcular intervalos (`DATEDIFF`). Formate para `d/m/Y` apenas na tela com PHP!

📐 Como Configurar no DrawDB Abrir DrawDB ↗

Type no DrawDB: DATE
Size / Values: Vazio
Default sugerido: Vazio ou CURRENT_DATE
Equivalência nos Dialetos do DrawDB:
MySQL / MariaDB DATE
PostgreSQL DATE
SQLite TEXT (formato YYYY-MM-DD)

📋 Exemplos Reais de Colunas com DATE:

data_nascimento
DATE NULL
Aniversário do usuário
data_vencimento
DATE NOT NULL
Vencimento de uma cobrança
🗄️ SQL DDL (Importar no DrawDB: Import > SQL)
CREATE TABLE faturas (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    cliente_id BIGINT UNSIGNED NOT NULL,
    valor DECIMAL(10, 2) NOT NULL,
    data_emissao DATE NOT NULL,
    data_vencimento DATE NOT NULL
);
🐘 Como Validar e Salvar no PHP 8 + PDO
<?php
declare(strict_types=1);

// 1. Convertendo data brasileira (DD/MM/YYYY) para padrão do banco (YYYY-MM-DD) com DateTimeImmutable
$inputUsuario = '25/12/2026';
$dataVencimento = DateTimeImmutable::createFromFormat('!d/m/Y', $inputUsuario);
$erros = DateTimeImmutable::getLastErrors();

if (!$dataVencimento || ($erros !== false && ($erros['warning_count'] > 0 || $erros['error_count'] > 0))) {
    throw new InvalidArgumentException('Data inválida.');
}

// 2. Salvando no banco no formato ISO Y-m-d
$stmt = $pdo->prepare('
    INSERT INTO faturas (cliente_id, valor, data_emissao, data_vencimento)
    VALUES (:cliente, :valor, :emissao, :vencimento)
');
$stmt->bindValue(':cliente', 10, PDO::PARAM_INT);
$stmt->bindValue(':valor', '250.00', PDO::PARAM_STR);
$stmt->bindValue(':emissao', (new DateTimeImmutable('today'))->format('Y-m-d'), PDO::PARAM_STR);
$stmt->bindValue(':vencimento', $dataVencimento->format('Y-m-d'), PDO::PARAM_STR);
$stmt->execute();

// 3. Ao exibir na tela em PHP:
echo 'Vencimento: ' . $dataVencimento->format('d/m/Y'); // 25/12/2026

DATETIME vs TIMESTAMP (Data + Hora)

⭐ Essencial 💾 TIMESTAMP: 4 Bytes (MySQL) | DATETIME: 5 a 8 Bytes

Registros de data e hora completa (AAAA-MM-DD HH:MM:SS) para eventos, agendamentos e auditoria.

Ambos guardam data e hora (`YYYY-MM-DD HH:MM:SS`), mas têm uma diferença vital: o `TIMESTAMP` converte automaticamente o horário da sessão para UTC ao salvar no disco e converte de volta para o fuso horário atual ao consultar (ótimo para `created_at` e `updated_at`), ocupando 4 bytes (nota: no MySQL de 32-bits/antigo o TIMESTAMP vai até 2038; no PostgreSQL `TIMESTAMPTZ` ocupa 8 bytes e não tem limite de 2038). Já o `DATETIME` ocupa 5 a 8 bytes, vai do ano 1000 ao 9999 e guarda a data/hora literal sem conversão de fuso (ideal para agendamentos futuros como consultas médicas).

Limites de Capacidade: DATETIME: 1000-01-01 00:00:00 a 9999-12-31 23:59:59 | TIMESTAMP MySQL: 1970 a 2038

✅ Quando Usar (Casos Ideais)

  • • Use `TIMESTAMP` (ou `TIMESTAMPTZ` no Postgres) para carimbos de auditoria do momento exato em que algo aconteceu: `criado_em`, `atualizado_em`, `pago_em`, `deletado_em`.
  • • Use `DATETIME` para datas futuras distantes (após 2038 no MySQL) ou agendamentos civis fixos (`data_hora_consulta DATETIME`).

🚫 Quando NÃO Usar (Armadilhas)

  • • Não use `INT` para guardar Unix Timestamp manual se quiser usar funções de data nativas do SQL de forma legível nos relatórios e no DrawDB.

📐 Como Configurar no DrawDB Abrir DrawDB ↗

Type no DrawDB: TIMESTAMP ou DATETIME
Size / Values: Vazio
Default sugerido: CURRENT_TIMESTAMP
Equivalência nos Dialetos do DrawDB:
MySQL / MariaDB DATETIME ou TIMESTAMP DEFAULT CURRENT_TIMESTAMP
PostgreSQL TIMESTAMPTZ (TIMESTAMP WITH TIME ZONE) DEFAULT CURRENT_TIMESTAMP
SQLite DATETIME DEFAULT CURRENT_TIMESTAMP

📋 Exemplos Reais de Colunas com TIMESTAMP:

criado_em
TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
Auditoria automática de criação
agendado_para
DATETIME NOT NULL
Data e hora de uma consulta médica
deletado_em
TIMESTAMP NULL DEFAULT NULL
Soft Delete (exclusão lógica)
🗄️ SQL DDL (Importar no DrawDB: Import > SQL)
CREATE TABLE consultas_medicas (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    paciente_id BIGINT UNSIGNED NOT NULL,
    agendado_para DATETIME NOT NULL,
    criado_em TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    atualizado_em TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    cancelado_em TIMESTAMP NULL DEFAULT NULL
);
🐘 Como Validar e Salvar no PHP 8 + PDO
<?php
declare(strict_types=1);

date_default_timezone_set('America/Sao_Paulo');

$agendamento = new DateTimeImmutable('2026-11-10 14:30:00');

$stmt = $pdo->prepare('
    INSERT INTO consultas_medicas (paciente_id, agendado_para)
    VALUES (:paciente_id, :agendado_para)
');
$stmt->bindValue(':paciente_id', 77, PDO::PARAM_INT);
$stmt->bindValue(':agendado_para', $agendamento->format('Y-m-d H:i:s'), PDO::PARAM_STR);
$stmt->execute();

TIME & YEAR (Hora Isolada e Ano)

💾 TIME: 3 Bytes | YEAR: 1 Byte

TIME armazena horários do dia ou durações (HH:MM:SS); YEAR armazena um ano em 1 Byte.

O tipo `TIME` (3 bytes) é ideal para horários recorrentes que independem de um dia específico, como horário de abertura e fechamento de uma loja (`08:00:00` às `18:00:00`). Já o tipo `YEAR` no MySQL ocupa apenas 1 byte para anos entre 1901 e 2155.

Limites de Capacidade: TIME: -838:59:59 a 838:59:59 | YEAR: 1901 a 2155

✅ Quando Usar (Casos Ideais)

  • • Horário comercial de abertura e fechamento (`abre_as TIME`, `fecha_as TIME`).
  • • Horário de início de uma turma escolar (`horario_aula TIME`).

🚫 Quando NÃO Usar (Armadilhas)

  • • Para anos fora da faixa de 1901 a 2155 (use `SMALLINT` em vez de `YEAR` para maior compatibilidade com PostgreSQL e SQLite).

📐 Como Configurar no DrawDB Abrir DrawDB ↗

Type no DrawDB: TIME
Size / Values: Vazio
Default sugerido: Ex: 08:00:00
Equivalência nos Dialetos do DrawDB:
MySQL / MariaDB TIME
PostgreSQL TIME
SQLite TEXT

📋 Exemplos Reais de Colunas com TIME:

horario_abertura
TIME NOT NULL
Hora em que a loja abre (ex: 08:30:00)
horario_fechamento
TIME NOT NULL
Hora em que a loja fecha (ex: 18:00:00)
🗄️ SQL DDL (Importar no DrawDB: Import > SQL)
CREATE TABLE horarios_funcionamento (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    dia_semana TINYINT UNSIGNED NOT NULL COMMENT '1=Dom, 2=Seg ... 7=Sab',
    horario_abertura TIME NOT NULL,
    horario_fechamento TIME NOT NULL
);
🐘 Como Validar e Salvar no PHP 8 + PDO
<?php
declare(strict_types=1);

$abertura   = '08:30:00';
$fechamento = '18:00:00';

if (!preg_match('/^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$/', $abertura)) {
    throw new InvalidArgumentException('Horário inválido.');
}

$stmt = $pdo->prepare('
    INSERT INTO horarios_funcionamento (dia_semana, horario_abertura, horario_fechamento)
    VALUES (:dia, :abre, :fecha)
');
$stmt->bindValue(':dia', 2, PDO::PARAM_INT); // Segunda-feira
$stmt->bindValue(':abre', $abertura, PDO::PARAM_STR);
$stmt->bindValue(':fecha', $fechamento, PDO::PARAM_STR);
$stmt->execute();

7. Estruturas Flexíveis & JSON 1 tipo

Armazenamento de objetos, arrays e metadados dinâmicos validados diretamente pelo banco de dados com suporte a buscas internas.

JSON / JSONB (Documentos Estruturados)

⭐ Essencial 💾 Aproximadamente o tamanho de um LONGBLOB/LONGTEXT (até 1 GB no Postgres / 4 GB no MySQL)

Armazena objetos e arrays JSON validados automaticamente e consultáveis via SQL.

O tipo nativo `JSON` (MySQL 5.7.8+ / MariaDB) e `JSONB` (PostgreSQL) valida automaticamente se a string enviada é um JSON válido e a armazena em um formato binário otimizado que permite buscar chaves específicas (`JSON_EXTRACT` ou operador `->`) sem precisar ler e fazer parse do texto inteiro.

Limites de Capacidade: Documentos JSON válidos (objetos `{}` ou arrays `[]`)

✅ Quando Usar (Casos Ideais)

  • • Atributos dinâmicos de produtos de categorias muito diferentes (ex: ficha técnica de uma Camiseta vs Placa de Vídeo).
  • • Preferências e configurações de interface do usuário (tema escuro, notificações, idioma).
  • • Payloads brutos de Webhooks (Stripe, Mercado Pago, Asaas) e snapshots de auditoria (antes/depois da edição).

🚫 Quando NÃO Usar (Armadilhas)

  • • NÃO use JSON como muleta para evitar criar tabelas e relacionamentos normais! Nunca guarde chaves estrangeiras dentro de um array JSON (`"produtos_ids": [1, 5, 9]`) — crie uma tabela associativa!

📐 Como Configurar no DrawDB Abrir DrawDB ↗

Type no DrawDB: JSON (ou JSONB no PostgreSQL)
Size / Values: Vazio
Default sugerido: Deixe vazio ou NULL
Equivalência nos Dialetos do DrawDB:
MySQL / MariaDB JSON
PostgreSQL JSONB (Binário indexável recomendado) ou JSON
SQLite TEXT (com funções da extensão JSON1 nativas)

📋 Exemplos Reais de Colunas com JSON:

especificacoes_tecnicas
JSON NULL
Atributos variáveis do produto (voltagem, memória, cor)
configuracoes
JSON NOT NULL
Preferências customizadas do usuário
🗄️ SQL DDL (Importar no DrawDB: Import > SQL)
CREATE TABLE produtos_catalogo (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    nome VARCHAR(150) NOT NULL,
    preco DECIMAL(10, 2) NOT NULL,
    atributos_extras JSON NULL COMMENT 'Ex: {"voltagem": "220v", "garantia_meses": 12}'
);
🐘 Como Validar e Salvar no PHP 8 + PDO
<?php
declare(strict_types=1);

// 1. Codificando array associativo do PHP para JSON com tratamento de erro nativo (JSON_THROW_ON_ERROR)
$atributos = [
    'processador' => 'Ryzen 7',
    'memoria_ram_gb' => 32,
    'conexoes' => ['HDMI 2.1', 'USB-C', 'Wi-Fi 6E'],
];

$jsonString = json_encode($atributos, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);

$stmt = $pdo->prepare('
    INSERT INTO produtos_catalogo (nome, preco, atributos_extras)
    VALUES (:nome, :preco, :atributos)
');
$stmt->bindValue(':nome', 'Notebook Pro 16', PDO::PARAM_STR);
$stmt->bindValue(':preco', '7499.90', PDO::PARAM_STR);
$stmt->bindValue(':atributos', $jsonString, PDO::PARAM_STR);
$stmt->execute();

// 2. Lendo do banco e decodificando de volta para Array PHP
$dadosAtributos = json_decode($jsonString, true, 512, JSON_THROW_ON_ERROR);
echo $dadosAtributos['processador']; // "Ryzen 7"

8. Opções & Conjuntos (Listas) 1 tipo

Restrição de valores a uma lista fechada pré-definida de opções (como status de pedido ou nível de acesso).

ENUM (Lista Fechada de Opções)

⭐ Essencial 💾 1 Byte (até 255 opções) ou 2 Bytes (até 65.535 opções)

Permite que a coluna assuma apenas UM valor dentre uma lista pré-definida de opções.

Internamente no MySQL, o ENUM armazena o texto das opções no metadado da tabela e grava apenas um índice numérico compacto de 1 byte na linha (para até 255 opções), mas exibe e filtra como string. No PHP 8.1+, combina perfeitamente com `BackedEnum`. Alternativamente, em sistemas onde a lista de opções muda com frequência, muitos arquitetos preferem `VARCHAR(30)` validado por um `enum` do PHP ou uma tabela de domínio auxiliar.

Limites de Capacidade: 1 valor escolhido por registro dentre a lista declarada

✅ Quando Usar (Casos Ideais)

  • • Status de máquina de estados estável: `ENUM('pendente', 'pago', 'enviado', 'cancelado')`.
  • • Tipo de pessoa: `ENUM('PF', 'PJ')`.
  • • Nível de severidade de um log: `ENUM('info', 'warning', 'error', 'critical')`.

🚫 Quando NÃO Usar (Armadilhas)

  • • Listas que o administrador do sistema pode cadastrar ou editar pelo painel (ex: categorias de produtos, cargos da empresa) — para isso, crie uma tabela separada com Foreign Key!

📐 Como Configurar no DrawDB Abrir DrawDB ↗

Type no DrawDB: ENUM
Size / Values: 'pendente', 'pago', 'enviado', 'cancelado'
Default sugerido: 'pendente'
Equivalência nos Dialetos do DrawDB:
MySQL / MariaDB ENUM('pendente', 'pago', 'cancelado') NOT NULL DEFAULT 'pendente'
PostgreSQL CREATE TYPE status_pedido AS ENUM (...) ou VARCHAR(20) CHECK (...)
SQLite TEXT CHECK(status IN ('pendente', 'pago', 'cancelado'))

📋 Exemplos Reais de Colunas com ENUM:

status
ENUM('pendente', 'pago', 'cancelado') NOT NULL DEFAULT 'pendente'
Ciclo de vida de um pedido
tipo_pessoa
ENUM('PF', 'PJ') NOT NULL
Pessoa Física ou Jurídica
🗄️ SQL DDL (Importar no DrawDB: Import > SQL)
CREATE TABLE pagamentos (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    pedido_id BIGINT UNSIGNED NOT NULL,
    metodo ENUM('pix', 'cartao_credito', 'boleto') NOT NULL,
    status ENUM('aguardando', 'aprovado', 'recusado', 'estornado') NOT NULL DEFAULT 'aguardando'
);
🐘 Como Validar e Salvar no PHP 8 + PDO
<?php
declare(strict_types=1);

// Usando Backed Enum nativo do PHP 8.1+ sincronizado com o banco!
enum StatusPagamento: string {
    case Aguardando = 'aguardando';
    case Aprovado   = 'aprovado';
    case Recusado   = 'recusado';
    case Estornado  = 'estornado';
}

// Validando uma string recebida via requisição com tryFrom()
$statusRecebido = StatusPagamento::tryFrom($_POST['status'] ?? 'aguardando');
if ($statusRecebido === null) {
    throw new ValueError('Status de pagamento inválido.');
}

$stmt = $pdo->prepare('
    INSERT INTO pagamentos (pedido_id, metodo, status)
    VALUES (:pedido_id, :metodo, :status)
');
$stmt->bindValue(':pedido_id', 1020, PDO::PARAM_INT);
$stmt->bindValue(':metodo', 'pix', PDO::PARAM_STR);
$stmt->bindValue(':status', $statusRecebido->value, PDO::PARAM_STR);
$stmt->execute();

9. Booleanos & Flags 1 tipo

Estados binários de verdadeiro/falso (ativo/inativo, pago/pendente, verificado/não verificado).

BOOLEAN / TINYINT(1) (Verdadeiro ou Falso)

⭐ Essencial 💾 1 Byte

Armazena estados binários Verdadeiro (1 / true) ou Falso (0 / false) como ativo, pago ou verificado.

No MySQL e MariaDB, a palavra-chave `BOOLEAN` (ou `BOOL`) é um apelido (alias) para `TINYINT(1)`, onde `0` representa `false` e `1` representa `true`. No PostgreSQL, `BOOLEAN` é um tipo dedicado que aceita `TRUE` e `FALSE`. Dica de modelagem: se você precisa saber QUANDO a flag virou verdadeira (ex: quando o e-mail foi verificado), em vez de `email_verificado BOOLEAN`, crie `email_verificado_em TIMESTAMP NULL`!

Limites de Capacidade: 0 (False) ou 1 (True) — mais NULL se a coluna for nullable

✅ Quando Usar (Casos Ideais)

  • • Flags de controle simples: `ativo`, `destaque_home`, `aceitou_termos`, `frete_gratis`.
  • • Configurações de liga/desliga.

🚫 Quando NÃO Usar (Armadilhas)

  • • Nunca use `VARCHAR(3)` escrevendo `"SIM"` e `"NAO"` ou `CHAR(1)` com `"S"` e `"N"`! Use sempre `BOOLEAN` / `TINYINT(1)` com `0` e `1`.

📐 Como Configurar no DrawDB Abrir DrawDB ↗

Type no DrawDB: BOOLEAN (ou TINYINT no MySQL)
Size / Values: 1 (se selecionar TINYINT) ou vazio (se selecionar BOOLEAN)
Default sugerido: 1 (true) ou 0 (false)
Equivalência nos Dialetos do DrawDB:
MySQL / MariaDB BOOLEAN NOT NULL DEFAULT 1 (ou TINYINT(1))
PostgreSQL BOOLEAN NOT NULL DEFAULT TRUE
SQLite INTEGER NOT NULL DEFAULT 1 (0 ou 1)

📋 Exemplos Reais de Colunas com BOOLEAN:

ativo
BOOLEAN NOT NULL DEFAULT 1
Indica se o cadastro está ativo no sistema
em_destaque
BOOLEAN NOT NULL DEFAULT 0
Exibir produto na vitrine principal
🗄️ SQL DDL (Importar no DrawDB: Import > SQL)
CREATE TABLE cupons_desconto (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    codigo VARCHAR(30) NOT NULL UNIQUE,
    ativo BOOLEAN NOT NULL DEFAULT 1,
    uso_unico BOOLEAN NOT NULL DEFAULT 0
);
🐘 Como Validar e Salvar no PHP 8 + PDO
<?php
declare(strict_types=1);

$ativo    = true;
$usoUnico = false;

// Nota importante no PDO + MySQL: converter bool para int (1 ou 0) com PDO::PARAM_INT
// evita incompatibilidades de driver em modo estrito do MySQL.
$stmt = $pdo->prepare('
    INSERT INTO cupons_desconto (codigo, ativo, uso_unico)
    VALUES (:codigo, :ativo, :uso_unico)
');
$stmt->bindValue(':codigo', 'BEMVINDO10', PDO::PARAM_STR);
$stmt->bindValue(':ativo', $ativo ? 1 : 0, PDO::PARAM_INT);
$stmt->bindValue(':uso_unico', $usoUnico ? 1 : 0, PDO::PARAM_INT);
$stmt->execute();

// Ao ler do banco no PHP, faça o cast explícito para (bool):
$cupomAtivo = (bool) 1; // true

10. Binários & Hashes Brutos 1 tipo

Armazenamento de bytes puros, chaves criptográficas, UUIDs compactados e arquivos (e quando usar Storage externo).

BINARY / VARBINARY / BLOB (Dados Binários)

💾 BINARY(16): 16 Bytes | BLOB: até 64 KB | MEDIUMBLOB: até 16 MB | LONGBLOB: até 4 GB

Armazena bytes brutos sem codificação de caracteres (chaves criptográficas, UUID binário, certificados).

`BINARY(n)` e `VARBINARY(n)` funcionam exatamente como `CHAR` e `VARCHAR`, mas guardam bytes puros em vez de caracteres de texto (sem collation). Já `BLOB` (Binary Large Object) guarda arquivos binários maiores. REGRA DE OURO DA ARQUITETURA: Evite salvar fotos de produtos, PDFs ou vídeos dentro de colunas `BLOB` no banco! Salve os arquivos no disco ou em Object Storage (Amazon S3, Cloudflare R2, MinIO) e grave apenas o caminho/URL em um `VARCHAR(255)` no banco.

Limites de Capacidade: Bytes puros sem conversão de charset

✅ Quando Usar (Casos Ideais)

  • • `BINARY(16)`: Armazenar UUID compactado em 16 bytes (`UUID_TO_BIN` no MySQL 8) ou endereços IPv4/IPv6 com `inet_pton()` do PHP (`VARBINARY(16)`).
  • • `VARBINARY` / `BLOB`: Certificados digitais A1 (`.pfx`) criptografados, chaves criptográficas pequenas ou assinaturas digitais.

🚫 Quando NÃO Usar (Armadilhas)

  • • Fotos de usuários, imagens de produtos, vídeos ou relatórios em PDF — eles inflam o backup do banco de dados e esgotam a memória RAM de cache (Buffer Pool). Use `VARCHAR(255)` com a URL do arquivo!

📐 Como Configurar no DrawDB Abrir DrawDB ↗

Type no DrawDB: VARBINARY ou BLOB
Size / Values: 16 (para IP ou UUID binário em VARBINARY)
Default sugerido: Vazio
Equivalência nos Dialetos do DrawDB:
MySQL / MariaDB BINARY(16), VARBINARY(255) ou BLOB
PostgreSQL BYTEA (ou tipo INET nativo para endereços IP)
SQLite BLOB

📋 Exemplos Reais de Colunas com VARBINARY:

ip_binario
VARBINARY(16) NOT NULL
Suporta tanto IPv4 (4 bytes) quanto IPv6 (16 bytes)
foto_perfil_url
VARCHAR(255) NULL
Alternativa recomendada ao BLOB para imagens!
🗄️ SQL DDL (Importar no DrawDB: Import > SQL)
CREATE TABLE sessoes_seguranca (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    usuario_id BIGINT UNSIGNED NOT NULL,
    ip_endereco VARBINARY(16) NOT NULL COMMENT 'IPv4 ou IPv6 compactado via inet_pton',
    token_hash BINARY(32) NOT NULL COMMENT 'SHA-256 binário puro de 32 bytes'
);
🐘 Como Validar e Salvar no PHP 8 + PDO
<?php
declare(strict_types=1);

// Armazenando endereço IP (IPv4 ou IPv6) em VARBINARY(16) via inet_pton() do PHP
$ipCliente = '2001:0db8:85a3:0000:0000:8a2e:0370:7334';
$ipBinario = inet_pton($ipCliente); // Converte para 16 bytes binários

// Hash SHA-256 binário puro (32 bytes em vez de 64 caracteres hexadecimais)
$tokenHashBinario = hash('sha256', 'token-secreto-do-usuario', true);

$stmt = $pdo->prepare('
    INSERT INTO sessoes_seguranca (usuario_id, ip_endereco, token_hash)
    VALUES (:usuario_id, :ip, :token)
');
$stmt->bindValue(':usuario_id', 1, PDO::PARAM_INT);
$stmt->bindValue(':ip', $ipBinario, PDO::PARAM_LOB);
$stmt->bindValue(':token', $tokenHashBinario, PDO::PARAM_LOB);
$stmt->execute();

// Para converter de volta dos bytes para string de IP legível no PHP:
$ipLegivel = inet_ntop($ipBinario);

11. Espaciais & Geográficos 1 tipo

Coordenadas de GPS (latitude/longitude), pontos no mapa, rotas e polígonos de áreas de entrega.

POINT / DECIMAL(10,8) (Coordenadas GPS)

💾 DECIMAL(10,8) + DECIMAL(11,8): 11 Bytes | POINT: 25 Bytes

Armazenamento de coordenadas geográficas (Latitude e Longitude) para mapas e cálculo de distância.

Você tem duas abordagens excelentes para guardar localização GPS: 1) Duas colunas separadas `latitude DECIMAL(10, 8)` e `longitude DECIMAL(11, 8)`, que é simples, universal e funciona em 100% dos bancos e ORMs; ou 2) O tipo espacial `POINT` com `SPATIAL INDEX`, ideal quando você precisa calcular raio de distância em tempo real (`ST_Distance_Sphere`) para apps de entrega.

Limites de Capacidade: Latitude: -90.00000000 a +90.00000000 | Longitude: -180.00000000 a +180.00000000

✅ Quando Usar (Casos Ideais)

  • • Endereços de lojas, restaurantes ou clientes para exibir pino no Google Maps / Leaflet (`DECIMAL(10,8)` e `DECIMAL(11,8)`).
  • • Busca geoespacial "encontrar lojas num raio de 5 km" usando `POINT` e `ST_Distance_Sphere`.

🚫 Quando NÃO Usar (Armadilhas)

  • • Não use `FLOAT` para coordenadas GPS precisas, pois perde precisão nas últimas casas decimais.

📐 Como Configurar no DrawDB Abrir DrawDB ↗

Type no DrawDB: DECIMAL (10,8 e 11,8) ou POINT
Size / Values: 10,8 (para latitude) e 11,8 (para longitude)
Default sugerido: NULL
Equivalência nos Dialetos do DrawDB:
MySQL / MariaDB DECIMAL(10, 8) / POINT SRID 4326
PostgreSQL NUMERIC(10, 8) / PostGIS GEOGRAPHY(POINT, 4326)
SQLite REAL / NUMERIC

📋 Exemplos Reais de Colunas com POINT:

latitude
DECIMAL(10, 8) NULL
Latitude GPS (ex: -23.56141400)
longitude
DECIMAL(11, 8) NULL
Longitude GPS (ex: -46.65588190)
🗄️ SQL DDL (Importar no DrawDB: Import > SQL)
CREATE TABLE filiais (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    nome VARCHAR(100) NOT NULL,
    latitude DECIMAL(10, 8) NOT NULL COMMENT 'De -90 a +90',
    longitude DECIMAL(11, 8) NOT NULL COMMENT 'De -180 a +180'
);
🐘 Como Validar e Salvar no PHP 8 + PDO
<?php
declare(strict_types=1);

$lat = -23.561414;
$lng = -46.655881;

if ($lat < -90 || $lat > 90 || $lng < -180 || $lng > 180) {
    throw new RangeException('Coordenadas GPS fora dos limites terrestres.');
}

$stmt = $pdo->prepare('
    INSERT INTO filiais (nome, latitude, longitude)
    VALUES (:nome, :lat, :lng)
');
$stmt->bindValue(':nome', 'Filial Avenida Paulista', PDO::PARAM_STR);
$stmt->bindValue(':lat', number_format($lat, 8, '.', ''), PDO::PARAM_STR);
$stmt->bindValue(':lng', number_format($lng, 8, '.', ''), PDO::PARAM_STR);
$stmt->execute();