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

[SQL] ¡Guía completa sobre el uso de la cláusula GROUP BY! Agrega tus datos con total libertad

Tanto si se trata de analizar el tráfico de un sitio web, las ventas en un sitio de comercio electrónico o el comportamiento de los usuarios en una app, la "agregación" es una tarea fundamental cuando se trabaja con datos. ¿No sería útil extraer instantáneamente información como "número de productos por categoría", "número de usuarios por prefectura" o "ventas totales por mes"? La cláusula GROUP BY de SQL es la clave para hacerlo posible.

La cláusula GROUP BY es una herramienta poderosa que divide los datos de una tabla en grupos basados en los valores de una columna determinada, y aplica funciones de agregación (COUNT, SUM, AVG, etc.) a cada grupo. A primera vista puede parecer un poco complicado, pero una vez que domines su uso, tus capacidades de análisis de datos se expandirán de forma notable.

Este artículo está diseñado para que incluso los principiantes que aspiran a convertirse en creadores web puedan seguirlo sin problemas. Explicaremos todo, desde lo básico del GROUP BY hasta técnicas avanzadas como el uso de la cláusula HAVING y la agrupación por múltiples columnas, ¡todo ello con abundantes ejemplos de código “listos para copiar y pegar”! ¡Descubramos juntos lo divertido que es “agrupar y agregar datos”!


Preparación: Crea datos de muestra para el análisis

La mejor manera de aprender SQL es probarlo en la práctica. Para este artículo, utilizaremos una tabla products que representa la lista de productos de un sitio ficticio de comercio electrónico. Las siguientes instrucciones SQL crean la tabla e insertan los datos de ejemplo. Todos los ejemplos del artículo utilizan esta tabla.

-- Elimina la tabla si existe (para poder ejecutarlo varias veces)
DROP TABLE IF EXISTS products;

-- Crea la tabla products
CREATE TABLE products (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL,
  category TEXT NOT NULL,
  prefecture TEXT NOT NULL,
  price INTEGER NOT NULL,
  stock_quantity INTEGER NOT NULL
);

-- Inserta datos
INSERT INTO products (id, name, category, prefecture, price, stock_quantity) VALUES
(1, 'Camiseta Increíble', 'Ropa', 'Tokyo', 3000, 50),
(2, 'Los Mejores Jeans', 'Ropa', 'Okayama', 12000, 30),
(3, 'Teclado Mágico', 'Accesorios de PC', 'Tokyo', 8500, 20),
(4, 'Ratón Luminoso', 'Accesorios de PC', 'Kanagawa', 4500, 45),
(5, 'Cuchillo Maestro', 'Utensilios de Cocina', 'Niigata', 9800, 15),
(6, 'Sartén Definitiva', 'Utensilios de Cocina', 'Niigata', 6200, 25),
(7, 'Introducción a SQL', 'Libros', 'Tokyo', 2800, 100),
(8, 'Diseño Web para Principiantes', 'Libros', 'Tokyo', 3200, 80),
(9, 'Zapatillas Cómodas', 'Ropa', 'Tokyo', 7800, 60);

Esta tabla contiene datos como ID de producto, nombre, categoría, lugar de producción (prefectura), precio y cantidad en stock. Hemos incluido entradas duplicadas intencionadamente —por ejemplo, 3 artículos en la categoría "Ropa" y 4 artículos de "Tokyo"—. Esto es precisamente lo que hace que GROUP BY sea significativo al agregar datos.


[Básico] Contar la cantidad de productos por categoría usando `GROUP BY`

Comencemos con el uso más básico de GROUP BY. ¿Quieres saber cuántos tipos de productos hay en cada categoría? Para eso, usamos GROUP BY para agrupar la columna category y luego usamos la conocida función COUNT() para contar las filas en cada grupo.

SELECT
  category,
  COUNT(*) AS product_count
FROM
  products
GROUP BY
  category;

Resultado de ejecución:

category       | product_count
---------------|---------------
Accesorios PC  | 2
Ropa           | 3
Utensilios     | 2
Libros         | 2

¡Hemos obtenido con éxito la cantidad de productos por categoría! Internamente, el motor SQL agrupa las filas que tienen el mismo valor en la columna category (como 'Ropa', 'Accesorios PC', etc.), y luego ejecuta COUNT(*) en cada grupo. También es importante destacar el uso de AS product_count para asignar un alias a la columna del resultado.


[Avanzado 1] Combinar con otras funciones agregadas como `SUM` y `AVG`

Lo grandioso de GROUP BY es que no se limita a COUNT(). Puedes combinarlo libremente con otras funciones agregadas como SUM() (suma), AVG() (promedio), MAX() (máximo) y MIN() (mínimo).

Ahora calculemos el “precio promedio por categoría”.

SELECT
  category,
  AVG(price) AS average_price
FROM
  products
GROUP BY
  category;

Resultado de ejecución:

category       | average_price
---------------|---------------
Accesorios PC  | 6500
Ropa           | 7600
Utensilios     | 8000
Libros         | 3000

Como puedes ver, al cambiar la función de agregación, puedes analizar diferentes aspectos de cada grupo. Por ejemplo, si quieres saber el “stock total por categoría”, usa SUM(stock_quantity).

SELECT
  category,
  SUM(stock_quantity) AS total_stock
FROM
  products
GROUP BY
  category;

Resultado de ejecución:

category       | total_stock
---------------|-------------
Accesorios PC  | 65
Ropa           | 140
Utensilios     | 40
Libros         | 180

Así, puedes ver de inmediato que la categoría “Libros” tiene el mayor stock, mientras que “Utensilios” tiene el menor.


[Avanzado 2] Filtrar los resultados agregados con la cláusula `HAVING`

Es muy común querer filtrar aún más los resultados después de usar GROUP BY. Por ejemplo, "quiero mostrar solo las categorías que tienen 3 o más productos".

Es importante tener en cuenta que la cláusula WHERE se usa para filtrar los datos antes de la agregación (por fila), por lo que no puede aplicarse a resultados agregados como COUNT(*).

La cláusula `HAVING` se utiliza para establecer condiciones sobre los grupos después de la agregación.

SELECT
  category,
  COUNT(*) AS product_count
FROM
  products
GROUP BY
  category
HAVING
  COUNT(*) >= 3;

Resultado de ejecución:

category | product_count
---------|---------------
Ropa     | 3

Al añadir la condición HAVING COUNT(*) >= 3, solo la categoría "Ropa", que tiene 3 productos, fue devuelta en el resultado.

`WHERE` se usa antes de la agrupación, `HAVING` se usa después. Asegúrate de recordar esta diferencia.


[Avanzado 3] Agrupar por múltiples columnas

También puedes usar GROUP BY con múltiples columnas para crear grupos más detallados. Por ejemplo, "quiero saber cuántos productos hay por cada combinación de categoría y lugar de producción".

Simplemente especifica varios nombres de columnas separados por comas en la cláusula GROUP BY.

SELECT
  category,
  prefecture,
  COUNT(*) AS product_count
FROM
  products
GROUP BY
  category, prefecture;

Resultado de ejecución:

category   | prefecture | product_count
-----------|------------|---------------
Periféricos| Kanagawa   | 1
Periféricos| Tokyo      | 1
Ropa       | Okayama    | 1
Ropa       | Tokyo      | 2
Cocina     | Niigata    | 2
Libros     | Tokyo      | 2

Puedes ver que "Ropa en Okayama" y "Ropa en Tokyo" se tratan como grupos separados. Esto permite un análisis más detallado, como "hay 2 tipos de productos de ropa en Tokyo".


[Sección práctica] ¡Ejecuta SQL libremente en el navegador!

¡La teoría ya fue suficiente! Ahora es tu turno de escribir SQL, ejecutarlo y verificar los resultados.

Copia todo el código HTML a continuación, guárdalo en tu PC con un nombre de archivo como sql_groupby_test.html y ábrelo en tu navegador web. Tendrás tu propio entorno de ejecución SQL con la tabla products integrada.

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Entorno de Ejecución SQL con GROUP BY</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: #2c3e50; }
    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: #3498db; color: white; border: none; padding: 12px 22px; font-size: 16px; border-radius: 6px; cursor: pointer; transition: background-color 0.2s; }
    button:hover { background-color: #2980b9; }
    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>¡Prueba SQL en tu navegador!</h1>
  <p>Ingresa sentencias SQL en el área de texto de abajo y haz clic en el botón “Ejecutar”. ¡Prueba las consultas presentadas en este artículo!</p>

  <textarea id="sql-input">-- Muestra precio promedio y precio máximo por prefactura
SELECT
  prefecture,
  AVG(price) AS "Precio Promedio",
  MAX(price) AS "Precio Máximo"
FROM
  products
GROUP BY
  prefecture;</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;
          CREATE TABLE products (
            id INTEGER PRIMARY KEY,
            name TEXT NOT NULL,
            category TEXT NOT NULL,
            prefecture TEXT NOT NULL,
            price INTEGER NOT NULL,
            stock_quantity INTEGER NOT NULL
          );
          INSERT INTO products (id, name, category, prefecture, price, stock_quantity) VALUES
          (1, 'Camiseta Asombrosa', 'Apparel', 'Tokyo', 3000, 50),
          (2, 'Mejores Jeans', 'Apparel', 'Okayama', 12000, 30),
          (3, 'Teclado Mágico', 'Accesorios PC', 'Tokyo', 8500, 20),
          (4, 'Ratón Iluminado', 'Accesorios PC', 'Kanagawa', 4500, 45),
          (5, 'Cuchillo Artesano', 'Utensilios Cocina', 'Niigata', 9800, 15),
          (6, 'Sartén Definitiva', 'Utensilios Cocina', 'Niigata', 6200, 25),
          (7, 'Introducción a SQL', 'Libros', 'Tokyo', 2800, 100),
          (8, 'Fundamentos de Diseño Web', 'Libros', 'Tokyo', 3200, 80),
          (9, 'Zapatillas Cómodas', 'Apparel', 'Tokyo', 7800, 60);
        `;
        db.run(setupSql);
        
        executeBtn.disabled = false;
        executeBtn.textContent = 'Ejecutar';
        resultOutput.innerHTML = '<p>¡Listo! Siéntete libre de experimentar con 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>La consulta se ejecutó pero no devolvió resultados (ej. INSERT, UPDATE).</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' : (typeof cellValue === 'number' ? cellValue.toLocaleString() : cellValue);
              bodyRow.appendChild(td);
            });
            tbody.appendChild(bodyRow);
          });
          
          table.appendChild(thead);
          table.appendChild(tbody);
          resultOutput.appendChild(table);
        });

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

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

[Desafío]


Punto de confusión común para principiantes: reglas de la cláusula `SELECT`

Cuando usas GROUP BY, uno de los errores más comunes es este: “solo puedes incluir columnas en la cláusula SELECT que estén en la cláusula GROUP BY o estén envueltas en funciones de agregación”.

Por ejemplo, la siguiente consulta SQL dará un error:

-- Este es un ejemplo que dará error
SELECT
  category,
  name -- ← columna que no está ni en GROUP BY ni en función agregada
FROM
  products
GROUP BY
  category;

¿Por qué causa error? Piensa en esto: al usar GROUP BY category para agrupar como “Ropa”, la columna name puede contener varias opciones: “Camiseta Asombrosa”, “Mejores Jeans”, “Zapatillas Cómodas”. El motor SQL no puede decidir cuál mostrar, por lo tanto, devuelve un error.

Una fila agrupada por GROUP BY representa varias filas originales como una sola. Por eso, en la cláusula SELECT solo puedes incluir columnas que identifican el grupo (en este caso, category) o funciones de agregación como COUNT(*) o SUM(price).


Técnica relacionada: ordenar resultados con `ORDER BY`

A menudo querrás ordenar los resultados de una consulta GROUP BY. En ese caso, utiliza la conocida cláusula ORDER BY. Debe colocarse después de GROUP BY y HAVING.

Por ejemplo, ordenemos la cantidad de productos por categoría de mayor a menor:

SELECT
  category,
  COUNT(*) AS product_count
FROM
  products
GROUP BY
  category
ORDER BY
  product_count DESC;

Resultado de ejecución:

category         | product_count
----------------|---------------
Ropa            | 3
Accesorios PC   | 2
Utensilios Cocina | 2
Libros          | 2

Al usar ORDER BY product_count DESC, ordenamos la columna product_count de forma descendente, facilitando ver qué categoría tiene más productos.


Resumen

En este artículo, aprendimos sobre la poderosa cláusula GROUP BY para agrupar y analizar datos.

Dominar GROUP BY te abre las puertas al mundo del análisis de datos. No solo podrás recuperar datos, sino entender sus patrones. ¡Prueba las consultas en la sección práctica y fortalece tus habilidades como desarrollador web con esta potente herramienta de agregación!