Subconsultas, conjuntos e divisão
EXISTS, IN, NULL, subconsultas escalares, máximos e perguntas universais em SQL.
Testar existência
EXISTSé verdadeiro com pelo menos uma linha; oSELECTinterior não interessa, usaSELECT 1.- Numa subconsulta correlacionada, conserva a ligação à linha exterior (
e.idCliente = c.id); sem ela, uma compra qualquer torna a condição verdadeira para todos. NOT EXISTSexprime ausência. “Há compra de outro cliente” não prova “este nunca comprou”.INtesta pertença sem multiplicar as linhas exteriores.
Com candidatos 1, 2, 3 e registos 1, NULL: NOT IN devolve vazio (para 2 e 3, 2 <> NULL é desconhecido e contamina a negação), enquanto NOT EXISTS correlacionado devolve 2 e 3. NOT IN só é seguro com interiores e exterior garantidamente não nulos; filtrar nulos interiores não resolve um exterior nulo. Evidência de dúvidas reais: programadores esperam linhas de NOT IN com NULL na lista e recebem vazio (Stack Overflow 1406215).
Valores únicos e tabelas derivadas
Uma subconsulta escalar ocupa o lugar de um valor: uma coluna e no máximo uma linha. Zero linhas dão NULL; várias linhas dão erro em PostgreSQL e a primeira linha em SQLite, que pode esconder um erro. Agrega ou ordena explicitamente em vez de depender disso.
Para todos os máximos com empates: precoCentimos = (SELECT MAX(precoCentimos) FROM Produto). A variante “não existe maior” também precisa de excluir o preço exterior NULL. Uma tabela derivada em FROM agrega primeiro por encomenda e só depois tira a média dos totais.
Conjuntos e quantificadores
UNION, INTERSECT e EXCEPT eliminam duplicados; UNION ALL conserva. Mesmo número de colunas, com significados e tipos compatíveis; SQLite não tem INTERSECT ALL nem EXCEPT ALL.
Para valores não nulos: x > ANY(S) equivale a existir s < x (falso com vazio); x > ALL(S) equivale a não existir s >= x (verdadeiro com vazio). SQLite não implementa ANY/ALL; trocar ALL por x > MAX(S) perde o caso vazio, porque MAX dá NULL.
O padrão “para todos”
“Comprou todos os requisitos” traduz-se em não existir requisito sem compra, com universo de candidatos declarado:
CREATE TABLE Exigido(idProduto INTEGER PRIMARY KEY REFERENCES Produto(id));
INSERT INTO Exigido VALUES (10), (11);
SELECT c.id, c.nome
FROM Cliente AS c
WHERE NOT EXISTS (
SELECT 1 FROM Exigido AS x
WHERE NOT EXISTS (
SELECT 1 FROM Encomenda AS e
JOIN Item AS i ON i.idEncomenda = e.id
WHERE e.idCliente = c.id AND i.idProduto = x.idProduto
)
)
ORDER BY c.id;PRAGMA foreign_keys = ON;
CREATE TABLE Cliente (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL,
email TEXT UNIQUE
);
CREATE TABLE Produto (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL,
precoCentimos INTEGER NOT NULL CHECK (precoCentimos >= 0),
stock INTEGER NOT NULL CHECK (stock >= 0)
);
CREATE TABLE Encomenda (
id INTEGER PRIMARY KEY,
data TEXT NOT NULL,
idCliente INTEGER NOT NULL REFERENCES Cliente(id)
);
CREATE TABLE Item (
idEncomenda INTEGER NOT NULL REFERENCES Encomenda(id) ON DELETE CASCADE,
idProduto INTEGER NOT NULL REFERENCES Produto(id),
qtd INTEGER NOT NULL CHECK (qtd > 0),
precoUnitario INTEGER NOT NULL CHECK (precoUnitario >= 0),
PRIMARY KEY (idEncomenda, idProduto)
);
INSERT INTO Cliente VALUES
(1, 'Ana', 'ana@example.test'),
(2, 'Rui', NULL),
(3, 'Mia', NULL);
INSERT INTO Produto VALUES
(10, 'Teclado', 4500, 20),
(11, 'Rato', 2500, 50),
(12, 'Monitor', 18000, 5);
INSERT INTO Encomenda VALUES
(100, '2026-01-05', 1),
(101, '2026-01-06', 1),
(102, '2026-01-06', 2);
INSERT INTO Item VALUES
(100, 10, 2, 4500),
(100, 11, 1, 2500),
(101, 12, 1, 18000),
(102, 11, 3, 2500);Executa a consulta para ver as tabelas.
Só Ana comprou 10 e 11; repetições não mudam existência. Com Exigido vazio, passam todos, incluindo Mia: verdade vacuamente satisfeita. Para exigir alguma compra, acrescenta outro EXISTS.
Por contagem, só vale contar distintos do conjunto exigido e conservar candidatos sem compras no caso vazio. Para totais por grupo (A supera B em todos os municípios), agrega primeiro por município/lista e procura um município sem comparação verdadeira: reprova empates, desconhecidos e resultados em falta. SUM ignora nulos e HAVING COUNT(votos)=COUNT(*) só valida registos presentes, não omissões.