Menu

Subqueries no SQLite: SELECT aninhado em WHERE e FROM

Aprenda a aninhar um SELECT dentro de outro no SQLite: subqueries escalares, IN, EXISTS, tabelas derivadas, subqueries correlacionadas e quando vale mais usar JOIN.

Esta página tem editores executáveis — edite, execute e veja a saída na hora.

Uma subquery é um SELECT dentro de outro SELECT

Uma subquery no SQLite é exatamente o que o nome sugere: um SELECT colocado dentro de outro comando, entre parênteses. O SQLite executa a consulta interna primeiro, pega o resultado e passa para a consulta externa.

Vamos montar um exemplo simples que vai servir de base pro resto:

Cinco pedidos, quatro clientes — sendo que dois deles ainda não fizeram compra alguma. Vamos usar essa base nos próximos exemplos.

Subquery no WHERE: filtrando por uma lista

Esse é o formato mais comum de subquery no SQLite: a consulta interna devolve uma lista de ids e a externa filtra com base nela.

A query interna devolve todo customer_id que aparece em orders. Já a query externa fica só com os clientes cujo id está nessa lista. Cleo, Boris e Ada aparecem; Dmitri (que não tem pedidos) fica de fora.

O padrão IN (SELECT ...) é o cavalo de batalha quando você quer "linhas de A que têm correspondência em B". Leia mentalmente como: "onde o valor dessa coluna é um dos valores retornados pela subquery".

NOT IN no SQLite: cuidado com os NULLs

A pergunta inversa — "quais clientes não fizeram pedidos?" — está a uma linha de distância:

Funciona bem aqui. Mas o NOT IN tem uma pegadinha clássica: se a subquery retornar um NULL que seja, o NOT IN inteiro vira NULL (que não é TRUE), e o resultado vem vazio. Surpresa silenciosa, daquelas chatas de debugar.

O hábito seguro ao usar NOT IN contra uma coluna que pode ter NULL:

Ou então use NOT EXISTS, que simplesmente não sofre desse problema. Já chegamos lá.

Subquery escalar: uma linha, uma coluna

Uma subquery escalar retorna um único valor — uma linha, uma coluna — e pode ser usada em qualquer lugar onde se espera um valor.

O SELECT MAX(total) FROM orders interno retorna 200. Em seguida, a consulta externa filtra os pedidos que batem com esse valor. Esse padrão é útil sempre que você precisa comparar com um agregado.

Você também pode usar uma subquery escalar na lista do SELECT para anexar um valor calculado a cada linha:

Cada linha de customers dispara a subquery interna uma vez, passando o customers.id como parâmetro. Isso é uma subquery correlacionada — já já falo mais sobre isso. Para casos do tipo "um número por linha" como esse, um LEFT JOIN com GROUP BY costuma ter desempenho melhor, mas a forma escalar fica linda de ler.

EXISTS no SQLite: só checar se existe alguma correspondência

O EXISTS é o primo mais discreto do IN. Ele não liga para os valores — só verifica se a subquery retorna qualquer linha. Por isso, é comum escrever SELECT 1 lá dentro, já que a coluna em si não importa.

Esse exemplo encontra clientes que fizeram pelo menos um pedido acima de 100. A query interna referencia c.id da query externa — é justamente isso que caracteriza uma subquery correlacionada. O SQLite para de varrer a tabela interna assim que encontra a primeira correspondência, e é por isso que o EXISTS costuma ter desempenho melhor que o IN quando a pergunta é "essa linha tem alguma linha relacionada?".

A versão negada, NOT EXISTS, é a forma segura contra NULL de perguntar "não existe linha relacionada":

Subquery no FROM: a tabela derivada

Uma subquery pode aparecer em qualquer lugar onde uma tabela apareceria — inclusive na cláusula FROM. Nesse caso, o select aninhado vira uma "tabela derivada" temporária e nomeada, que você pode usar em JOIN, filtrar ou agregar como qualquer outra tabela.

A consulta interna calcula o total por cliente. A externa tira a média desses totais por país. Agregações em dois estágios como essa são justamente o caso de uso das tabelas derivadas — quando não dá pra resolver tudo em um único GROUP BY.

O alias AS per_customer é obrigatório: toda tabela derivada precisa de um nome.

Subqueries correlacionadas: executadas a cada linha externa

Uma subquery é correlacionada quando faz referência a uma coluna da consulta externa. O SQLite precisa reavaliar a consulta interna para cada linha da externa, o que dá flexibilidade, mas pode sair caro.

Para cada cliente, queremos descobrir o maior pedido. Como a consulta interna depende de customers.id, ela é executada uma vez para cada cliente. Quem nunca fez pedido aparece com NULL — exatamente o comportamento esperado.

Subqueries correlacionadas caem como uma luva no padrão "para cada linha de A, calcule algo a partir de B". Se a tabela for pequena ou a busca estiver indexada, beleza. Mas em tabelas grandes sem índices que ajudem, faça profile antes de mandar pra produção — um JOIN com GROUP BY costuma ser mais rápido.

Subquery ou JOIN no SQLite: qual escolher?

As duas consultas a seguir respondem à mesma pergunta:

As duas formas devolvem as mesmas linhas. O otimizador do SQLite, inclusive, costuma reescrever uma na outra internamente. Escolha com base na legibilidade:

  • Use uma subquery quando você só precisa filtrar e não quer que as colunas da tabela interna apareçam no resultado.
  • Use um JOIN quando o resultado precisa de colunas das duas tabelas.
  • Use EXISTS quando a pergunta é "existe pelo menos uma linha relacionada?" — fica mais claro e evita as armadilhas de NULL que aparecem com IN/NOT IN.

Na dúvida, escreva a versão que se explica sozinha quando você lê em voz alta.

Uma cilada comum: subquery retornando várias linhas

Uma subquery usada com = precisa retornar no máximo uma linha. Se ela devolver mais de uma, o SQLite escolhe uma qualquer (basicamente ao acaso) e você fica com um resultado silenciosamente errado — sem nenhum erro.

Use IN quando a subquery interna puder retornar várias linhas:

Se você espera que volte exatamente uma linha e quer garantir isso, adicione LIMIT 1 junto com um ORDER BY — assim a escolha pelo menos fica determinística. Melhor ainda: escreva a query de um jeito que os próprios dados garantam uma única linha (filtrando por uma coluna única, por exemplo).

Próximo passo: Common Table Expressions

Subqueries dentro do FROM viram uma bagunça rapidinho — principalmente quando você precisa da mesma tabela derivada duas vezes, ou quando o aninhamento começa a ter três níveis de profundidade. As Common Table Expressions (WITH ... AS (...)) permitem dar um nome à subquery logo no início e reaproveitá-la pelo nome no restante do comando. É esse o assunto da próxima página.

Perguntas frequentes

O que é uma subquery no SQLite?

Uma subquery é um SELECT colocado dentro de outro comando, sempre entre parênteses. O SQLite executa a consulta interna primeiro e passa o resultado para a externa. Dá pra usar subqueries em WHERE, FROM, SELECT e em várias outras cláusulas.

Qual a diferença entre IN e EXISTS no SQLite?

IN (SELECT ...) verifica se um valor está presente entre as linhas retornadas pela subquery. Já EXISTS (SELECT ...) só checa se a subquery devolve alguma linha — não importa qual valor. Quando a consulta interna referencia a linha externa (ou seja, é correlacionada), EXISTS costuma performar melhor.

É melhor usar subquery ou JOIN no SQLite?

Use JOIN quando você precisa de colunas das duas tabelas no resultado final. Use subquery quando o objetivo é só filtrar ou calcular um valor único. Na prática, o otimizador do SQLite muitas vezes reescreve uma forma na outra, então escolha a versão que ficar mais legível.

O que é uma subquery correlacionada no SQLite?

É uma subquery que referencia uma coluna da consulta externa, ou seja, ela precisa ser reavaliada para cada linha de fora. São flexíveis, mas podem ficar lentas em tabelas grandes. Se uma subquery correlacionada virar gargalo, geralmente reescrever como JOIN ou CTE resolve.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR