🇯🇵 日本語 | 🇺🇸 English | 🇪🇸 Español | 🇵🇹 Português | 🇹🇭 ไทย | 🇨🇳 中文

【Tutorial da Cláusula IN do SQL】Lide com Múltiplas Condições de Forma Inteligente! Uma Maneira Melhor de Escrever Cláusulas WHERE com IN e NOT IN

Ao desenvolver sites, você frequentemente encontrará situações em que precisa encontrar dados que correspondam a uma de várias opções, como "exibir uma lista de produtos de categorias específicas" ou "enviar uma notificação de campanha apenas para usuários em São Paulo, Rio de Janeiro e Minas Gerais".

Nesses casos, como você escreveria a cláusula WHERE?

WHERE category = 'Papelaria' OR category = 'Eletrodomésticos' OR category = 'Alimentos'

Claro, isso funciona corretamente. Mas e se o número de opções aumentar para cinco ou dez? O código se tornaria uma longa cadeia de ORs, difícil de ler e de manter. O herói que resolve esse problema em um instante é o nosso tópico principal de hoje: a cláusula IN.

Este artigo explicará detalhadamente tudo, desde o uso básico da cláusula IN e sua oposta, a cláusula NOT IN, até técnicas avançadas usando subconsultas, e até mesmo a "armadilha do NULL" na qual muitas pessoas caem, tudo com código que você pode copiar e colar. Vamos dominar a cláusula IN e evoluir seu SQL para ser mais inteligente e legível!


Preparação: Vamos Preparar Nossos Dados de Produtos

Para experimentar as operações, primeiro precisamos de alguns dados. Desta vez, vamos criar uma tabela simples de products para gerenciar uma lista de produtos e uma tabela de suppliers para gerenciar informações sobre os fornecedores. Ter várias categorias e fornecedores tornará mais fácil apreciar a conveniência da cláusula IN.

-- Se as tabelas existirem, exclua-as (para testes repetíveis)
DROP TABLE IF EXISTS products;
DROP TABLE IF EXISTS suppliers;

-- Criar tabela de fornecedores
CREATE TABLE suppliers (
  id INTEGER PRIMARY KEY,
  supplier_name TEXT NOT NULL,
  region TEXT NOT NULL -- 'Sudeste', 'Sul'
);

-- Criar tabela de produtos
CREATE TABLE products (
  id INTEGER PRIMARY KEY,
  product_name TEXT NOT NULL,
  category TEXT NOT NULL,
  price INTEGER,
  supplier_id INTEGER,
  notes TEXT -- Uma coluna de notas que permite NULL
);

-- Inserir dados de fornecedores
INSERT INTO suppliers (id, supplier_name, region) VALUES
(101, 'Fornecedor de Escritório SP', 'Sudeste'),
(102, 'Utensílios de Cozinha PR', 'Sul'),
(103, 'Serviços Alimentícios RJ', 'Sudeste'),
(104, 'Papelaria Tradicional RS', 'Sul');

-- Inserir dados de produtos
INSERT INTO products (id, product_name, category, price, supplier_id, notes) VALUES
(1, 'Caneta Esferográfica Incrível', 'Papelaria', 150, 101, 'Escrita suave'),
(2, 'Caderno Mágico', 'Papelaria', 300, 104, NULL),
(3, 'Panela de Arroz Definitiva', 'Eletrodomésticos', 25000, 102, 'Produto muito popular'),
(4, 'Maçã Milagrosa', 'Alimentos', 500, 103, 'Quantidade limitada'),
(5, 'Hub USB-C', 'Periféricos de PC', 4000, 101, NULL),
(6, 'Pincel Tradicional', 'Papelaria', 1200, 104, 'Feito à mão'),
(7, 'Suco Misto Tropical', 'Alimentos', 350, NULL, 'Fornecedor desconhecido');

Agora estamos prontos para extrair dados com diversas condições.


【O Básico】Especificando Múltiplas Opções com a Cláusula IN

A sintaxe básica da cláusula IN é muito simples.

WHERE nome_da_coluna IN (valor1, valor2, valor3, ...);

Você apenas coloca uma lista dos valores que está procurando dentro dos parênteses. Isso significa "há uma correspondência se o valor da coluna for igual a qualquer um dos valores nesta lista". O exemplo da introdução que usava muitos ORs pode ser escrito de forma limpa com a cláusula IN da seguinte maneira.

Cenário: "Encontrar todos os produtos que estão na categoria 'Papelaria' ou 'Alimentos'."

SELECT * FROM products
WHERE category IN ('Papelaria', 'Alimentos');

Resultado:

id | product_name                 | category  | price | supplier_id | notes
---|------------------------------|-----------|-------|-------------|-------------------------
1  | Caneta Esferográfica Incrível| Papelaria | 150   | 101         | Escrita suave
2  | Caderno Mágico               | Papelaria | 300   | 104         | NULL
4  | Maçã Milagrosa               | Alimentos | 500   | 103         | Quantidade limitada
6  | Pincel Tradicional           | Papelaria | 1200  | 104         | Feito à mão
7  | Suco Misto Tropical          | Alimentos | 350   | NULL        | Fornecedor desconhecido

O que você acha? Não é muito mais fácil de entender a intenção em comparação com encadear múltiplos ORs? Este conceito de "lista" é a base da cláusula IN.


【A Condição Oposta】Especificando Tudo Exceto a Lista com NOT IN

Claro, há momentos em que você quer encontrar coisas que **não** estão em uma lista. É aí que você usa a cláusula NOT IN.

Cenário: "Encontrar todos os produtos que estão em categorias **diferentes de** 'Papelaria' e 'Alimentos'."

SELECT * FROM products
WHERE category NOT IN ('Papelaria', 'Alimentos');

Resultado:

id | product_name              | category           | price | supplier_id | notes
---|---------------------------|--------------------|-------|-------------|----------------------
3  | Panela de Arroz Definitiva| Eletrodomésticos   | 25000 | 102         | Produto muito popular
5  | Hub USB-C                 | Periféricos de PC  | 4000  | 101         | NULL

Simplesmente adicionando NOT antes de IN, você pode facilmente especificar uma condição negativa. Este também é um recurso muito útil.


【Aplicação】Combinando a Cláusula IN com uma Subconsulta

O verdadeiro poder da cláusula IN é liberado quando ela é combinada com uma subconsulta. Uma subconsulta é outra instrução SELECT aninhada dentro de uma instrução SQL.

Usando isso, em vez de codificar uma lista de valores após IN, você pode usar o resultado de outra consulta como a lista.

Cenário: "Encontrar todos os produtos fornecidos por fornecedores localizados na região 'Sudeste'."

Esta busca pode ser pensada em dois passos:

  1. Primeiro, obter uma lista de IDs de fornecedores da tabela suppliers onde a region é 'Sudeste'.
  2. Em seguida, usar essa lista de IDs para encontrar os produtos correspondentes na tabela products.

Você pode incorporar o Passo 1 como uma subconsulta dentro da cláusula IN.

SELECT * FROM products
WHERE supplier_id IN (SELECT id FROM suppliers WHERE region = 'Sudeste');

Resultado:

id | product_name                 | category           | price | supplier_id | notes
---|------------------------------|--------------------|-------|-------------|---------------------
1  | Caneta Esferográfica Incrível| Papelaria          | 150   | 101         | Escritura suave
4  | Maçã Milagrosa               | Alimentos          | 500   | 103         | Quantidade limitada
5  | Hub USB-C                    | Periféricos de PC  | 4000  | 101         | NULL

O banco de dados primeiro executa a subconsulta dentro dos parênteses, SELECT id FROM suppliers WHERE region = 'Sudeste', e obtém uma lista de IDs (neste caso, `(101, 103)`). Então, a consulta externa é executada efetivamente como WHERE supplier_id IN (101, 103). A capacidade de gerar dinamicamente esta lista de condições é a maior vantagem de combiná-la com uma subconsulta.


O Aviso Mais Importante! A Terrível Armadilha do `NOT IN` e do `NULL`

A cláusula IN é intuitiva e fácil de usar, mas a cláusula NOT IN tem uma "armadilha" muito desagradável na qual muitos desenvolvedores caem pelo menos uma vez. Isso acontece quando a lista contém um valor `NULL`.

Para ser direto, se uma lista usada com `NOT IN` contiver mesmo que um `NULL`, a consulta inteira retornará zero registros.

Por quê? No SQL, NULL é um valor especial que representa "nenhum valor", e qualquer comparação com NULL (p. ex., `id = NULL` ou `id <> NULL`) sempre resulta em `UNKNOWN` (desconhecido), não em `TRUE` ou `FALSE`.

Uma condição como WHERE id NOT IN (1, 2, NULL) é avaliada internamente de uma forma que é aproximadamente equivalente a WHERE id <> 1 AND id <> 2 AND id <> NULL. Mesmo que o `id` de um registro seja `3`, a parte final, `3 <> NULL`, é avaliada como `UNKNOWN`. Como a condição geral nunca pode se tornar `TRUE`, nenhuma linha jamais corresponderá.

Isso é especialmente perigoso quando combinado com as subconsultas que acabamos de usar.

Cenário: "Encontrar todos os produtos **exceto** aqueles fornecidos por um fornecedor desconhecido (onde supplier_id é NULL)."

Isso pode parecer correto à primeira vista, mas...

-- 【ARMADILHA!】Esta consulta não funcionará como esperado!
SELECT * FROM products
WHERE supplier_id NOT IN (SELECT id FROM suppliers WHERE region = 'Sul');

-- A subconsulta acima retorna (102, 104), mas se a tabela suppliers
-- tivesse uma linha com um id NULL, a lista se tornaria (102, 104, NULL),
-- e esta consulta retornaria 0 resultados.

Se houver a possibilidade de que o resultado da subconsulta possa incluir `NULL`, a consulta retornará zero linhas. Nossos dados de amostra não têm um fornecedor com um id `NULL`, mas nossa tabela `products` tem um registro com um `supplier_id` `NULL` (o produto com ID 7). Vamos ver como este registro é tratado por `NOT IN`.

Solução: Ao usar `NOT IN` com uma subconsulta, é uma regra de ouro garantir que a subconsulta nunca possa retornar valores `NULL` adicionando uma condição como `WHERE id IS NOT NULL` dentro da subconsulta, ou usar uma sintaxe diferente como `NOT EXISTS`.


【Playground Interativo】Domine a Cláusula IN Executando SQL no seu Navegador!

Agora é hora de transformar conhecimento em habilidade! Copie todo o código HTML abaixo, salve-o como um arquivo tipo sql_in_test.html, e abra-o no seu navegador. Seu próprio ambiente SQL pessoal será iniciado, pré-carregado com as tabelas products e suppliers que usamos neste artigo.

Tente alterar o conteúdo das listas IN ou NOT IN, ou modificar as condições da subconsulta, e veja como os resultados mudam!

<!DOCTYPE html>
<html lang="pt">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Playground Online da Cláusula IN do SQL</title>
  <style>
    body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; line-height: 1.7; color: #333; max-width: 800px; margin: 2rem auto; padding: 0 1rem; }
    h1 { color: #8e44ad; }
    textarea { width: 100%; height: 180px; font-family: "SF Mono", "Consolas", monospace; font-size: 16px; padding: 12px; border: 1px solid #ccc; border-radius: 6px; box-sizing: border-box; margin-bottom: 1rem; }
    button { background-color: #9b59b6; color: white; border: none; padding: 12px 22px; font-size: 16px; border-radius: 6px; cursor: pointer; transition: background-color 0.2s; }
    button:hover { background-color: #8e44ad; }
    button:disabled { background-color: #bdc3c7; cursor: not-allowed; }
    #result-container { margin-top: 2rem; border: 1px solid #ddd; padding: 1rem; border-radius: 6px; background: #fdfdfd; min-height: 50px; }
    #error-message { color: #e74c3c; font-weight: bold; }
    table { border-collapse: collapse; width: 100%; margin-top: 1rem; }
    th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }
    th { background-color: #f2f2f2; }
    tr:nth-child(even) { background-color: #f9f9f9; }
  </style>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.3/sql-wasm.js"></script>
</head>
<body>

  <h1>Vamos Experimentar o SQL!</h1>
  <p>Digite uma instrução SQL na área de texto abaixo e clique no botão "Executar". Experimente com diferentes condições!</p>

  <textarea id="sql-input">-- Vamos encontrar produtos fornecidos por fornecedores da região 'Sul'
SELECT * FROM products
WHERE supplier_id IN (SELECT id FROM suppliers WHERE region = 'Sul');
  </textarea>
  
  <button id="execute-btn">Executar</button>
  
  <div id="result-container">
    <p id="error-message"></p>
    <div id="result-output"></div>
  </div>

  <script>
    const sqlInput = document.getElementById('sql-input');
    const executeBtn = document.getElementById('execute-btn');
    const errorMsg = document.getElementById('error-message');
    const resultOutput = document.getElementById('result-output');

    let db;

    async function initDb() {
      executeBtn.disabled = true;
      executeBtn.textContent = 'Inicializando BD...';
      try {
        const SQL = await initSqlJs({
          locateFile: file => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.3/${file}`
        });
        db = new SQL.Database();
        
        const setupSql = `
          DROP TABLE IF EXISTS products;
          DROP TABLE IF EXISTS suppliers;

          CREATE TABLE suppliers (id INTEGER PRIMARY KEY, supplier_name TEXT NOT NULL, region TEXT NOT NULL);
          CREATE TABLE products (id INTEGER PRIMARY KEY, product_name TEXT NOT NULL, category TEXT NOT NULL, price INTEGER, supplier_id INTEGER, notes TEXT);

          INSERT INTO suppliers (id, supplier_name, region) VALUES (101, 'Fornecedor de Escritório SP', 'Sudeste'), (102, 'Utensílios de Cozinha PR', 'Sul'), (103, 'Serviços Alimentícios RJ', 'Sudeste'), (104, 'Papelaria Tradicional RS', 'Sul');
          INSERT INTO products (id, product_name, category, price, supplier_id, notes) VALUES (1, 'Caneta Esferográfica Incrível', 'Papelaria', 150, 101, 'Escrita suave'), (2, 'Caderno Mágico', 'Papelaria', 300, 104, NULL), (3, 'Panela de Arroz Definitiva', 'Eletrodomésticos', 25000, 102, 'Produto muito popular'), (4, 'Maçã Milagrosa', 'Alimentos', 500, 103, 'Quantidade limitada'), (5, 'Hub USB-C', 'Periféricos de PC', 4000, 101, NULL), (6, 'Pincel Tradicional', 'Papelaria', 1200, 104, 'Feito à mão'), (7, 'Suco Misto Tropical', 'Alimentos', 350, NULL, 'Fornecedor desconhecido');
        `;
        db.run(setupSql);
        
        executeBtn.disabled = false;
        executeBtn.textContent = 'Executar';
        resultOutput.innerHTML = '<p>Pronto! Sinta-se à vontade para experimentar suas próprias consultas SQL.</p>';

      } catch (err) {
        errorMsg.textContent = 'Falha ao inicializar o banco de dados: ' + err.message;
        console.error(err);
      }
    }

    function executeSql() {
      if (!db) return;
      
      const sql = sqlInput.value;
      errorMsg.textContent = '';
      resultOutput.innerHTML = '';

      try {
        const results = db.exec(sql);
        if (results.length === 0) {
          resultOutput.innerHTML = '<p>Consulta executada com sucesso, mas nenhum conjunto de resultados foi retornado.</p>';
          return;
        }
        
        results.forEach(result => {
          const table = document.createElement('table');
          const thead = document.createElement('thead');
          const tbody = document.createElement('tbody');
          
          const headerRow = document.createElement('tr');
          result.columns.forEach(colName => {
            const th = document.createElement('th');
            th.textContent = colName;
            headerRow.appendChild(th);
          });
          thead.appendChild(headerRow);
          
          result.values.forEach(row => {
            const bodyRow = document.createElement('tr');
            row.forEach(cellValue => {
              const td = document.createElement('td');
              td.textContent = cellValue === null ? 'NULL' : cellValue;
              bodyRow.appendChild(td);
            });
            tbody.appendChild(bodyRow);
          });
          
          table.appendChild(thead);
          table.appendChild(tbody);
          resultOutput.appendChild(table);
        });

      } catch (err) {
        errorMsg.textContent = 'Erro de SQL: ' + err.message;
        console.error(err);
      }
    }

    executeBtn.addEventListener('click', executeSql);
    
    initDb();
  </script>
</body>
</html>

Conclusão

Neste artigo, aprendemos sobre as cláusulas IN e NOT IN para lidar de forma inteligente com múltiplas condições.

A cláusula IN é uma técnica fundamental para tornar seu código mais simples e legível. A combinação com subconsultas, em particular, é um caminho inevitável ao extrair dados com condições complexas. E conhecer a relação entre NOT IN e NULL` é um conhecimento crucial que pode mudar drasticamente o tempo que você gasta depurando. Certifique-se de dominar o conteúdo deste artigo para elevar suas habilidades em SQL!