# Consultas SQL

SELECT, junções, agregação e subconsultas sobre a loja.

Página: https://resumos.rgo.pt/cadeiras/bd/sql-consultas/

A DML de leitura é o `SELECT`, e a sua estrutura segue a álgebra relacional: filtra linhas (`WHERE`), combina tabelas (`JOIN`), agrupa (`GROUP BY`), filtra grupos (`HAVING`) e projeta colunas (`SELECT`). A ordem de escrita é fixa, `SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY`, mesmo quando só usas metade das cláusulas.

A ordem lógica em figura, do `WHERE` ao `ORDER BY`:

![Ordem lógica de uma consulta: WHERE filtra linhas, JOIN combina tabelas, GROUP BY agrupa, HAVING filtra grupos, SELECT projeta colunas e ORDER BY ordena.](https://resumos.rgo.pt/cadeiras/bd/sql-consultas/figura-1.svg)

## Filtrar e ordenar

“Produtos com preço acima de 30, do mais caro ao mais barato”:

```
SELECT nome, preco FROM Produto
WHERE preco > 30
ORDER BY preco DESC;
```

Resultado: Monitor 180.0, Teclado 45.0. O `WHERE` é a seleção $\sigma$, a lista do `SELECT` é a projeção $\pi$. Repara que o Rato ficou de fora pelo filtro e a ordem é decrescente pelo `DESC`.

## Juntar e agregar

“Total gasto por cada cliente, incluindo quem nunca comprou”:

```
SELECT Cliente.nome, COALESCE(SUM(Item.qtd * Produto.preco), 0) AS total
FROM Cliente
LEFT JOIN Encomenda ON Encomenda.idCliente = Cliente.id
LEFT JOIN Item ON Item.idEncomenda = Encomenda.id
LEFT JOIN Produto ON Produto.id = Item.idProduto
GROUP BY Cliente.nome
ORDER BY total DESC;
```

O `LEFT JOIN` mantém os clientes sem encomendas, onde a soma seria nula e o `COALESCE` a converte em 0. Contas: Ana, 2 Teclados (90) mais 1 Rato (25) mais 1 Monitor (180), total 295; Rui, 3 Ratos, total 75; Mia, sem linhas, total 0.

Corre a consulta sobre a loja completa e confirma os três totais:

```sql
PRAGMA foreign_keys = ON;
CREATE TABLE Cliente(id INTEGER PRIMARY KEY, nome TEXT NOT NULL, email TEXT UNIQUE NOT NULL);
CREATE TABLE Produto(id INTEGER PRIMARY KEY, nome TEXT NOT NULL, preco REAL NOT NULL, stock INTEGER NOT NULL);
CREATE TABLE Encomenda(id INTEGER PRIMARY KEY, data TEXT NOT NULL, idCliente INTEGER NOT NULL REFERENCES Cliente(id));
CREATE TABLE Item(idEncomenda INTEGER REFERENCES Encomenda(id), idProduto INTEGER REFERENCES Produto(id), qtd INTEGER NOT NULL, PRIMARY KEY (idEncomenda, idProduto));
INSERT INTO Cliente VALUES (1, 'Ana', 'ana@mail'), (2, 'Rui', 'rui@mail'), (3, 'Mia', 'mia@mail');
INSERT INTO Produto VALUES (10, 'Teclado', 45.0, 20), (11, 'Rato', 25.0, 50), (12, 'Monitor', 180.0, 5);
INSERT INTO Encomenda VALUES (100, '2026-01-05', 1), (101, '2026-01-12', 1), (102, '2026-01-08', 2);
INSERT INTO Item VALUES (100, 10, 2), (100, 11, 1), (101, 12, 1), (102, 11, 3);
SELECT Cliente.nome, COALESCE(SUM(Item.qtd * Produto.preco), 0) AS total FROM Cliente LEFT JOIN Encomenda ON Encomenda.idCliente = Cliente.id LEFT JOIN Item ON Item.idEncomenda = Encomenda.id LEFT JOIN Produto ON Produto.id = Item.idProduto GROUP BY Cliente.nome ORDER BY total DESC;
```

O resultado é `Ana|295.0`, `Rui|75.0`, `Mia|0`: os separadores verticais são o formato de lista do SQLite, e a Mia aparece com 0 graças ao `COALESCE`.

Duas armadilhas habituais: agregar sem `GROUP BY` quando há colunas soltas, e filtrar grupos com `WHERE` em vez de `HAVING`. `WHERE` filtra linhas antes de agrupar; `HAVING` filtra grupos depois. “Clientes com total acima de 100” acrescenta `HAVING total > 100` e devolve só a Ana.

## Subconsultas

“Clientes que compraram o Monitor”, com uma subconsulta que primeiro descobre quem:

```
SELECT nome FROM Cliente
WHERE id IN (
  SELECT Encomenda.idCliente FROM Encomenda
  JOIN Item ON Item.idEncomenda = Encomenda.id
  JOIN Produto ON Produto.id = Item.idProduto
  WHERE Produto.nome = 'Monitor'
);
```

A consulta interior devolve o cliente 1 (a encomenda 101 leva o produto 12) e a exterior traduz para `Ana`. Sempre que a pergunta tem um “os X que …” com condição noutra tabela, uma subconsulta com `IN` é a formulação direta.

A mesma pergunta em três formulações, para comparares:

A subconsulta devolve os ids e o `IN` testa pertença. É a leitura mais direta do “clientes que …”.

```
SELECT nome FROM Cliente
WHERE id IN (
  SELECT Encomenda.idCliente FROM Encomenda
  JOIN Item ON Item.idEncomenda = Encomenda.id
  JOIN Produto ON Produto.id = Item.idProduto
  WHERE Produto.nome = 'Monitor'
);
```

O `EXISTS` testa linha a linha se há prova da compra, correlacionando a interior com a exterior pelo cliente. Costuma ser a mais rápida com índices.

```
SELECT nome FROM Cliente c
WHERE EXISTS (
  SELECT 1 FROM Encomenda e
  JOIN Item i ON i.idEncomenda = e.id
  JOIN Produto p ON p.id = i.idProduto
  WHERE e.idCliente = c.id AND p.nome = 'Monitor'
);
```

A junção direta seguida de `DISTINCT` elimina os duplicados (um cliente com duas compras do Monitor apareceria duas vezes). Prefere-a quando também queres colunas das outras tabelas.

```
SELECT DISTINCT c.nome FROM Cliente c
JOIN Encomenda e ON e.idCliente = c.id
JOIN Item i ON i.idEncomenda = e.id
JOIN Produto p ON p.id = i.idProduto
WHERE p.nome = 'Monitor';
```

O erro mais comum

Juntar sem condição de junção e obter o produto cartesiano: cada cliente repetido por cada encomenda de todos. Se o resultado tem linhas a mais e valores absurdos, falta o `ON` ou ele compara as colunas erradas. Confere sempre que cada `JOIN` tem o seu `ON` com PK de um lado e FK do outro.

## Para ver em vídeo

[Vídeo: SQL Tutorial - Full Database Course for Beginners](https://www.youtube.com/watch?v=HXV3zeQKqGY)

A miniatura vem do YouTube. O vídeo só carrega quando clicas. [Abrir no YouTube](https://www.youtube.com/watch?v=HXV3zeQKqGY)

[Vídeo: 6 SQL Joins you MUST know! (Animated + Practice)](https://www.youtube.com/watch?v=9prkapPewGs)

A miniatura vem do YouTube. O vídeo só carrega quando clicas. [Abrir no YouTube](https://www.youtube.com/watch?v=9prkapPewGs)

## Para saber mais

*   Gramática do `SELECT`, junções e agregação na documentação oficial: [sqlite.org/lang\_select.html](https://sqlite.org/lang_select.html).
