Subconsultas, conjuntos e divisão

EXISTS, IN, NULL, subconsultas escalares, máximos e perguntas universais em SQL.

TLDR

Perguntar sobre esta página

ChatGPTClaudePerplexityGrokGeminiCopiar e abrirDeepSeekCopiar e abrir

Testar existência

  • EXISTS é verdadeiro com pelo menos uma linha; o SELECT interior não interessa, usa SELECT 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 EXISTS exprime ausência. “Há compra de outro cliente” não prova “este nunca comprou”.
  • IN testa 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 SS vazio); x > ALL(S) equivale a não existir s >= x (verdadeiro com SS 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:

Ficheiros do exemplo
Ausência de um produto exigido em falta
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);
Resultado

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.

Quantificação universal e casos limite.

À tua maneira

Aparência

Modo de cor
Tema
Cor de destaque

Leitura

Fonte de leitura

Álgebra, lógica e uma ideia de cada vez.

Código

Fonte do código

Navegação

Cursor de leitura, sem edição. h, j, k, l movem; / pesquisa nesta página. v seleciona caracteres; V seleciona linhas; y copia. Tab permite destacar ou comentar. Espaço ou Enter abre blocos recolhíveis. Nos cartões, h, j, k, l mudam o foco.

CSS personalizado

Ativa uma sugestão ou guarda o teu CSS. Só muda este navegador.

Usa --page, --text e --accent para acompanhar o tema.

Seletores e recuperação

.site-header, .prose, .course-card, [data-reading-history], [data-playground] e .page-actions.

Abre a aparência com a tecla vírgula ou no rodapé. Se o CSS esconder os controlos, abre esta página sem CSS personalizado.

Pesquisar

Escreve para pesquisar em todo o site.

para escolher · Enter para abrir · Esc para fechar

Copiar fórmula

A cópia automática não está disponível. Copia o LaTeX abaixo.

Pesquisar nesta página

Enter procura · n / N repetem · Esc cancela. Ctrl / Cmd K pesquisa todo o site.

Atalhos de teclado

Clica numa tecla para a mudar. Esc cancela. Backspace desativa.

PesquisarCtrl / Cmd K

Os atalhos não interferem enquanto escreves. Tab e Enter funcionam sempre.