# PostgreSQL — Lista de 60 Exercícios
> Objetivo: praticar o ecossistema criado no `postgres-labs` cobrindo DDL, DML e consultas variadas.
## Como entregar
- Crie um arquivo `respostas.sql` com as soluções numeradas (1 a 60) em comentários acima de cada query/comando.
- Quando a tarefa pedir *explicação curta*, escreva em comentário SQL (`--`).
- Para exercícios de `VIEW/MATERIALIZED VIEW`, coloque o `DROP ... IF EXISTS` antes, para facilitar a correção.
---
## A) Aquecimento: DDL/DML e Modelagem (1–10)
1) **Criar tabela** `comercio.cupons` com colunas: `codigo TEXT PK`, `percentual NUMERIC(5,2) CHECK (percentual BETWEEN 0 AND 100)`, `expira_em DATE`. Insira 3 cupons (um expirado ontem, dois válidos).
*Pergunta direta:* por que usar `NUMERIC` em vez de `REAL` para dinheiro? (responda em 1 linha)
2) **Adicionar coluna** `cupom_codigo TEXT` em `comercio.pedidos`, com FK opcional para `comercio.cupons(codigo)`; em `ON DELETE` ponha `SET NULL`.
3) **Criar tipo ENUM** `ufo.severidade` com valores `('BAIXA','MEDIA','ALTA')` e adicionar coluna `severidade ufo.severidade` em `ufo.avistamentos` com default `MEDIA`.
4) **Crie índice BTREE** em `comercio.pedidos(criado_em)` e explique em 1 frase quando BTREE é melhor que GIN.
5) **Inserir dados**: crie 5 `comercio.clientes` e 5 `comercio.pedidos` (misture `ABERTO/PAGO/ENVIADO`). Insira itens em 3 pedidos.
6) **UPDATE com regra**: aumente `preco` em 10% apenas dos `comercio.produtos` cuja `atributos->>'switch'` = `'brown'` **ou** que tenham tag `'gamer'`.
7) **DELETE seguro**: remova todos os `comercio.pagamentos` com `valor = 0`. Antes, mostre-os com um `SELECT` usando as mesmas condições (sem apagar ainda).
8) **ALTER TABLE**: torne `org.funcionarios.email` obrigatório (`NOT NULL`) criando a coluna se não existir; se já existir com nulos, corrija usando um email sintético `'<nome>@ex.com'` antes do `ALTER`.
9) **Pergunta direta:** qual a diferença entre `DELETE FROM` e `TRUNCATE`? Cite 2 diferenças práticas em 2 linhas.
10) **Gerar chaves**: ajuste `comercio.produtos` para usar `GENERATED ALWAYS AS IDENTITY` (se ainda estiver `SERIAL`). Explique em 1 linha a vantagem sobre `SERIAL`.
---
## B) Consultas básicas e filtros (11–20)
11) Liste `id, nome, preco` de `comercio.produtos` com `preco BETWEEN 100 AND 400` **e** (`nome ILIKE '%sql%'` **ou** tem tag `'office'`). Ordene por `preco DESC`, pegue só 5.
12) Liste clientes sem email (`IS NULL`). *Pergunta direta:* por que `WHERE email = NULL` não funciona? Reescreva corretamente.
13) Mostre `pedidos` com `status` em (`ABERTO`,`PAGO`) **e** `criado_em` nos últimos 60 dias. Mostre `id, status, criado_em`.
14) Busque `produtos` cujo `atributos` contenha **todas** as chaves `switch` **e** `layout`. Dica: operadores JSONB `?` e `?&`/`@>`.
15) Traga `produtos` sem nenhuma tag (array JSON vazio **ou** `NULL`).
16) Liste `itens_pedido` calculando `subtotal = quantidade*preco_unit`. Filtre apenas `subtotal >= 300`. Ordene por `subtotal DESC`.
17) *Pergunta direta:* explique `COALESCE` com um exemplo em 1 linha usando `cupom_codigo`.
18) Liste `produtos` cujo nome **não** contenha as palavras `('Mouse','Headset')` (case-insensitive).
19) Mostre os 10 clientes criados mais recentemente (considerando `created_at`).
20) Liste `funcionarios` do `org` contratados *antes* de 2024-01-01 com `skills` contendo `'java'` **ou** `'sql'` (ARRAY).
---
## C) JOINs e agregações (21–30)
21) Para cada `pedido`, traga `id, cliente, status, total_itens` (soma de `quantidade*preco_unit`). Inclua pedidos sem itens (soma 0).
22) Para cada `cliente`, traga `qtd_pedidos` e `valor_total` (soma global). Ordene por `valor_total DESC`. Mostre top 5.
23) Liste `produtos` e o total vendido de cada um. Mostre só os que venderam **algo** (> 0).
24) Descubra quais `categorias` **nunca** tiveram produtos vendidos (zero vendas).
25) Para cada `categoria`, mostre o **ticket médio por pedido** (use join via `produto_categorias`).
26) *Pergunta direta:* diferença entre `INNER JOIN` e `LEFT JOIN` em 1 linha com mini-exemplo.
27) Quantos pedidos com cupom válido (não expirado)? Considere `cupom_codigo` no pedido e compare com `cupons.expira_em`.
28) Média de preço por `tag` (explodindo `tags`). Mostre `tag` e `avg(preco)`.
29) Percentual de pedidos por `status` (use `count(*)` e proporção sobre o total). Arredonde para 2 casas.
30) Top 3 clientes por **valor total pago** (considere `comercio.pagamentos.valor` somados por pedido).
---
## D) Operadores de conjunto (31–35)
31) `UNION`: IDs de produtos que são `('Teclado Mecânico','Mouse Vertical')` **ou** têm a tag `'office'` — sem duplicar.
32) `UNION ALL`: repita o exercício anterior com `UNION ALL` e **conte** o total de linhas (mostre que duplica quando cai nas duas regras).
*Pergunta direta:* em 1 linha, diferença de `UNION` vs `UNION ALL`.
33) `INTERSECT`: SKUs que atendam **ambas** as condições: nome ILIKE `%sql%` **e** tenham tag `'estudo'`.
34) `EXCEPT`: produtos com tag `'gamer'` **exceto** os com `preco < 400`.
35) Combine `clientes` criados no último mês **UNION** clientes que fizeram pedidos acima de 1000 em qualquer data (IDs únicos).
---
## E) Subqueries e EXISTS (36–40)
36) Clientes **com** pedidos: use `EXISTS`. Liste `id, nome`.
37) Clientes **sem** pedidos: use `NOT EXISTS` (anti-join).
38) Produtos com preço **acima** da média de `comercio.produtos` (subquery escalar).
39) Para cada `pedido`, mostre `id` e um campo `tem_headset_gamer BOOLEAN` se **existe** item cujo produto tenha tag `'gamer'` **e** nome ILIKE `%headset%`.
40) *Pergunta direta:* cite 2 vantagens de `EXISTS/NOT EXISTS` sobre `IN/NOT IN` em subqueries correlacionadas (2 linhas).
---
## F) Datas e faixas (41–45)
41) Pedidos **entre** `now() - interval '90 days'` e `now()`. Inclua bordas.
42) Agrupe faturamento **mensal** (por `date_trunc('month', criado_em)`) mostrando `mes`, `valor_total`, `qtd_pedidos`.
43) Gere uma série de dias do último mês (`generate_series`) e faça `LEFT JOIN` com vendas diárias para mostrar **dias sem vendas** como zero.
44) *Pergunta direta:* `TIMESTAMP WITH TIME ZONE` vs `TIMESTAMP WITHOUT TIME ZONE` — quando escolher um ou outro? (2 linhas)
45) Extraia `dow` (dia da semana) e mostre qual dia tem maior número de pedidos (0–6).
---
## G) CTE (WITH) e janelamento (46–50)
46) Use `WITH` para calcular `somas_por_pedido` e depois agregue por `status` (soma total dos pedidos por status).
47) `WITH` para montar uma “tabela” de parâmetros (`limite NUMERIC := 500`) e reutilizar em duas consultas no mesmo bloco.
48) Window function: para cada `categoria`, calcule o **ranking** de produtos por quantidade vendida (`dense_rank()` desc). Mostre top 3 por categoria.
49) Window function: calcule a **média móvel** de 7 dias do faturamento diário.
50) *CTE recursiva simples*: gere números de 1 a 10 e una ao número de pedidos criados nesses 10 últimos dias (mapa `dia_n -> qtd_pedidos`).
---
## H) Arrays e JSONB (51–55)
51) Arrays: liste `funcionarios` que tenham **todas** as skills em `{'java','sql'}`. Dica: `@>` em arrays.
52) Arrays: exploda `skills` com `unnest` e conte quantos funcionários possuem cada skill (ranking desc).
53) JSONB: traga `produtos` cujo `atributos` contenha `{"surround": true}` e `{"layout":"abnt2"}` ao mesmo tempo. Use `@>`.
54) JSONB: produza colunas derivadas `dpi_int` (cast de `atributos->>'dpi'`) e `tem_rgb BOOLEAN` (`tags @> '["rgb"]'`). Filtre `dpi_int >= 8000 OR tem_rgb`.
55) *Pergunta direta:* JSON vs JSONB — cite 2 diferenças práticas, especialmente para indexação e performance (2 linhas).
---
## I) Views e Materialized Views (56–60)
56) VIEW atualizável `comercio.vw_pedidos_abertos` (somente `status='ABERTO'`) com `WITH CHECK OPTION`. Teste um `UPDATE` que viole a condição (deve falhar).
57) VIEW `comercio.vw_itens_enriquecidos` trazendo item + nome do produto + nome do cliente + status do pedido. (Somente SELECT; sem agregação.)
58) VIEW `comercio.vw_vendas_por_mes` produzindo `mes, valor_total, qtd_pedidos`. Valide com 2 queries usando meses diferentes.
59) MATERIALIZED VIEW `comercio.mv_top_produtos` com `id_produto, nome, qtd_vendida`. Crie **índice único** necessário para `REFRESH CONCURRENTLY`. Faça um `REFRESH` após inserir novos itens e comprove a mudança.
60) *Pergunta direta (views):* em 2 linhas, quando preferir **MV** em vez de **VIEW**? Cite um trade-off importante.
---
## Dicas gerais
- Para segurança de correção, prefixe todas as suas views/MVs com `DROP VIEW/MATERIALIZED VIEW IF EXISTS ...;`
- Em JSONB use índices GIN (`jsonb_path_ops` ou padrão). Em arrays, filtros com `@>` podem usar GIN em `TEXT[]`.
- `EXPLAIN ANALYZE` é seu amigo para comparar consulta direta vs MV.