SQL IN

A Loja Aurora vai fazer uma entrega especial nas cidades de Recife, Picos e Oeiras. Com o que você já sabe, a Ana escreveria cidade = 'Recife' OR cidade = 'Picos' OR cidade = 'Oeiras'. Funciona, mas e se fossem dez cidades? O IN ("em", em inglês: "a cidade está em esta lista?") faz a mesma pergunta de um jeito curto: cidade IN ('Recife', 'Picos', 'Oeiras'). Nesta aula você vai usá-lo, conhecer o seu contrário, o NOT IN, e ver de perto a armadilha mais famosa dele: um NULL dentro da lista.

Você vai aprender
  • Comparar uma coluna com uma lista de valores usando IN.
  • Que IN é um atalho para vários = unidos por OR.
  • O NOT IN: "não é nenhum destes".
  • A armadilha do NULL: por que um NOT IN com NULL na lista devolve zero linhas.
  • Uma prévia do IN com uma consulta dentro (subconsulta), onde a armadilha mais aparece.
  • Os erros mais comuns, com as mensagens reais do SQLite.

Os exemplos usam o banco da Loja Aurora, e cada quadro "Resultado" vem do SQLite de verdade, rodando no seu navegador. Antes desta aula, vale ter visto OR e NOT.


A ideia: a lista de convidados

Na porta de uma festa, o segurança tem uma lista de nomes. Para cada pessoa que chega, ele faz uma pergunta só: "o nome está na lista?". Ele não precisa saber quantos nomes há, nem a ordem deles. O IN é esse segurança: para cada linha da tabela, ele pergunta se o valor da coluna está na lista.

Com ORCom IN
cidade = 'Recife' OR cidade = 'Picos' OR cidade = 'Oeiras'cidade IN ('Recife', 'Picos', 'Oeiras')
Repete a coluna e o = a cada valorEscreve a coluna uma vez e os valores em fila

1. O primeiro IN

SELECT nome, cidade
FROM clientes
WHERE cidade IN ('Recife', 'Picos', 'Oeiras');
cidade
A coluna que vai ser comparada.
IN
"Em": a pergunta é "o valor está nesta lista?".
( … )
A lista vai entre parênteses.
'Recife', 'Picos', 'Oeiras'
Os valores, separados por vírgula. São textos, então cada um vai entre aspas simples.

Quatro clientes: dois de Recife, uma de Picos e um de Oeiras. Compare com a versão com OR, que dá exatamente as mesmas linhas:

SELECT nome, cidade
FROM clientes
WHERE cidade = 'Recife' OR cidade = 'Picos' OR cidade = 'Oeiras';

Uma coisa que o IN não faz: ordenar. Os clientes vieram na ordem da tabela, não na ordem da lista. Para ordenar, use ORDER BY.

Experimente: a consulta abaixo mostra só os clientes de Recife. Troque o = por um IN com as três cidades da entrega: Recife, Picos e Oeiras.

SELECT nome, cidade
FROM clientes
WHERE cidade = 'Recife';
SELECT nome, cidade
FROM clientes
WHERE cidade IN ('Recife', 'Picos', 'Oeiras');

2. IN com números

A lista pode ter números, sem aspas. Os produtos das categorias Livros (4) e Alimentos (7):

SELECT nome, categoria_id, preco
FROM produtos
WHERE categoria_id IN (4, 7);

Um uso muito comum: buscar linhas pelo id, quando você já sabe quais quer. Os dois produtos descontinuados e um dos que nunca foram vendidos:

SELECT id, nome
FROM produtos
WHERE id IN (12, 27, 43);

Algumas regras da lista, que valem para textos e números:

  • Um valor só também serve: estado IN ('PE') é o mesmo que estado = 'PE'.
  • Valores repetidos não atrapalham: estado IN ('PE', 'PE', 'PE') traz os mesmos 2 clientes de Pernambuco, sem repetir ninguém.
  • A ordem dos valores não importa.
  • Cada valor é comparado com =, então maiúsculas contam: cidade IN ('teresina', 'recife') não acha ninguém, porque está guardado "Teresina" e "Recife".
  • O IN não conhece curingas: estado IN ('P%') procura o texto "P%" de verdade e não acha nada. Para padrões, use LIKE.

3. NOT IN: "nenhum destes"

Com NOT antes do IN, a pergunta vira "o valor não está na lista?". Os clientes que não são do Piauí, do Ceará nem de São Paulo:

SELECT nome, estado
FROM clientes
WHERE estado NOT IN ('PI', 'CE', 'SP');

Sobraram os 2 clientes de Pernambuco. O NOT IN é o mesmo que vários <> unidos por AND: estado <> 'PI' AND estado <> 'CE' AND estado <> 'SP'. Repare: com o NOT, o OR virou AND, como na troca de De Morgan da aula NOT. Também dá para escrever NOT estado IN ('PI', 'CE', 'SP'), com o NOT na frente; o resultado é o mesmo.

Missão: as categorias 1, 2 e 3 são Papelaria, Eletrônicos e Casa e Cozinha. A consulta abaixo traz os produtos dessas categorias. Mude-a para trazer os produtos de todas as outras (são 26).

SELECT nome, categoria_id
FROM produtos
WHERE categoria_id IN (1, 2, 3);
SELECT nome, categoria_id
FROM produtos
WHERE categoria_id NOT IN (1, 2, 3);

4. A armadilha do NULL na lista

Este é o ponto mais importante da aula. A Ana está montando a lista de estados para excluir e, por descuido (ou porque a lista veio de uma planilha com uma célula vazia), entra um NULL no meio:

SELECT nome, estado
FROM clientes
WHERE estado NOT IN ('PI', 'CE', 'SP', NULL);

Zero linhas. Nenhum erro, nenhum aviso: os 2 clientes de Pernambuco simplesmente sumiram. E não importa quais estados estão na lista: com um NULL lá dentro, esse NOT IN nunca devolve linha nenhuma.

Por quê?

Lembre-se de que NOT IN é uma fila de <> unidos por AND. Para a Juliana, de Pernambuco, o SQLite faz estas contas:

PerguntaResposta
'PE' <> 'PI'verdadeiro
'PE' <> 'CE'verdadeiro
'PE' <> 'SP'verdadeiro
'PE' <> NULLNULL ("não sei": o PE é diferente de um valor desconhecido? Ninguém sabe)
verdadeiro AND verdadeiro AND verdadeiro AND NULLNULL

Um único "não sei" no AND deixa o resultado inteiro em "não sei", e o WHERE só deixa passar o que é verdadeiro. A documentação do SQLite traz essa regra numa tabela: se a lista contém NULL e o valor não foi encontrado nela, o resultado do IN e do NOT IN é NULL. Veja as contas direto:

SELECT 'PE' IN ('PI', 'CE'),
       'PE' NOT IN ('PI', 'CE'),
       'PE' IN ('PI', NULL),
       'PE' NOT IN ('PI', NULL),
       'PI' IN ('PI', NULL),
       'PI' NOT IN ('PI', NULL);
ExpressãoResultadoLeitura
'PE' IN ('PI', 'CE')0Não está na lista: falso.
'PE' NOT IN ('PI', 'CE')1Não está na lista: o NOT IN é verdadeiro.
'PE' IN ('PI', NULL)NULLNão achou o PE, mas talvez o valor desconhecido fosse PE: não sei.
'PE' NOT IN ('PI', NULL)NULLO mesmo "não sei". É aqui que as linhas somem.
'PI' IN ('PI', NULL)1Achou o PI na lista: verdadeiro, o NULL nem importa.
'PI' NOT IN ('PI', NULL)0Achou o PI: o NOT IN é falso.

Repare que, no IN sem NOT, o estrago é menor: estado IN ('PI', NULL) acha normalmente os 14 clientes do Piauí, porque, quando o valor é encontrado, a resposta é "sim" de qualquer jeito. O problema sério é o NOT IN: lá, o NULL transforma todo "não está na lista" em "não sei".

Regra de ouro

Antes de usar NOT IN, garanta que a lista não tem NULL. Numa lista escrita à mão, é só não escrever. Quando a lista vem de outra consulta (próxima seção), filtre os vazios dela.

E o NULL na coluna (não na lista)?

Outra situação, parecida: a lista está limpa, mas a coluna tem NULL. Os fornecedores que não são do Piauí:

SELECT nome, estado
FROM fornecedores
WHERE estado NOT IN ('PI');

Vieram 2, mas os fornecedores de Portugal e da Argentina, que têm estado NULL, também "não são do Piauí" e ficaram de fora. É a mesma armadilha da aula NOT: NULL NOT IN (…) é "não sei". Para incluí-los, acrescente OR estado IS NULL (aula IS NULL).


5. Prévia: IN com uma consulta dentro

A lista do IN não precisa ser escrita à mão: ela pode ser o resultado de outro SELECT, entre parênteses. Isso se chama subconsulta e tem aula própria (Subconsultas); aqui vai só uma prévia, porque é onde a armadilha do NULL mais aparece na vida real.

Os produtos que nunca foram vendidos são os que não aparecem em nenhum item de pedido:

SELECT id, nome
FROM produtos
WHERE id NOT IN (SELECT produto_id FROM itens_pedido);

Os produtos 43, 44 e 45. O SELECT de dentro devolve a lista de todos os produto_id vendidos, e o NOT IN fica com os que não estão nela. Funcionou porque produto_id nunca é NULL.

Agora a Ana quer os funcionários que não chefiam ninguém: os que não aparecem na coluna chefe_id de nenhum colega.

SELECT id, nome
FROM funcionarios
WHERE id NOT IN (SELECT chefe_id FROM funcionarios);

Zero linhas! Mas a loja tem 6 funcionários que não chefiam ninguém. O que aconteceu: a gerente, Marta Lustosa, não tem chefe, e o chefe_id dela é NULL. A lista que a subconsulta devolveu tem um NULL no meio, e caímos exatamente na armadilha da seção 4. O conserto é tirar os vazios da lista:

SELECT id, nome
FROM funcionarios
WHERE id NOT IN (SELECT chefe_id FROM funcionarios WHERE chefe_id IS NOT NULL);

Agora vieram os 6: Caio, Renata, Pedro, Igor, Sofia e Davi. Esse é um dos erros mais comuns em consultas de verdade, justamente porque não dá mensagem: a consulta "roda", só que vazia. Outro jeito de escrever, que não sofre com o NULL, é o NOT EXISTS (aula EXISTS).


6. Formas diferentes de escrever

EscritaObservação
cidade IN ('Recife', 'Picos')A forma curta. Boa a partir de dois valores.
cidade = 'Recife' OR cidade = 'Picos'O mesmo resultado; fica comprido com muitos valores.
estado NOT IN ('PI', 'CE')"Nenhum destes".
NOT estado IN ('PI', 'CE')O mesmo, com o NOT na frente.
estado <> 'PI' AND estado <> 'CE'O mesmo, por extenso.
id IN (SELECT …)A lista vem de outra consulta (Subconsultas).
SituaçãoQual usar
Um valor só=
Vários valores exatosIN (…)
Uma faixa (de 10 a 50)BETWEEN, não uma lista com todos os números
Um pedaço de textoLIKE (o IN não tem curingas)
"Nenhum destes", com lista escrita à mãoNOT IN (…), sem NULL na lista
"Nenhum destes", com a lista vinda de uma consultaNOT IN (SELECT … WHERE coluna IS NOT NULL) ou NOT EXISTS

Quando dá errado

Esqueceu os parênteses
SELECT nome FROM clientes WHERE estado IN 'PI', 'SP';

A mensagem: near ",": syntax error. Sem parênteses, o SQLite não reconhece a lista e tropeça na primeira vírgula.

Como consertar: a lista vai sempre entre parênteses: IN ('PI', 'SP').

Esqueceu uma vírgula
SELECT nome FROM clientes WHERE estado IN ('PI' 'SP');

A mensagem: near "'SP'": syntax error. Entre dois valores tem de haver uma vírgula; o SQLite chegou no 'SP' sem esperar outro valor ali.

Texto sem aspas, ou com aspas duplas
SELECT nome FROM clientes WHERE estado IN (PI, SP);

A mensagem: no such column: PI. Sem aspas, PI é lido como nome de coluna. Com aspas duplas, IN ("PI", "SP"), o SQLite dá a dica: no such column: "PI" - should this be a string literal in single-quotes? ("isto deveria ser um texto entre aspas simples?").

Como consertar: textos da lista entre aspas simples: IN ('PI', 'SP').

NOT IN com NULL: sem erro, sem linhas

O caso da seção 4: estado NOT IN ('PI', 'CE', 'SP', NULL) roda sem reclamar e devolve 0 linhas. Na subconsulta (seção 5), o mesmo acontece quando a coluna de dentro tem algum NULL. Desconfie de todo NOT IN que devolve vazio.

Maiúsculas e curingas não funcionam no IN

Também sem erro: cidade IN ('teresina') não acha "Teresina", e estado IN ('P%') não acha "PI". O IN compara com =: valor exato, letra por letra.

A lista vazia
SELECT nome FROM clientes WHERE estado IN ();

O que aconteceu: 0 linhas, sem erro. O SQLite aceita uma lista vazia: "estar numa lista vazia" é sempre falso (e NOT IN () é sempre verdadeiro). A documentação do SQLite avisa que a maioria dos outros bancos e o padrão SQL não aceitam isso: lá, uma lista vazia é erro de sintaxe. Isso costuma aparecer quando um programa monta a lista sozinho e ela sai sem nenhum item.

Nos outros bancos
  • O IN e o NOT IN, com lista ou com subconsulta, são padrão SQL e funcionam igual no MySQL, no PostgreSQL e no SQL Server, inclusive a armadilha do NULL: ela vem da própria lógica do SQL (verdadeiro, falso e "não sei"), não é coisa do SQLite.
  • A lista vazia IN () só é aceita no SQLite; no padrão SQL e na maioria dos bancos, a lista precisa ter pelo menos um valor.
  • Maiúsculas no IN seguem a regra de comparação de texto de cada banco: no MySQL, com a collation padrão, cidade IN ('teresina') acharia "Teresina".

Referência rápida

EscritaO que faz
coluna IN (a, b, c)O valor é a, b ou c (o mesmo que = a OR = b OR = c)
coluna NOT IN (a, b, c)Não é nenhum deles (o mesmo que <> a AND <> b AND <> c)
coluna IN ('x', 'y')Textos entre aspas simples
coluna IN (SELECT …)A lista vem de outra consulta
x NOT IN (…, NULL)Nunca é verdadeiro: devolve 0 linhas
NULL IN (…) / NULL NOT IN (…)NULL: linhas com a coluna vazia ficam de fora dos dois
IN ()Lista vazia: sempre falso (só o SQLite aceita)
Mensagem do SQLiteQuer dizer
near ",": syntax errorLista sem parênteses
near "'SP'": syntax errorFaltou uma vírgula entre os valores
no such column: PITexto sem aspas
no such column: "PI" - should this be a string literal in single-quotes?Texto entre aspas duplas

Exercícios

1. A palavra da lista

Complete para mostrar os produtos das categorias 5 e 6:

SELECT nome FROM produtos WHERE categoria_id (5, 6);

2. O contrário

Complete para mostrar os clientes que não são de Teresina nem de Fortaleza:

SELECT nome FROM clientes WHERE cidade IN ('Teresina', 'Fortaleza');

3. Qual escrita é igual a estado IN ('PI', 'CE')?

4. Quantas linhas WHERE estado NOT IN ('PI', 'CE', 'SP', NULL) devolve na tabela de clientes?

5. 'PI' IN ('PI', NULL) dá qual resultado?

6. Por que id NOT IN (SELECT chefe_id FROM funcionarios) devolve 0 linhas?

Missão final: o NULL que sumiu com tudo

Missão: a Ana copiou a lista de estados de uma planilha e veio junto uma célula vazia (o NULL). Por isso a consulta abaixo não traz nada. Conserte para aparecerem os 2 clientes de Pernambuco.

SELECT nome, cidade, estado
FROM clientes
WHERE estado NOT IN ('PI', 'CE', 'SP', NULL);
SELECT nome, cidade, estado
FROM clientes
WHERE estado NOT IN ('PI', 'CE', 'SP');

Resumo

  • IN ("em") pergunta se o valor está numa lista: cidade IN ('Recife', 'Picos', 'Oeiras'). É um atalho para vários = com OR.
  • A lista vai entre parênteses, com vírgulas; textos entre aspas simples, números sem aspas. Ordem e repetições não importam; maiúsculas importam; curingas não existem.
  • NOT IN é "nenhum destes": vários <> com AND.
  • Com NULL na lista, NOT IN nunca é verdadeiro: devolve 0 linhas, sem erro. Com NULL na coluna, a linha fica de fora do IN e do NOT IN.
  • A lista pode vir de uma subconsulta. Se a coluna de dentro pode ter NULL, filtre com WHERE … IS NOT NULL antes de usar NOT IN, ou use NOT EXISTS.
  • O SQLite aceita a lista vazia IN (); os outros bancos, não.