1. RevisĂŁo: a estrutura estĂĄ criada â agora vamos popular
Na aula anterior, vocĂȘ aprendeu a modelar o domĂnio usando o Diagrama de Entidade-Relacionamento (DER) e a traduzir esse modelo em tabelas SQL com CREATE TABLE, chaves primĂĄrias, chaves estrangeiras e constraints. VocĂȘ criou a estrutura do banco â o esqueleto. Agora chegou a hora de dar vida a esse esqueleto: inserir dados, consultĂĄ-los, atualizĂĄ-los e eventualmente removĂȘ-los. Esse conjunto de quatro operaçÔes Ă© o que chamamos de CRUD â Create, Read, Update, Delete.
CRUD nĂŁo Ă© apenas uma sigla de mnemĂŽnico: Ă© a fundação de qualquer sistema que persiste dados. NĂŁo importa se vocĂȘ estĂĄ construindo um e-commerce, um sistema hospitalar ou um aplicativo de tarefas â em algum nĂvel, toda funcionalidade se reduz a uma combinação dessas quatro operaçÔes. Uma "matrĂcula de aluno" Ă© um INSERT em matriculas. Um "relatĂłrio de vendas" Ă© um SELECT com GROUP BY. Uma "atualização de endereço" Ă© um UPDATE com WHERE. Uma "exclusĂŁo de conta" Ă©, muitas vezes, nĂŁo um DELETE, mas um UPDATE que marca o registro como inativo â e entender essa nuance Ă© parte do que separa desenvolvedores experientes de iniciantes.
O ciclo de vida de um dado em um sistema tĂpico começa com a inserção (geralmente disparada por uma ação do usuĂĄrio: preencher um formulĂĄrio, finalizar uma compra, registrar uma presença). Em seguida, o dado Ă© lido repetidamente â em listagens, relatĂłrios, telas de detalhe. Com o tempo, pode ser atualizado (um pedido muda de status, um produto tem o preço corrigido). E eventualmente pode ser arquivado ou removido. Cada etapa desse ciclo exige que vocĂȘ escreva SQL correto, seguro e eficiente â e esse Ă© o tema central desta aula.
Ao longo desta aula, usaremos as tabelas clientes, produtos, pedidos e itens_pedido criadas na Aula 2. Se precisar, revise o DER e os CREATE TABLE antes de prosseguir â o SQL desta aula pressupĂ”e que essas tabelas existem no banco.
2. INSERT â Inserindo dados
O comando INSERT INTO Ă© a forma de adicionar novos registros a uma tabela. A sintaxe bĂĄsica exige que vocĂȘ especifique o nome da tabela, a lista de colunas que estĂĄ preenchendo e os valores correspondentes. Listar explicitamente as colunas Ă© uma boa prĂĄtica obrigatĂłria: se um dia vocĂȘ alterar a ordem das colunas na tabela ou adicionar uma nova coluna, um INSERT sem lista de colunas quebrarĂĄ silenciosamente â ou, pior, inserirĂĄ dados nas colunas erradas sem nenhuma mensagem de erro.
O PostgreSQL nĂŁo exige que vocĂȘ forneça valores para colunas com DEFAULT ou SERIAL (geradas automaticamente). Mas para colunas definidas com NOT NULL sem valor padrĂŁo, omiti-las causarĂĄ um erro imediato. Esse erro Ă© desejĂĄvel: o banco estĂĄ protegendo a integridade dos seus dados. A lição prĂĄtica Ă© simples â antes de escrever um INSERT, leia o CREATE TABLE e identifique quais colunas sĂŁo obrigatĂłrias.
Para inserçÔes que envolvem mĂșltiplas tabelas relacionadas â por exemplo, criar um pedido e seus itens ao mesmo tempo â a ordem importa. VocĂȘ deve inserir primeiro o registro pai (o pedido) antes dos registros filhos (os itens), porque a chave estrangeira pedido_id em itens_pedido deve referenciar um pedido que jĂĄ existe. Se tentar inserir os itens primeiro, o banco rejeitarĂĄ a operação com uma violação de chave estrangeira. Ă exatamente por isso que transaçÔes â que veremos adiante â sĂŁo essenciais em inserçÔes em cascata.
-- Inserindo um produto
INSERT INTO produtos (nome, preco, estoque)
VALUES ('Notebook', 2500.00, 10);
2.1 INSERT mĂșltiplo e RETURNING
O PostgreSQL permite inserir mĂșltiplas linhas em um Ășnico comando INSERT, separando cada conjunto de valores por vĂrgula. Isso Ă© significativamente mais eficiente do que enviar N comandos INSERT separados, pois reduz o nĂșmero de round-trips entre a aplicação e o banco â cada ida e volta pela rede tem um custo de latĂȘncia que se acumula. Em testes com 1000 registros, um INSERT em lote pode ser 10 a 50 vezes mais rĂĄpido que 1000 INSERTs individuais.
A clĂĄusula RETURNING Ă© uma extensĂŁo do PostgreSQL (nĂŁo existe no SQL padrĂŁo) que permite recuperar valores de colunas dos registros recĂ©m-inseridos sem precisar fazer um SELECT adicional. Isso Ă© especialmente Ăștil para obter o id gerado automaticamente por um SERIAL ou BIGSERIAL. Em vez de inserir e depois fazer SELECT MAX(id) (que Ă© inseguro em ambientes concorrentes), vocĂȘ usa RETURNING id e obtĂ©m o valor correto da sua inserção especĂfica, mesmo que outros clientes estejam inserindo registros simultaneamente.
Outra forma poderosa de inserção Ă© o INSERT INTO ... SELECT, que permite copiar ou transformar dados de uma tabela para outra em uma Ășnica operação. Essa tĂ©cnica Ă© Ăștil para migraçÔes de dados, criação de tabelas de histĂłrico, população de tabelas de cache e operaçÔes de ETL (Extract, Transform, Load). O SELECT pode incluir qualquer transformação que o banco suporte â funçÔes de string, cĂĄlculos, subqueries, JOINs â e todos os resultados serĂŁo inseridos de uma vez.
-- INSERT mĂșltiplo: mais eficiente que INSERTs separados
INSERT INTO produtos (nome, preco, estoque) VALUES
('Mouse', 45.90, 150),
('Teclado', 120.00, 80),
('Monitor', 890.00, 25);
-- RETURNING: obtém o id gerado sem um SELECT extra
INSERT INTO pedidos (cliente_id, total)
VALUES (1, 350.00)
RETURNING id, criado_em;
-- INSERT com SELECT: copia produtos descontinuados para histĂłrico
INSERT INTO produtos_historico (produto_id, nome, preco, arquivado_em)
SELECT id, nome, preco, NOW()
FROM produtos
WHERE descontinuado = TRUE;
Sempre liste as colunas explicitamente â nunca confie na ordem implĂcita da tabela.
Use INSERT em lote quando precisar inserir muitos registros de uma vez.
Use RETURNING para obter IDs gerados em vez de fazer SELECT MAX(id).
Envolva INSERTs em cascata em transaçÔes â se a inserção do item falhar, o pedido deve ser desfeito.
3. SELECT â Consultando dados
O SELECT Ă© o comando mais usado em SQL â e tambĂ©m o mais poderoso. Toda consulta começa com uma pergunta em linguagem natural ("quais produtos custam mais de R$100 e tĂȘm estoque disponĂvel?") e termina em SQL que o banco executa eficientemente. O processo de transformar perguntas de negĂłcio em SQL Ă© uma habilidade fundamental para desenvolvedores back-end.
O SELECT * (asterisco) retorna todas as colunas de uma tabela e Ă© conveniente para exploração durante o desenvolvimento. No entanto, em cĂłdigo de produção, prefira sempre listar as colunas que vocĂȘ realmente precisa. Isso reduz a quantidade de dados transferidos pelo banco, documenta o contrato de dados da sua query e evita surpresas quando alguĂ©m adiciona novas colunas Ă tabela. Um SELECT * em produção tambĂ©m dificulta o uso de Ăndices cobrindo (covering indexes), que sĂł sĂŁo usados quando o Ăndice contĂ©m todas as colunas que a query precisa.
Os aliases (apelidos) com AS permitem renomear colunas no resultado da query. Isso Ă© especialmente Ăștil quando o nome da coluna no banco Ă© tĂ©cnico (como dt_nasc) e vocĂȘ quer retornar algo mais legĂvel para a camada de aplicação (como data_nascimento). Aliases tambĂ©m sĂŁo usados em expressĂ”es calculadas â vocĂȘ nĂŁo pode fazer SELECT preco * 0.9 FROM produtos ORDER BY preco * 0.9; Ă© muito mais claro usar SELECT preco * 0.9 AS preco_com_desconto FROM produtos ORDER BY preco_com_desconto.
-- Listando colunas especĂficas com alias
SELECT
nome AS produto,
preco AS preco_original,
preco * 0.9 AS preco_com_desconto,
estoque AS quantidade_disponivel
FROM produtos
WHERE estoque > 0
ORDER BY preco_com_desconto DESC;
3.1 WHERE, BETWEEN, IN, LIKE, IS NULL
A clĂĄusula WHERE Ă© onde a maioria da lĂłgica de filtro acontece. VocĂȘ pode combinar condiçÔes com AND e OR, agrupando com parĂȘnteses quando necessĂĄrio para controlar a precedĂȘncia â AND tem precedĂȘncia sobre OR, assim como multiplicação tem sobre adição na matemĂĄtica. Uma query sem WHERE retorna todos os registros da tabela, o que pode ser intencional (em uma listagem completa) ou acidental (em um UPDATE, como veremos, isso Ă© catastrĂłfico).
O operador BETWEEN Ă© um atalho conveniente para coluna >= valor1 AND coluna <= valor2 â observe que Ă© inclusivo em ambos os lados. O operador IN permite comparar uma coluna com uma lista de valores, substituindo mĂșltiplos OR. O LIKE permite busca por padrĂŁo em strings: % representa zero ou mais caracteres e _ representa exatamente um caractere. Para buscas case-insensitive no PostgreSQL, use ILIKE em vez de LIKE.
O tratamento de valores nulos exige atenção especial. Em SQL, NULL representa a ausĂȘncia de valor â e qualquer comparação com NULL usando = ou != retorna NULL (nĂŁo TRUE, nĂŁo FALSE â NULL). Por isso, para verificar se um campo Ă© nulo, vocĂȘ deve usar IS NULL ou IS NOT NULL. Esquecer isso Ă© um bug clĂĄssico: WHERE descricao != NULL nunca retorna nada, pois a comparação Ă© NULL, nĂŁo TRUE.
-- BETWEEN: faixa de preço (inclusivo em ambos os lados)
SELECT nome, preco FROM produtos
WHERE preco BETWEEN 50 AND 500
AND estoque > 0
ORDER BY preco DESC
LIMIT 10;
-- IN: filtrar por lista de valores
SELECT nome, categoria FROM produtos
WHERE categoria IN ('eletrÎnicos', 'acessórios', 'periféricos');
-- ILIKE: busca case-insensitive por padrĂŁo
SELECT nome, email FROM clientes
WHERE nome ILIKE '%silva%';
-- IS NULL / IS NOT NULL: valores ausentes
SELECT nome, telefone FROM clientes
WHERE telefone IS NULL; -- clientes sem telefone cadastrado
SELECT nome, descricao FROM produtos
WHERE descricao IS NOT NULL; -- produtos que tĂȘm descrição
Em SQL, NULL = NULL retorna NULL, nĂŁo TRUE. Para verificar valores nulos, sempre use IS NULL ou IS NOT NULL. Usar = NULL Ă© um erro silencioso que nĂŁo filtra nada e nĂŁo gera nenhuma mensagem de erro â apenas retorna um conjunto vazio inesperado.
4. FunçÔes de agregação
As funçÔes de agregação calculam um valor Ășnico a partir de um conjunto de linhas. Elas sĂŁo fundamentais para relatĂłrios, dashboards e qualquer situação em que vocĂȘ precisa responder perguntas como "quantos?", "qual a soma?", "qual a mĂ©dia?", "qual o maior?" ou "qual o menor?". Sem funçÔes de agregação, vocĂȘ teria que buscar todos os dados para a aplicação e calcular tudo no cĂłdigo â o que Ă© ineficiente, escala mal e aumenta o trĂĄfego de rede desnecessariamente.
A distinção entre COUNT(*) e COUNT(coluna) Ă© importante e frequentemente confunde iniciantes. COUNT(*) conta o nĂșmero de linhas no conjunto de resultados, independente de qualquer valor ser NULL. COUNT(coluna) conta apenas as linhas onde o valor dessa coluna especĂfica nĂŁo Ă© NULL. Se vocĂȘ quer saber quantos clientes existem, use COUNT(*). Se quer saber quantos clientes tĂȘm telefone cadastrado (campo opcional), use COUNT(telefone). Essa diferença pode produzir resultados significativamente diferentes em tabelas com muitos valores nulos.
As funçÔes SUM, AVG, MIN e MAX tambĂ©m ignoram valores NULL automaticamente â o que geralmente Ă© o comportamento desejado. Uma exceção importante: se vocĂȘ usar AVG em uma coluna onde alguns valores sĂŁo NULL e outros sĂŁo zero, os zeros entram no cĂĄlculo mas os NULLs nĂŁo. Isso pode fazer a mĂ©dia parecer maior do que seria se NULL fosse tratado como zero. Se NULL foi usado erroneamente para representar "zero", vocĂȘ tem um problema de modelagem de dados que distorcerĂĄ qualquer agregação.
4.1 GROUP BY e HAVING
O GROUP BY divide os resultados em grupos com base nos valores de uma ou mais colunas, e as funçÔes de agregação sĂŁo calculadas para cada grupo separadamente. Por exemplo, para calcular o total de vendas por categoria de produto, vocĂȘ agrupa por categoria e soma os valores. Uma regra obrigatĂłria do SQL: toda coluna que aparece no SELECT e nĂŁo estĂĄ dentro de uma função de agregação deve aparecer no GROUP BY. Violar isso Ă© um erro em bancos como PostgreSQL (alguns outros bancos, como MySQL em modo permissivo, aceitam â mas produzem resultados imprevisĂveis).
O HAVING filtra grupos apĂłs a agregação, da mesma forma que o WHERE filtra linhas antes. A distinção Ă© crucial: WHERE age sobre as linhas individuais antes do agrupamento; HAVING age sobre os grupos resultantes depois do agrupamento. VocĂȘ nĂŁo pode usar funçÔes de agregação no WHERE â isso gerarĂĄ um erro. Se quiser filtrar por uma condição que envolve uma agregação (como "mostrar apenas categorias com mais de 5 produtos"), use HAVING.
-- Total e média de preço por categoria
SELECT
categoria,
COUNT(*) AS total_produtos,
AVG(preco) AS preco_medio,
SUM(preco * estoque) AS valor_em_estoque
FROM produtos
GROUP BY categoria
HAVING COUNT(*) > 5 -- sĂł categorias com mais de 5 produtos
ORDER BY valor_em_estoque DESC;
-- Clientes com mais de 3 pedidos
SELECT
c.nome,
COUNT(p.id) AS total_pedidos,
SUM(p.total) AS valor_total_compras
FROM clientes c
JOIN pedidos p ON p.cliente_id = c.id
GROUP BY c.id, c.nome
HAVING COUNT(p.id) > 3
ORDER BY valor_total_compras DESC;
| Clåusula | Quando age | Pode usar agregação? | Exemplo |
|---|---|---|---|
| WHERE | Antes do GROUP BY â filtra linhas | NĂŁo | WHERE preco > 100 |
| GROUP BY | Depois do WHERE â agrupa os grupos | â | GROUP BY categoria |
| HAVING | Depois do GROUP BY â filtra grupos | Sim | HAVING COUNT(*) > 5 |
| ORDER BY | Por Ășltimo â ordena o resultado | Sim | ORDER BY AVG(preco) DESC |
5. UPDATE â Atualizando dados
O comando UPDATE modifica o valor de uma ou mais colunas em registros existentes. Sua sintaxe Ă© aparentemente simples â UPDATE tabela SET coluna = valor WHERE condição â mas esconde um perigo que pode destruir dados de produção em segundos. O perigo Ă© a ausĂȘncia da clĂĄusula WHERE: um UPDATE sem WHERE atualiza todos os registros da tabela. Um UPDATE usuarios SET senha = 'abc123' sem WHERE nĂŁo atualiza um usuĂĄrio â ele atualiza todos os usuĂĄrios, substituindo todas as senhas. Esse tipo de acidente Ă© responsĂĄvel por incidentes de segurança e perdas de dados que custam horas ou dias de trabalho para recuperar.
A regra de ouro â praticada por desenvolvedores experientes â Ă© sempre verificar o WHERE com um SELECT antes de executar o UPDATE. Escreva o SELECT equivalente Ă sua atualização, execute-o, e confirme que os registros retornados sĂŁo exatamente os que vocĂȘ quer atualizar. SĂł entĂŁo substitua o SELECT pelo UPDATE. Essa prĂĄtica de dois passos Ă© especialmente importante quando vocĂȘ estĂĄ trabalhando diretamente em um banco de produção, onde nĂŁo hĂĄ como fazer Ctrl+Z.
Assim como o INSERT, o UPDATE suporta a clĂĄusula RETURNING no PostgreSQL, permitindo que vocĂȘ veja os valores atualizados sem precisar de um SELECT adicional. Isso Ă© particularmente Ăștil quando a atualização depende do valor anterior â por exemplo, incrementar um contador â e vocĂȘ precisa saber qual foi o valor resultante. O UPDATE tambĂ©m suporta subqueries no SET e no WHERE, permitindo atualizaçÔes complexas baseadas em dados de outras tabelas.
-- PASSO 1: verificar quais registros serĂŁo afetados
SELECT * FROM produtos WHERE categoria = 'eletrĂŽnicos';
-- PASSO 2: sĂł apĂłs confirmar, executar o UPDATE
UPDATE produtos
SET desconto = 0.10
WHERE categoria = 'eletrĂŽnicos';
-- UPDATE com RETURNING: ver o resultado imediatamente
UPDATE pedidos
SET status = 'confirmado', atualizado_em = NOW()
WHERE id = 42
RETURNING id, status, atualizado_em;
-- UPDATE com subquery: atualizar estoque com base em pedidos
UPDATE produtos p
SET estoque = p.estoque - (
SELECT SUM(i.quantidade)
FROM itens_pedido i
WHERE i.produto_id = p.id
AND i.pedido_id = 42
)
WHERE p.id IN (
SELECT produto_id FROM itens_pedido WHERE pedido_id = 42
);
Nunca execute um UPDATE sem WHERE em produção. Sempre valide os registros afetados com um SELECT antes. Em ambientes crĂticos, considere começar o UPDATE dentro de uma transação (BEGIN) e sĂł fazer COMMIT apĂłs confirmar que os dados estĂŁo corretos â assim vocĂȘ pode usar ROLLBACK se necessĂĄrio.
6. DELETE â Excluindo dados
O comando DELETE FROM remove registros de uma tabela de forma permanente. Como o UPDATE, o DELETE sem WHERE remove todos os registros da tabela â algo potencialmente catastrĂłfico. A mesma prĂĄtica de segurança se aplica: valide com um SELECT primeiro, execute o DELETE depois. E como o UPDATE, o DELETE tambĂ©m suporta RETURNING no PostgreSQL, o que pode ser Ăștil quando vocĂȘ precisa saber exatamente o que foi removido para fins de auditoria ou para desfazer a operação em outro sistema.
O DELETE com subquery permite remover registros baseados em condiçÔes de outras tabelas. Por exemplo, para remover logs antigos sem um Ăndice eficiente na coluna de data, vocĂȘ pode usar um subquery com LIMIT para deletar em lotes menores, evitando bloquear a tabela por tempo excessivo. Essa tĂ©cnica de "delete em lotes" Ă© uma boa prĂĄtica em tabelas de log e histĂłrico de grandes sistemas, onde deletar milhĂ”es de registros de uma vez pode degradar o desempenho do banco por minutos.
Em muitos sistemas de negĂłcio, a exclusĂŁo permanente de dados nĂŁo Ă© desejĂĄvel â nem do ponto de vista tĂ©cnico (um cliente que cancela pode voltar, um pedido cancelado pode precisar de auditoria) nem do ponto de vista legal (legislaçÔes como LGPD e GDPR tĂȘm regras sobre retenção e exclusĂŁo de dados). Por isso, o padrĂŁo de soft delete (exclusĂŁo suave) Ă© amplamente usado: em vez de deletar o registro, vocĂȘ adiciona uma coluna como deleted_at TIMESTAMP ou ativo BOOLEAN e marca o registro como inativo. O registro continua no banco, mas as queries normais o excluem do resultado com WHERE deleted_at IS NULL.
6.1 DELETE vs TRUNCATE vs DROP
Esses trĂȘs comandos tĂȘm efeitos muito diferentes e confundi-los Ă© um erro perigoso. O DELETE remove registros selecionados (ou todos, sem WHERE), registra cada remoção no log de transaçÔes e pode ser revertido com ROLLBACK dentro de uma transação. O TRUNCATE remove todos os registros de uma tabela de forma muito mais rĂĄpida que um DELETE sem WHERE, pois nĂŁo registra cada linha no log â ele simplesmente descarta as pĂĄginas de dados e reseta os contadores de SERIAL. E o DROP TABLE nĂŁo apenas remove os dados â ele remove a tabela inteira, incluindo sua definição, Ăndices e constraints.
-- DELETE com WHERE: remove registros especĂficos
DELETE FROM logs
WHERE criado_em < NOW() - INTERVAL '30 days';
-- DELETE com RETURNING: saber o que foi removido
DELETE FROM sessoes
WHERE expirado_em < NOW()
RETURNING usuario_id, criado_em;
-- TRUNCATE: remove TUDO rapidamente (sem WHERE!)
TRUNCATE logs;
TRUNCATE logs RESTART IDENTITY; -- tambĂ©m reseta sequĂȘncia SERIAL
-- Soft delete: desativar em vez de remover
UPDATE clientes
SET deleted_at = NOW()
WHERE id = 7;
-- Query que exclui registros "deletados"
SELECT * FROM clientes
WHERE deleted_at IS NULL; -- apenas ativos
| Comando | O que remove | TransacionĂĄvel? | Velocidade | Remove a estrutura? |
|---|---|---|---|---|
| DELETE | Linhas selecionadas (ou todas) | Sim â pode usar ROLLBACK | Lento em grandes volumes | NĂŁo |
| TRUNCATE | Todas as linhas | Sim no PostgreSQL | Muito rĂĄpido | NĂŁo |
| DROP TABLE | Tudo â dados e estrutura | NĂŁo Ă© recomendado | Imediato | Sim â tabela some |
Para implementar soft delete, adicione deleted_at TIMESTAMP DEFAULT NULL Ă tabela. Registros ativos tĂȘm deleted_at IS NULL. Registros excluĂdos tĂȘm um timestamp. Crie um Ăndice em deleted_at e inclua sempre WHERE deleted_at IS NULL nas suas queries de listagem. Isso preserva o histĂłrico, facilita auditoria e permite restaurar dados acidentalmente "excluĂdos".
7. Ăndices â Acelerando consultas
Um Ăndice Ă© uma estrutura de dados auxiliar que o banco mantĂ©m separadamente dos dados da tabela, com o objetivo de acelerar consultas. A analogia clĂĄssica Ă© o Ăndice remissivo de um livro: sem ele, para encontrar todas as pĂĄginas que mencionam "transação", vocĂȘ teria que ler o livro inteiro. Com o Ăndice, vocĂȘ vai diretamente Ă s pĂĄginas listadas. Da mesma forma, sem um Ăndice em cliente_id na tabela de pedidos, o banco precisa varrer todos os pedidos para encontrar os de um cliente especĂfico â uma operação chamada de sequential scan. Com o Ăndice, ele vai diretamente Ă s linhas relevantes.
O tipo de Ăndice padrĂŁo do PostgreSQL Ă© o B-tree (ĂĄrvore B balanceada). Internamente, o Ăndice organiza os valores indexados em uma estrutura de ĂĄrvore onde cada nĂł aponta para um subconjunto de valores. Buscar um valor começa na raiz e percorre a ĂĄrvore â tipicamente 3 a 4 nĂveis â atĂ© encontrar as linhas correspondentes. Isso reduz uma busca em uma tabela de 1 milhĂŁo de registros de 1.000.000 comparaçÔes para cerca de 20. B-trees funcionam para comparaçÔes de igualdade (=), ranges (BETWEEN, <, >) e ordenação (ORDER BY). Para buscas por texto com LIKE '%palavra%' (prefixo desconhecido), um B-tree nĂŁo ajuda â seria necessĂĄrio um Ăndice de texto completo (GIN/GiST).
O custo de um Ăndice nĂŁo Ă© apenas de espaço em disco (tipicamente 10-30% do tamanho da tabela). Cada INSERT, UPDATE e DELETE precisa tambĂ©m atualizar todos os Ăndices da tabela. Em tabelas com muitos Ăndices e alto volume de escrita, os Ăndices podem se tornar um gargalo. A regra prĂĄtica: crie Ăndices em colunas usadas frequentemente em WHERE, JOIN ON e ORDER BY. Evite indexar colunas com poucos valores distintos (como um campo booleano ativo), pois o banco muitas vezes preferirĂĄ um sequential scan nesses casos de qualquer jeito.
-- Ăndice simples em coluna de JOIN/WHERE frequente
CREATE INDEX idx_pedidos_cliente_id
ON pedidos (cliente_id);
-- Ăndice composto: Ăștil quando WHERE usa ambas as colunas
CREATE INDEX idx_pedidos_status_criado
ON pedidos (status, criado_em DESC);
-- Ăndice Ășnico: garante unicidade e acelera buscas
CREATE UNIQUE INDEX idx_clientes_email
ON clientes (email);
-- Ăndice parcial: sĂł indexa registros ativos (menor, mais rĂĄpido)
CREATE INDEX idx_produtos_ativos
ON produtos (categoria, preco)
WHERE deleted_at IS NULL;
7.1 EXPLAIN ANALYZE â Entendendo o que o banco faz
O comando EXPLAIN ANALYZE Ă© a ferramenta mais poderosa para diagnosticar problemas de desempenho em queries SQL. Enquanto EXPLAIN mostra o plano que o banco pretende executar (sem executar a query de fato), EXPLAIN ANALYZE executa a query e mostra o plano real com os tempos medidos em cada etapa. Isso permite comparar estimativas com a realidade, identificar onde o banco estĂĄ gastando mais tempo e confirmar se seus Ăndices estĂŁo sendo usados.
No plano de execução, vocĂȘ procura dois indicadores principais: Seq Scan (sequential scan â varrendo a tabela toda) versus Index Scan (usando um Ăndice). Um Seq Scan em uma tabela pequena (poucos milhares de registros) Ă© normal e esperado. Um Seq Scan em uma tabela com milhĂ”es de registros em uma coluna de filtro usada frequentemente Ă© um sinal claro de que um Ăndice estĂĄ faltando. ApĂłs criar o Ăndice, execute o EXPLAIN ANALYZE novamente e verifique se o plano mudou.
-- Analisa o plano de execução E executa a query
EXPLAIN ANALYZE
SELECT * FROM pedidos WHERE cliente_id = 42;
-- SaĂda tĂpica SEM Ăndice (ruim):
-- Seq Scan on pedidos (cost=0.00..245.00 rows=3 width=48)
-- Filter: (cliente_id = 42)
-- Rows Removed by Filter: 9997
-- Execution Time: 12.345 ms
-- SaĂda tĂpica COM Ăndice (bom):
-- Index Scan using idx_pedidos_cliente_id
-- Index Cond: (cliente_id = 42)
-- Execution Time: 0.112 ms
Crie Ăndices nas colunas que aparecem em WHERE, JOIN ON e ORDER BY em queries frequentes e de alto volume. Mas nĂŁo crie Ăndices "por precaução" â cada Ăndice tem um custo em escrita. Medir com EXPLAIN ANALYZE antes e depois Ă© a Ășnica forma de saber se um Ăndice estĂĄ ajudando.
8. TransaçÔes na pråtica
Uma transação Ă© um grupo de operaçÔes SQL que sĂŁo executadas como uma unidade atĂŽmica â ou todas tĂȘm sucesso, ou nenhuma tem efeito. As propriedades que garantem isso sĂŁo conhecidas pelo acrĂŽnimo ACID: Atomicidade (tudo ou nada), ConsistĂȘncia (o banco passa de um estado vĂĄlido para outro), Isolamento (transaçÔes concorrentes nĂŁo interferem entre si) e Durabilidade (uma vez confirmada, a transação persiste mesmo em caso de falha de hardware). Sem transaçÔes, um banco de dados relacional nĂŁo seria confiĂĄvel para aplicaçÔes que precisam de garantias de integridade.
O exemplo clĂĄssico de por que transaçÔes sĂŁo necessĂĄrias Ă© a transferĂȘncia bancĂĄria. Para transferir R$100 da conta A para a conta B, vocĂȘ precisa de dois UPDATEs: debitar R$100 da conta A e creditar R$100 na conta B. Se o sistema falhar apĂłs o primeiro UPDATE e antes do segundo â seja por queda de energia, bug na aplicação ou sobrecarga do servidor â o dinheiro desaparecerĂĄ da conta A sem aparecer na conta B. Com uma transação, esses dois UPDATEs sĂŁo atĂŽmicos: se qualquer um falhar, ambos sĂŁo desfeitos automaticamente. O banco nunca ficarĂĄ em um estado inconsistente.
O SAVEPOINT permite criar pontos de retorno dentro de uma transação, de forma que vocĂȘ possa fazer rollback parcial sem desfazer toda a transação. Isso Ă© Ăștil em operaçÔes complexas onde algumas etapas podem falhar de forma aceitĂĄvel. Por exemplo, em um processo de importação de dados, vocĂȘ pode usar um SAVEPOINT por registro importado â se um registro falhar (dados invĂĄlidos), vocĂȘ faz rollback ao SAVEPOINT daquele registro e continua com o prĂłximo, sem perder os registros jĂĄ importados com sucesso.
-- TransferĂȘncia bancĂĄria segura com transação
BEGIN;
SAVEPOINT antes_da_transferencia;
UPDATE contas SET saldo = saldo - 100.00 WHERE id = 1;
UPDATE contas SET saldo = saldo + 100.00 WHERE id = 2;
-- Verificar se nenhuma conta ficou negativa
SELECT id, saldo FROM contas WHERE id IN (1, 2);
-- Se tudo ok:
COMMIT;
-- Se algo deu errado:
-- ROLLBACK TO SAVEPOINT antes_da_transferencia;
-- ROLLBACK; (desfaz tudo desde o BEGIN)
Aprofundamento: o que acontece se o servidor cair no meio de uma transação?
▼O PostgreSQL usa um mecanismo chamado Write-Ahead Log (WAL) para garantir durabilidade. Antes de modificar os dados no disco, o banco escreve a intenção no log WAL. Se o servidor cair durante uma transação que ainda nĂŁo fez COMMIT, ao reiniciar, o banco verifica o WAL, descobre que havia uma transação incompleta e automaticamente faz o rollback. Apenas as transaçÔes que chegaram a fazer COMMIT â ou seja, que gravaram o registro de commit no WAL â sĂŁo recuperadas.
Isso significa que vocĂȘ nunca precisa se preocupar com "transação parcialmente gravada no disco". O banco garante que ou a transação toda estĂĄ lĂĄ, ou nĂŁo estĂĄ. Essa garantia Ă© implementada pelo WAL em conjunto com o sistema de checkpoints do PostgreSQL, que periodicamente sincroniza o estado do banco com o disco.
9. Diagramas de SequĂȘncia â visualizando o fluxo CRUD
AtĂ© aqui vocĂȘ viu o SQL â a linguagem que descreve o que queremos que o banco faça. Mas entre o seu comando e a linha modificada no disco hĂĄ uma cadeia de componentes em interação: a aplicação valida, o driver serializa, o banco abre uma transação, o lock manager protege a linha, o query planner escolhe um Ăndice, o WAL grava a intenção e sĂł entĂŁo o COMMIT torna a mudança visĂvel para outros. Quando algo dĂĄ errado â uma duplicidade de e-mail, um deadlock, um pedido com itens parciais â vocĂȘ precisa entender onde na cadeia o problema apareceu para diagnosticĂĄ-lo.
O Diagrama de SequĂȘncia UML Ă© a ferramenta canĂŽnica para visualizar essa cadeia. Diferentemente do diagrama de classes (estrutura) ou do diagrama de Casos de Uso (objetivos do ator), o diagrama de sequĂȘncia mostra linhas verticais de vida (uma por componente) e setas horizontais cronolĂłgicas (mensagens trocadas), com o tempo fluindo de cima para baixo. Blocos especiais â alt (alternativa), opt (opcional), loop (repetição), par (paralelo) â modelam decisĂ”es e iteração. Notas anexadas a participantes esclarecem invariantes e custos.
Nesta seção vocĂȘ vai ler seis diagramas que cobrem os fluxos CRUD que aprendemos nas seçÔes anteriores: o INSERT que pode violar UNIQUE, o SELECT que se beneficia (ou nĂŁo) de Ăndice, o UPDATE protegido por lock e versionamento MVCC, o DELETE que esbarra em FK, a transação multi-tabela com estoque e a transferĂȘncia bancĂĄria com SAVEPOINT. Cada diagrama Ă© seguido de uma leitura linha a linha, dos atores envolvidos, de uma armadilha comum e do takeaway prĂĄtico. Se um Ășnico conceito vocĂȘ levar desta seção, que seja: SQL Ă© declarativo, mas sua execução Ă© orquestrada â diagramas de sequĂȘncia sĂŁo o roteiro dessa orquestração.
đ Como ler um diagrama de sequĂȘncia
Linhas verticais = participantes (atores, sistemas, componentes). Setas â = mensagens sĂncronas (chama e espera resposta). Setas âą tracejadas = retorno. Tempo = de cima para baixo. Tudo que estĂĄ no mesmo "bloco horizontal" acontece em sequĂȘncia cronolĂłgica.
đ§© Blocos estruturais
alt = um e somente um dos caminhos serĂĄ percorrido (if/else). opt = caminho que pode ou nĂŁo ocorrer. loop = bloco repetido N vezes. Note over X,Y = anotação que cobre mĂșltiplos participantes. Combinados, esses blocos descrevem qualquer fluxo CRUD do mais simples ao mais complexo.
9.1 INSERT com RETURNING e violação de UNIQUE
O fluxo mais comum de criação: a API recebe dados do usuĂĄrio, valida no nĂvel de aplicação, abre uma transação implĂcita, executa o INSERT com RETURNING id para devolver o identificador gerado e confirma com COMMIT. O ponto delicado Ă© a violação de UNIQUE â quando o e-mail jĂĄ existe, o PostgreSQL retorna o SQLSTATE 23505 e a transação precisa de ROLLBACK explĂcito antes que a aplicação retorne 409 ao cliente. Note que o WAL grava a intenção antes de o COMMIT retornar OK: essa Ă© a base da Durabilidade do ACID.
23505 (unique) e 23503 (FK) explicitamente â devolver 500 para conflitos previsĂveis confunde o consumidor da API e esconde bugs.ROLLBACK apĂłs erro mantĂ©m a transação aberta no pool, "envenenando" a prĂłxima requisição que reusar a mesma conexĂŁo.9.2 SELECT â query planner, Ăndice B-tree e buffer cache
O SELECT esconde uma decisĂŁo crĂtica: ler a tabela inteira (Sequential Scan) ou usar um Ăndice (Index Scan). O query planner faz essa escolha consultando estatĂsticas (pg_stats) sobre cardinalidade, seletividade e distribuição. Para uma coluna com Ăndice e seletividade alta, o planner caminha pela ĂĄrvore B-tree em O(log n) atĂ© encontrar os ponteiros para as linhas, depois lĂȘ apenas essas linhas no heap. AlĂ©m disso, antes de tocar o disco, ele verifica se a pĂĄgina jĂĄ estĂĄ no buffer cache (RAM): cache hit custa microssegundos, cache miss exige I/O e pode custar milissegundos.
ANALYZE
Buffer Cache â pool de pĂĄginas em RAM (shared_buffers)
Ăndice B-tree â estrutura ordenada para busca em O(log n)
Heap â armazenamento fĂsico das linhas
EXPLAIN ANALYZE mostra exatamente esse fluxo â qual nĂł foi usado, quantas linhas voltaram, quanto cada etapa custou. Use sempre antes de criar um Ăndice "por intuição".status com 3 valores) frequentemente Ă© ignorado pelo planner â cria custo de escrita sem ganho de leitura.9.3 UPDATE â lock de linha, MVCC e visibilidade
O UPDATE no PostgreSQL nĂŁo sobrescreve a linha original â ele cria uma nova versĂŁo da tupla (MVCC: Multi-Version Concurrency Control) e marca a antiga como expirada. Enquanto sua transação estĂĄ aberta, outras transaçÔes ainda enxergam a versĂŁo antiga; sĂł apĂłs o COMMIT a nova versĂŁo se torna visĂvel. Para evitar que duas transaçÔes concorrentes alterem a mesma linha simultaneamente, o lock manager solicita um ROW EXCLUSIVE. Se a linha jĂĄ estiver travada, sua transação espera â e se houver dependĂȘncia circular entre dois locks, o detector de deadlock aborta uma das transaçÔes.
a fila de locks e aborta uma transação
se detectar dependĂȘncia circular else Lock concedido LM-->>DB: ROW EXCLUSIVE adquirido DB->>MVCC: Cria nova versĂŁo da tupla (xmin = txid atual) MVCC->>ROW: Marca versĂŁo antiga como expirada (xmax) Note over MVCC,ROW: Outras transaçÔes ainda
leem a versĂŁo antiga (snapshot isolation) DB-->>API: 1 linha afetada alt Aplicação confirma API->>DB: COMMIT DB->>LM: Libera ROW EXCLUSIVE Note over MVCC,ROW: Nova versĂŁo visĂvel para todos
a partir deste ponto else Erro detectado pela aplicação API->>DB: ROLLBACK DB->>MVCC: Descarta a nova versão DB->>LM: Libera ROW EXCLUSIVE end end
xmin/xmax
Linha â armazenada como mĂșltiplas versĂ”es enquanto hĂĄ transaçÔes abertas
VACUUM (auto ou manual) Ă© quem recupera esse espaço â atualizaçÔes em massa sem VACUUM levam Ă degradação chamada table bloat.UPDATE clientes SET status='ativo' sem WHERE atualiza todas as linhas â sempre rode primeiro como SELECT com o mesmo WHERE para validar o conjunto afetado.9.4 DELETE â FOREIGN KEY constraints (RESTRICT, CASCADE, SET NULL)
Deletar uma linha referenciada por outras tabelas Ă© o ponto onde a integridade referencial entra em ação. A clĂĄusula ON DELETE da chave estrangeira define o comportamento: RESTRICT (default) bloqueia a deleção se houver dependentes, CASCADE propaga a deleção em ĂĄrvore e SET NULL mantĂ©m os dependentes mas zera a referĂȘncia. Em sistemas reais, CASCADE em tabelas crĂticas (como pedidos) Ă© raramente desejĂĄvel â preferimos soft delete (marcar deleted_at) para preservar histĂłrico.
protege integridade else FK ON DELETE CASCADE DB->>IT: Verifica FK itens_pedido.pedido_id REFERENCES pedidos(id) IT-->>DB: 23 itens referenciam os 5 pedidos DB->>IT: DELETE em cascata (23 linhas) DB->>PE: DELETE em cascata (5 linhas) DB->>CL: DELETE da linha original (1 linha) DB-->>API: OK â 29 linhas afetadas no total Note over DB,IT: â ïž Dados removidos
permanentemente em 3 tabelas else FK ON DELETE SET NULL DB->>PE: UPDATE pedidos SET cliente_id = NULL WHERE cliente_id = 42 DB->>CL: DELETE da linha original DB-->>API: OK â pedidos preservados como ĂłrfĂŁos Note over DB,PE: Ătil para auditoria histĂłrica
quando o cliente Ă© excluĂdo por LGPD end
CREATE TABLE ou ALTER TABLE
ON DELETE CASCADE em tabelas com milhĂ”es de linhas pode disparar uma deleção que dura horas e segura locks â sempre prefira deleção em batch controlada pela aplicação.9.5 Transação multi-tabela: criar pedido com itens e baixar estoque
Este Ă© o fluxo "ordem + itens + estoque" â um padrĂŁo clĂĄssico em e-commerce. A operação envolve trĂȘs tabelas (pedidos, itens_pedido, produtos), mĂșltiplos INSERTs e UPDATEs e uma trava pessimista (SELECT ... FOR UPDATE) para evitar a venda concorrente do mesmo produto sem estoque. Se qualquer item falhar â estoque insuficiente, produto inexistente, erro de rede â o ROLLBACK desfaz a transação inteira, garantindo que o pedido nunca seja persistido pela metade. Esta Ă© a Atomicidade do ACID em ação.
nenhum dado persiste API-->>U: 422 Unprocessable Entity {erro: "sem estoque"} else Estoque suficiente API->>DB: INSERT INTO itens_pedido (pedido_id, produto_id, qtd, preco) DB->>ITM: Insere linha de detalhe API->>DB: UPDATE produtos SET estoque = estoque - $qtd WHERE id = $1 DB->>STK: Decrementa estoque (lock garante atomicidade) end end API->>DB: UPDATE pedidos SET total = (SELECT SUM(qtd*preco) FROM itens_pedido WHERE pedido_id = 100) WHERE id = 100 DB->>ORD: Atualiza total calculado API->>DB: COMMIT Note over DB,STK: Locks liberados
todas as 3 tabelas mudam atomicamente API-->>U: 201 Created {pedido_id: 100, total: 1234.56}
SELECT FOR UPDATE sem NOWAIT ou SKIP LOCKED pode bloquear requisiçÔes concorrentes indefinidamente â defina statement_timeout no pool de conexĂŁo como rede de segurança.9.6 SAVEPOINT â rollback parcial em transferĂȘncia bancĂĄria
O SAVEPOINT permite criar pontos de retorno dentro de uma transação aberta. Ă a ferramenta para implementar lĂłgica condicional nĂŁo-trivial: "tente debitar, se passar do limite faça rollback parcial e tente de outra fonte, mas mantenha o log de auditoria jĂĄ inserido". Diferente de aninhamento (que o PostgreSQL nĂŁo suporta diretamente para transaçÔes), o SAVEPOINT Ă© um marcador leve que pode ser revertido sem encerrar a transação. Este diagrama mostra a transferĂȘncia bancĂĄria clĂĄssica com validação de saldo negativo apĂłs os UPDATEs e trĂȘs caminhos possĂveis: COMMIT, ROLLBACK TO SAVEPOINT (parcial) e ROLLBACK total.
preserva o log de auditoria API->>DB: UPDATE contas SET saldo = saldo - 100 WHERE id = 1 DB->>CA: Debita R$ 100 (saldo: 350 â 250) API->>DB: UPDATE contas SET saldo = saldo + 100 WHERE id = 2 DB->>CB: Credita R$ 100 (saldo: 700 â 800) API->>DB: SELECT id, saldo FROM contas WHERE id IN (1, 2) DB-->>API: [{id:1, saldo:250}, {id:2, saldo:800}] alt Validação OK (sem saldo negativo) API->>DB: COMMIT Note over DB,CB: TransferĂȘncia efetivada
auditoria + UPDATEs persistidos else Saldo negativo detectado API->>DB: ROLLBACK TO SAVEPOINT antes_da_transferencia Note over DB,CA: Apenas os UPDATEs sĂŁo desfeitos
o INSERT na auditoria permanece pendente API->>DB: UPDATE auditoria SET status='falhou' WHERE id = currval('auditoria_id_seq') API->>DB: COMMIT Note over DB,LOG: Rastreia tentativa falha
para anĂĄlise de fraude else Erro inesperado (ex: rede caiu) API->>DB: ROLLBACK Note over DB,LOG: Desfaz TUDO desde o BEGIN
nem mesmo a auditoria persiste end
Na prĂłxima seção, RM-ODP, vamos enquadrar essas operaçÔes nos viewpoints Information (estrutura dos dados) e Computational (interfaces dos componentes). Diagramas de sequĂȘncia sĂŁo, na prĂĄtica, a materialização do viewpoint Computational â eles tornam visĂvel o contrato de mensagens entre componentes que vocĂȘ atĂ© agora viu apenas como SQL e cĂłdigo.
10. RM-ODP â Viewpoints Information e Computational
Na Aula 1, apresentamos as cinco visĂ”es do RM-ODP (Reference Model for Open Distributed Processing). Nesta aula, dois viewpoints se tornam especialmente relevantes: o Information e o Computational. Analisar as operaçÔes CRUD atravĂ©s dessas lentes nĂŁo Ă© um exercĂcio burocrĂĄtico â Ă© uma forma de pensar sobre o que o sistema faz com a informação e como essa informação Ă© acessada por diferentes componentes.
O viewpoint Information descreve como a informação Ă© estruturada, quais sĂŁo seus invariantes (regras que nunca podem ser violadas) e como ela evolui ao longo do tempo. No contexto do CRUD, cada tabela do banco Ă© um objeto de informação, e cada operação CRUD Ă© uma transformação desse objeto. Um INSERT cria uma nova instĂąncia do objeto. Um UPDATE transiciona o objeto de um estado para outro. Um DELETE (ou soft delete) marca o objeto como inativo. Os constraints do banco â NOT NULL, UNIQUE, CHECK, FOREIGN KEY â sĂŁo a forma de expressar os invariantes do viewpoint Information no SQL.
O viewpoint Computational descreve a decomposição funcional do sistema em componentes com interfaces bem definidas. As operaçÔes CRUD sĂŁo exatamente as interfaces que a camada de banco expĂ”e para a camada de aplicação. Quando um desenvolvedor back-end escreve um Model no padrĂŁo MVC, esse Model encapsula as operaçÔes CRUD de uma entidade â o ClienteModel.buscarPorId(id) nada mais Ă© que um SELECT com WHERE. O PedidoModel.criar(dados) Ă© um BEGIN + INSERT + INSERT (itens) + COMMIT. Pensar em CRUD como interfaces computacionais ajuda a projetar APIs mais coerentes e testĂĄveis.
A camada de informação (banco) e a camada computacional (cĂłdigo) se comunicam por SQL. Um erro de modelagem no viewpoint Information â como armazenar mĂșltiplos valores em uma coluna â força o viewpoint Computational a implementar lĂłgica de parsing que nĂŁo deveria existir. Quando vocĂȘ modela bem o banco, o cĂłdigo fica mais simples. Quando o banco estĂĄ mal modelado, a complexidade migra para o cĂłdigo.
11. RF, RN e RNF â Mapeando CRUD a requisitos
Toda operação CRUD que vocĂȘ implementa deve ser rastreĂĄvel a pelo menos um Requisito Funcional (RF) que justifica sua existĂȘncia. Se nenhum RF exige que o sistema exclua pedidos cancelados definitivamente, entĂŁo o DELETE nĂŁo deveria estar no cĂłdigo â e o soft delete provavelmente Ă© a implementação correta. Esse rastreamento nĂŁo Ă© burocracia: ele Ă© a diferença entre desenvolver funcionalidades que o sistema precisa e desenvolver funcionalidades que ninguĂ©m pediu.
As Regras de NegĂłcio (RN) se traduzem diretamente em validaçÔes e constraints no banco. "Um pedido nĂŁo pode ter total negativo" Ă© uma RN que se materializa como CHECK (total >= 0) na tabela de pedidos â e tambĂ©m como validação na camada de aplicação (lembre: validação no banco Ă© a Ășltima linha de defesa, mas a aplicação deve validar antes para dar mensagens de erro adequadas ao usuĂĄrio). "Um e-mail de cliente deve ser Ășnico" Ă© uma RN que vira um UNIQUE INDEX na coluna email.
Os Requisitos NĂŁo Funcionais (RNF) associados Ă s operaçÔes CRUD se concentram em trĂȘs eixos principais: Desempenho (as consultas respondem em menos de X ms sob Y usuĂĄrios simultĂąneos â endereçado por Ăndices, queries otimizadas e EXPLAIN ANALYZE), Confiabilidade (os dados nunca ficam em estado inconsistente â endereçado por transaçÔes, constraints e backups), e Segurança (dados sensĂveis como senhas, CPFs e informaçÔes de cartĂŁo nunca sĂŁo armazenados em texto puro nem expostos desnecessariamente nas respostas da API â endereçado por criptografia, hashing e seleção cuidadosa das colunas no SELECT).
| Tipo | Exemplo | Implementação SQL/DB |
|---|---|---|
| RF | O sistema permite cadastrar produtos com nome, preço e estoque | INSERT INTO produtos (nome, preco, estoque) VALUES (...) |
| RF | O sistema lista os pedidos de um cliente, do mais recente ao mais antigo | SELECT ... FROM pedidos WHERE cliente_id = ? ORDER BY criado_em DESC |
| RN | O estoque não pode ficar negativo após uma venda | CHECK (estoque >= 0) + validação no UPDATE de estoque |
| RN | Um pedido deve ter pelo menos um item | Validação na camada de aplicação antes do COMMIT |
| RNF | Desempenho: listagem de pedidos por cliente em menos de 50ms | CREATE INDEX idx_pedidos_cliente_id ON pedidos(cliente_id) |
| RNF | Confiabilidade: operaçÔes de compra nunca deixam o banco inconsistente | BEGIN/COMMIT/ROLLBACK em todos os fluxos de compra |
| RNF | Segurança: senhas nunca armazenadas em texto puro | Hashing (bcrypt) antes do INSERT; nunca SELECT senha FROM usuarios |
"O sistema deve ser rĂĄpido" nĂŁo Ă© um RNF â Ă© uma intenção vaga. Um RNF bem escrito tem eixo, mĂ©trica, carga e evidĂȘncia: "As consultas de listagem de pedidos por cliente devem responder em menos de 50ms para 95% das requisiçÔes com 500 usuĂĄrios simultĂąneos, medido com EXPLAIN ANALYZE e teste de carga com k6." SĂł assim vocĂȘ sabe quando o requisito foi atendido.
12. Checklist de Estudo
Clique nos itens Ă medida que vocĂȘ dominar cada conceito:
- ✓Consigo escrever um INSERT bĂĄsico listando explicitamente as colunas, sem omitir nenhuma NOT NULL.
- ✓Consigo inserir mĂșltiplas linhas em um Ășnico INSERT e usar RETURNING para obter o ID gerado.
- ✓Consigo escrever um SELECT com WHERE usando AND/OR, BETWEEN, IN, LIKE e IS NULL corretamente.
- ✓Consigo explicar a diferença entre COUNT(*) e COUNT(coluna) e quando cada um Ă© adequado.
- ✓Consigo usar GROUP BY com COUNT, SUM e AVG, e filtrar grupos com HAVING.
- ✓Consigo explicar por que WHERE e HAVING existem separados e quando usar cada um.
- ✓Consigo escrever um UPDATE seguro: verificar com SELECT antes, usar WHERE correto, e RETURNING.
- ✓Consigo explicar a diferença entre DELETE, TRUNCATE e DROP TABLE e quando cada um Ă© adequado.
- ✓Consigo descrever o padrĂŁo de soft delete e implementĂĄ-lo com a coluna deleted_at.
- ✓Consigo explicar como um Ăndice B-tree funciona e o tradeoff entre velocidade de leitura e escrita.
- ✓Consigo usar EXPLAIN ANALYZE para identificar se uma query estĂĄ fazendo Seq Scan desnecessĂĄrio.
- ✓Consigo escrever uma transação com BEGIN/COMMIT/ROLLBACK e explicar as propriedades ACID.
- ✓Consigo mapear as operaçÔes CRUD aos viewpoints Information e Computational do RM-ODP.
- ✓Consigo associar cada operação CRUD a RFs, RNs e RNFs com mĂ©tricas mensurĂĄveis.
ReferĂȘncias
- PostgreSQL Docs â INSERT â referĂȘncia completa do comando INSERT, incluindo RETURNING e INSERT ... SELECT
- PostgreSQL Docs â SELECT â referĂȘncia completa de todas as clĂĄusulas do SELECT
- PostgreSQL Docs â UPDATE â UPDATE com subqueries, FROM e RETURNING
- PostgreSQL Docs â DELETE â DELETE com USING e RETURNING
- PostgreSQL Docs â Indexes â tipos de Ăndice, Ăndices parciais, Ăndices cobrindo
- PostgreSQL Docs â EXPLAIN â como interpretar planos de execução
- PostgreSQL Docs â Transactions â tutorial de transaçÔes, SAVEPOINT e ROLLBACK
- Use The Index, Luke â guia prĂĄtico sobre Ăndices em bancos relacionais
- ISO/IEC 10746 â RM-ODP â especificação das cinco visĂ”es do sistema