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

【Tutorial de la Cláusula IN de SQL】¡Maneja Múltiples Condiciones con Elegancia! Cómo Cambia la Escritura de WHERE con IN y NOT IN

Cuando desarrollas sitios web, te encontrarás frecuentemente con situaciones en las que necesitas buscar datos que coincidan con una de varias opciones, como "mostrar una lista de productos de categorías específicas" o "enviar una notificación de campaña solo a usuarios de Tokio, Kanagawa y Chiba".

En tales casos, ¿cómo escribirías la cláusula WHERE?

WHERE category = 'Papelería' OR category = 'Electrodomésticos' OR category = 'Alimentos'

Por supuesto, esto funciona correctamente. Pero, ¿qué pasa si el número de opciones aumenta a cinco o diez? El código se convertiría en una cadena larga de ORs, difícil de leer y de mantener. El héroe que resuelve este problema en un instante es el tema principal de hoy: la cláusula IN.

Este artículo explicará a fondo todo, desde el uso básico de la cláusula IN y su opuesta, la cláusula NOT IN, hasta técnicas avanzadas usando subconsultas, e incluso la "trampa de NULL" en la que mucha gente cae, todo con código que puedes copiar y pegar. ¡Domina la cláusula IN y haz que tu SQL sea más inteligente y legible!


Preparación: Preparemos Nuestros Datos de Productos para la Búsqueda

Para probar las operaciones, primero necesitamos algunos datos. Esta vez, crearemos una tabla simple de products para gestionar una lista de productos y una tabla de suppliers para gestionar la información sobre los proveedores. Tener múltiples categorías y proveedores hará más fácil apreciar la conveniencia de la cláusula IN.

-- Si las tablas existen, eliminarlas (para pruebas repetibles)
DROP TABLE IF EXISTS products;
DROP TABLE IF EXISTS suppliers;

-- Crear tabla de proveedores
CREATE TABLE suppliers (
  id INTEGER PRIMARY KEY,
  supplier_name TEXT NOT NULL,
  region TEXT NOT NULL -- 'Kanto', 'Kansai'
);

-- Crear tabla de productos
CREATE TABLE products (
  id INTEGER PRIMARY KEY,
  product_name TEXT NOT NULL,
  category TEXT NOT NULL,
  price INTEGER,
  supplier_id INTEGER,
  notes TEXT -- Una columna de notas que permite NULL
);

-- Insertar datos de proveedores
INSERT INTO suppliers (id, supplier_name, region) VALUES
(101, 'Suministros de Oficina Tokio', 'Kanto'),
(102, 'Utensilios de Cocina Osaka', 'Kansai'),
(103, 'Servicio de Alimentos Chiba', 'Kanto'),
(104, 'Papelería Tradicional Kioto', 'Kansai');

-- Insertar datos de productos
INSERT INTO products (id, product_name, category, price, supplier_id, notes) VALUES
(1, 'Bolígrafo Asombroso', 'Papelería', 150, 101, 'Escritura suave'),
(2, 'Cuaderno Mágico', 'Papelería', 300, 104, NULL),
(3, 'Arrocera Definitiva', 'Electrodomésticos', 25000, 102, 'Producto muy popular'),
(4, 'Manzana Milagrosa', 'Alimentos', 500, 103, 'Cantidad limitada'),
(5, 'Hub USB-C', 'Periféricos de PC', 4000, 101, NULL),
(6, 'Pincel Tradicional', 'Papelería', 1200, 104, 'Hecho a mano'),
(7, 'Zumo Mixto de Osaka', 'Alimentos', 350, NULL, 'Proveedor desconocido');

Ahora estamos listos para extraer datos con diversas condiciones.


【Lo Básico】Especificando Múltiples Opciones con la Cláusula IN

La sintaxis básica de la cláusula IN es muy simple.

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

Simplemente pones una lista de los valores que estás buscando dentro de los paréntesis. Esto significa "hay una coincidencia si el valor de la columna es igual a cualquiera de los valores de esta lista". El ejemplo de la introducción que usaba muchos ORs se puede escribir de forma limpia con la cláusula IN de la siguiente manera.

Escenario: "Encontrar todos los productos que estén en la categoría 'Papelería' o 'Alimentos'."

SELECT * FROM products
WHERE category IN ('Papelería', 'Alimentos');

Resultado:

id | product_name        | category  | price | supplier_id | notes
---|---------------------|-----------|-------|-------------|---------------------
1  | Bolígrafo Asombroso | Papelería | 150   | 101         | Escritura suave
2  | Cuaderno Mágico     | Papelería | 300   | 104         | NULL
4  | Manzana Milagrosa   | Alimentos | 500   | 103         | Cantidad limitada
6  | Pincel Tradicional  | Papelería | 1200  | 104         | Hecho a mano
7  | Zumo Mixto de Osaka | Alimentos | 350   | NULL        | Proveedor desconocido

¿Qué te parece? ¿No es mucho más fácil de entender la intención en comparación con encadenar múltiples ORs? Este concepto de "lista" es la base de la cláusula IN.


【La Condición Opuesta】Especificando Todo Excepto la Lista con NOT IN

Por supuesto, hay veces que quieres encontrar cosas que **no** están en una lista. Es entonces cuando usas la cláusula NOT IN.

Escenario: "Encontrar todos los productos que estén en categorías **distintas a** 'Papelería' y 'Alimentos'."

SELECT * FROM products
WHERE category NOT IN ('Papelería', 'Alimentos');

Resultado:

id | product_name        | category           | price | supplier_id | notes
---|---------------------|--------------------|-------|-------------|----------------------
3  | Arrocera Definitiva | Electrodomésticos  | 25000 | 102         | Producto muy popular
5  | Hub USB-C           | Periféricos de PC  | 4000  | 101         | NULL

Simplemente añadiendo NOT antes de IN, puedes especificar fácilmente una condición negativa. Esta también es una característica muy útil.


【Aplicación】Combinando la Cláusula IN con una Subconsulta

El verdadero poder de la cláusula IN se desata cuando se combina con una subconsulta. Una subconsulta es otra sentencia SELECT anidada dentro de una sentencia SQL.

Usando esto, en lugar de codificar una lista de valores después de IN, puedes usar el resultado de otra consulta como la lista.

Escenario: "Encontrar todos los productos suministrados por proveedores ubicados en la región 'Kanto'."

Esta búsqueda se puede pensar en dos pasos:

  1. Primero, obtener una lista de IDs de proveedores de la tabla suppliers donde la region es 'Kanto'.
  2. Luego, usar esa lista de IDs para encontrar los productos correspondientes en la tabla products.

Puedes incrustar el Paso 1 como una subconsulta dentro de la cláusula IN.

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

Resultado:

id | product_name        | category           | price | supplier_id | notes
---|---------------------|--------------------|-------|-------------|------------------
1  | Bolígrafo Asombroso | Papelería          | 150   | 101         | Escritura suave
4  | Manzana Milagrosa   | Alimentos          | 500   | 103         | Cantidad limitada
5  | Hub USB-C           | Periféricos de PC  | 4000  | 101         | NULL

La base de datos primero ejecuta la subconsulta dentro de los paréntesis, SELECT id FROM suppliers WHERE region = 'Kanto', y obtiene una lista de IDs (en este caso, `(101, 103)`). Luego, la consulta externa se ejecuta efectivamente como WHERE supplier_id IN (101, 103). La capacidad de generar dinámicamente esta lista de condiciones es la mayor ventaja de combinarla con una subconsulta.


¡La Advertencia Más Importante! La Terrible Trampa de `NOT IN` y `NULL`

La cláusula IN es intuitiva y fácil de usar, pero la cláusula NOT IN tiene una "trampa" muy desagradable en la que muchos desarrolladores caen al menos una vez. Esto sucede cuando la lista contiene un valor NULL.

Para decirlo sin rodeos, si una lista usada con `NOT IN` contiene aunque sea un solo `NULL`, la consulta entera devolverá cero registros.

¿Por qué? En SQL, NULL es un valor especial que representa "ningún valor", y cualquier comparación con NULL (p. ej., `id = NULL` o `id <> NULL`) siempre resulta en `UNKNOWN` (desconocido), no en `TRUE` o `FALSE`.

Una condición como WHERE id NOT IN (1, 2, NULL) se evalúa internamente de una manera que es aproximadamente equivalente a WHERE id <> 1 AND id <> 2 AND id <> NULL. Incluso si el `id` de un registro es `3`, la parte final, `3 <> NULL`, se evalúa como `UNKNOWN`. Dado que la condición general nunca puede llegar a ser `TRUE`, ninguna fila coincidirá jamás.

Esto es especialmente peligroso cuando se combina con las subconsultas que acabamos de usar.

Escenario: "Encontrar todos los productos **excepto** aquellos suministrados por un proveedor desconocido (donde supplier_id es NULL)."

Esto podría parecer correcto a primera vista, pero...

-- 【¡TRAMPA!】¡Esta consulta no funcionará como se espera!
SELECT * FROM products
WHERE supplier_id NOT IN (SELECT id FROM suppliers WHERE region = 'Kansai');

-- La subconsulta anterior devuelve (102, 104), pero si la tabla suppliers
-- tuviera una fila con un id NULL, la lista se convertiría en (102, 104, NULL),
-- y esta consulta devolvería 0 resultados.

Si existe la posibilidad de que el resultado de la subconsulta incluya `NULL`, la consulta devolverá cero filas. Nuestros datos de muestra no tienen un proveedor con un id `NULL`, pero nuestra tabla `products` sí tiene un registro con un `supplier_id` `NULL` (el producto con ID 7). Veamos cómo se trata este registro con `NOT IN`.

Solución: Al usar `NOT IN` con una subconsulta, es una regla de oro asegurarse de que la subconsulta nunca pueda devolver valores `NULL` añadiendo una condición como `WHERE id IS NOT NULL` dentro de la subconsulta, o usar una sintaxis diferente como `NOT EXISTS`.


【Patio de Recreo Interactivo】¡Domina la Cláusula IN Ejecutando SQL en tu Navegador!

¡Ahora es el momento de convertir el conocimiento en una habilidad! Copia todo el código HTML a continuación, guárdalo como un archivo llamado sql_in_test.html y ábrelo en tu navegador. Se iniciará tu propio entorno SQL personal, precargado con las tablas products y suppliers que hemos estado usando en este artículo.

¡Intenta cambiar el contenido de las listas IN o NOT IN, o modifica las condiciones de la subconsulta, y mira cómo cambian los resultados!

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Patio de Recreo en Línea de la Cláusula IN de 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>¡Probemos SQL!</h1>
  <p>Introduce una sentencia SQL en el área de texto de abajo y haz clic en el botón "Ejecutar". ¡Prueba con diferentes condiciones!</p>

  <textarea id="sql-input">-- Busquemos productos suministrados por proveedores de la región 'Kansai'
SELECT * FROM products
WHERE supplier_id IN (SELECT id FROM suppliers WHERE region = 'Kansai');
  </textarea>
  
  <button id="execute-btn">Ejecutar</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, 'Suministros de Oficina Tokio', 'Kanto'), (102, 'Utensilios de Cocina Osaka', 'Kansai'), (103, 'Servicio de Alimentos Chiba', 'Kanto'), (104, 'Papelería Tradicional Kioto', 'Kansai');
          INSERT INTO products (id, product_name, category, price, supplier_id, notes) VALUES (1, 'Bolígrafo Asombroso', 'Papelería', 150, 101, 'Escritura suave'), (2, 'Cuaderno Mágico', 'Papelería', 300, 104, NULL), (3, 'Arrocera Definitiva', 'Electrodomésticos', 25000, 102, 'Producto muy popular'), (4, 'Manzana Milagrosa', 'Alimentos', 500, 103, 'Cantidad limitada'), (5, 'Hub USB-C', 'Periféricos de PC', 4000, 101, NULL), (6, 'Pincel Tradicional', 'Papelería', 1200, 104, 'Hecho a mano'), (7, 'Zumo Mixto de Osaka', 'Alimentos', 350, NULL, 'Proveedor desconocido');
        `;
        db.run(setupSql);
        
        executeBtn.disabled = false;
        executeBtn.textContent = 'Ejecutar';
        resultOutput.innerHTML = '<p>¡Todo listo! Siéntete libre de probar tus propias consultas SQL.</p>';

      } catch (err) {
        errorMsg.textContent = 'Error al inicializar la base de datos: ' + 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 ejecutada con éxito, pero no se devolvió ningún conjunto de resultados.</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 = 'Error de SQL: ' + err.message;
        console.error(err);
      }
    }

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

Conclusión

En este artículo, hemos aprendido sobre las cláusulas IN y NOT IN para manejar inteligentemente múltiples condiciones.

La cláusula IN es una técnica fundamental para hacer tu código más simple y legible. La combinación con subconsultas, en particular, es un camino ineludible al extraer datos con condiciones complejas. Y conocer la relación entre NOT IN y NULL` es un conocimiento crucial que puede cambiar drásticamente el tiempo que pasas depurando. ¡Asegúrate de dominar el contenido de este artículo para subir de nivel tus habilidades de SQL!