DDL — Modelagem de Dados
Aula 5 de 8
CREATE TABLE
CREATE TABLE clientes (
id SERIAL PRIMARY KEY,
nome VARCHAR(100) NOT NULL,
email VARCHAR(200) UNIQUE NOT NULL,
cpf CHAR(11) UNIQUE,
data_nascimento DATE,
telefone VARCHAR(20),
endereco TEXT,
ativo BOOLEAN DEFAULT true,
data_cadastro TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
data_atualizacao TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Constraints
CREATE TABLE pedidos (
id SERIAL PRIMARY KEY,
cliente_id INTEGER NOT NULL,
codigo VARCHAR(20) NOT NULL,
status VARCHAR(20) DEFAULT 'pendente',
total DECIMAL(10,2) NOT NULL CHECK (total >= 0),
data_pedido TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- Foreign Key
CONSTRAINT fk_cliente FOREIGN KEY (cliente_id)
REFERENCES clientes(id)
ON DELETE RESTRICT
ON UPDATE CASCADE,
-- Unique composta
CONSTRAINT uk_cliente_codigo UNIQUE (cliente_id, codigo),
-- Check
CONSTRAINT chk_status CHECK (status IN ('pendente', 'pago', 'cancelado'))
);
-- ON DELETE options:
-- RESTRICT → impede deleção do pai se há filhos
-- CASCADE → deleta filhos automaticamente
-- SET NULL → seta FK como NULL
-- NO ACTION → similar RESTRICT (padrão)
ALTER TABLE
-- Adicionar coluna
ALTER TABLE clientes ADD COLUMN whatsapp VARCHAR(20);
-- Modificar tipo
ALTER TABLE clientes ALTER COLUMN telefone TYPE VARCHAR(30);
-- Adicionar constraint
ALTER TABLE pedidos ADD CONSTRAINT ck_valor_minimo CHECK (total >= 0);
-- Remover coluna
ALTER TABLE clientes DROP COLUMN endereco;
-- Renomear
ALTER TABLE clientes RENAME COLUMN whatsapp TO celular;
ALTER TABLE clientes RENAME TO clientes_ativos;
-- Default value
ALTER TABLE pedidos ALTER COLUMN status SET DEFAULT 'pendente';
-- NOT NULL
ALTER TABLE clientes ALTER COLUMN telefone SET NOT NULL;
INDEX — Performance
-- B-tree (padrão) — igualdade, range, ordenação
CREATE INDEX idx_pedidos_data ON pedidos (data_pedido);
-- Índice composto (ordem importa!)
CREATE INDEX idx_cliente_data ON pedidos (cliente_id, data_pedido DESC);
-- WHERE cliente_id = 1 ORDER BY data_pedido DESC usa este índice
-- Único
CREATE UNIQUE INDEX idx_cliente_email ON clientes (email);
-- Parcial
CREATE INDEX idx_pedidos_pendentes ON pedidos (data_pedido)
WHERE status = 'pendente';
-- Hash (igualdade apenas, PostgreSQL)
CREATE INDEX idx_cliente_hash ON clientes USING HASH (id);
-- GIN (arrays, JSONB, full-text)
CREATE INDEX idx_produtos_tags ON produtos USING GIN (tags);
CREATE INDEX idx_produtos_descricao ON produtos USING GIN (to_tsvector('portuguese', descricao));
-- BRIN (dados ordenados, grandes volumes)
CREATE INDEX idx_vendas_data_brin ON vendas USING BRIN (data_pedido);
Normalização
Formas Normais:
1NF: Cada coluna atômica, sem grupos repetidos
❌ telefones VARCHAR(200) → "119999,118888"
✅ tabela separada telefones
2NF: 1NF + colunas não-chave dependem da chave completa
❌ pedido_id, produto_id, produto_nome (produto_nome depende só de produto_id)
✅ tabela separada produtos
3NF: 2NF + colunas não dependem de outras colunas não-chave
❌ cliente_id, cidade, uf (uf depende de cidade)
✅ tabela separada cidades
BCNF: 3NF + toda dependência é chave candidata
Lab: E-commerce Schema
CREATE TABLE categorias (
id SERIAL PRIMARY KEY,
nome VARCHAR(50) NOT NULL,
categoria_pai_id INTEGER REFERENCES categorias(id),
ativo BOOLEAN DEFAULT true
);
CREATE TABLE produtos (
id SERIAL PRIMARY KEY,
nome VARCHAR(200) NOT NULL,
descricao TEXT,
preco_custo DECIMAL(10,2) NOT NULL,
preco_venda DECIMAL(10,2) NOT NULL CHECK (preco_venda >= preco_custo),
categoria_id INTEGER NOT NULL REFERENCES categorias(id),
estoque INTEGER DEFAULT 0 CHECK (estoque >= 0),
ativo BOOLEAN DEFAULT true,
criado_em TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_produtos_categoria ON produtos (categoria_id) WHERE ativo = true;
CREATE INDEX idx_produtos_busca ON produtos USING GIN (to_tsvector('portuguese', nome || ' ' || COALESCE(descricao, '')));
Normalização evita redundância. Índices aceleram consultas (mas custam escrita). Escolha o tipo de índice certo: B-tree (padrão), GIN (arrays/texto), BRIN (séries temporais).