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

[SQL] Explicação completa sobre a cláusula GROUP BY! Agrupe e agregue dados com facilidade

Seja para analisar o tráfego de um site, vendas de uma loja online ou o comportamento dos usuários em um aplicativo, a “agregação” é uma tarefa indispensável ao lidar com dados. Não seria útil obter rapidamente informações como “número de produtos por categoria”, “número de usuários por província” ou “vendas totais por mês”? A cláusula GROUP BY do SQL torna isso possível.

GROUP BY é uma ferramenta poderosa que divide os dados de uma tabela em grupos com base nos valores de uma coluna específica, aplicando funções de agregação (COUNT, SUM, AVG etc.) a cada grupo. À primeira vista, pode parecer um pouco complicado, mas ao dominar seu uso, suas habilidades de análise de dados se expandirão significativamente.

Este artigo foi elaborado para que até mesmo iniciantes que desejam se tornar criadores web possam acompanhar. Explicaremos desde o uso básico de GROUP BY até técnicas avançadas como o uso da cláusula HAVING e agrupamento por múltiplas colunas, com muitos códigos “prontos para copiar e colar”! Vamos experimentar juntos a diversão de “agrupar e agregar dados”!


Preparação: Vamos preparar dados de exemplo para análise

A melhor maneira de aprender SQL é praticar com as próprias mãos. Neste artigo, utilizaremos a tabela products, que simula uma lista de produtos de uma pequena loja online fictícia. O código SQL abaixo cria a tabela e insere os dados de exemplo. Todos os exemplos deste artigo usarão essa tabela.

-- Exclui a tabela se já existir (para permitir repetir os testes)
DROP TABLE IF EXISTS products;

-- Cria a tabela 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
);

-- Insere os dados
INSERT INTO products (id, name, category, prefecture, price, stock_quantity) VALUES
(1, 'Camiseta Incrível', 'Roupas', 'Tóquio', 3000, 50),
(2, 'Melhor Calça Jeans', 'Roupas', 'Okayama', 12000, 30),
(3, 'Teclado Mágico', 'Acessórios de PC', 'Tóquio', 8500, 20),
(4, 'Mouse Iluminado', 'Acessórios de PC', 'Kanagawa', 4500, 45),
(5, 'Faca Artesanal', 'Utensílios de Cozinha', 'Niigata', 9800, 15),
(6, 'Frigideira Suprema', 'Utensílios de Cozinha', 'Niigata', 6200, 25),
(7, 'Primeiros Passos em SQL', 'Livros', 'Tóquio', 2800, 100),
(8, 'Introdução ao Web Design', 'Livros', 'Tóquio', 3200, 80),
(9, 'Tênis Confortável', 'Roupas', 'Tóquio', 7800, 60);

Esta tabela contém dados como ID do produto, nome do produto, categoria, local de fabricação (província), preço e quantidade em estoque. Intencionalmente, há dados duplicados — como três itens na categoria “Roupas” e quatro de “Tóquio”. Esses dados são ideais para praticar a agregação com GROUP BY.


[Básico] Contar o número de produtos por categoria usando `GROUP BY`

Vamos começar com o uso mais básico de GROUP BY. “Quantos tipos de produtos existem em cada categoria?” Para descobrir isso, usamos GROUP BY na coluna category e a conhecida função COUNT() para contar o número de linhas em cada grupo.

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

Resultado da execução:

category       | product_count
---------------|---------------
Acessórios PC  | 2
Roupas         | 3
Utensílios     | 2
Livros         | 2

Conseguimos obter com sucesso a quantidade de produtos por categoria! O mecanismo SQL agrupa internamente as linhas com os mesmos valores na coluna category (como 'Roupas', 'Acessórios PC', etc.), e então executa COUNT(*) para cada grupo. Também vale destacar o uso de AS product_count para nomear a coluna de resultado.


[Avançado 1] Combinar com outras funções de agregação como `SUM` e `AVG`

O incrível do GROUP BY é que você pode combiná-lo livremente não só com COUNT(), mas também com outras funções de agregação como SUM() (soma), AVG() (média), MAX() (máximo) e MIN() (mínimo).

Agora, vamos calcular o “preço médio por categoria”.

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

Resultado da execução:

category       | average_price
---------------|---------------
Acessórios PC  | 6500
Roupas         | 7600
Utensílios     | 8000
Livros         | 3000

Como pode ver, ao mudar a função de agregação, é possível analisar vários aspectos de cada grupo. Por exemplo, para saber o “estoque total por categoria”, use SUM(stock_quantity).

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

Resultado da execução:

category       | total_stock
---------------|-------------
Acessórios PC  | 65
Roupas         | 140
Utensílios     | 40
Livros         | 180

Com isso, é fácil ver que a categoria “Livros” tem o maior estoque, enquanto “Utensílios” tem o menor.


[Avançado 2] Filtrar resultados agregados com a cláusula `HAVING`

É comum querer refinar ainda mais os resultados após usar GROUP BY. Por exemplo, "quero exibir apenas as categorias com 3 ou mais produtos".

É importante observar que a cláusula WHERE é usada para filtrar os dados antes da agregação (linha por linha), portanto não pode ser usada em resultados agregados como COUNT(*).

A cláusula `HAVING` é usada para aplicar condições aos grupos após a agregação.

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

Resultado da execução:

category | product_count
---------|---------------
Roupas   | 3

Com a condição HAVING COUNT(*) >= 3, apenas a categoria "Roupas", que possui 3 produtos, foi retornada no resultado.

`WHERE` é usado antes da agregação, `HAVING` é usado depois. Lembre-se bem dessa diferença.


[Avançado 3] Agrupar por múltiplas colunas

O GROUP BY também permite combinar várias colunas para criar agrupamentos mais detalhados. Por exemplo, "quero saber a quantidade de produtos por combinação de categoria e local de produção".

Basta especificar os nomes das colunas separados por vírgulas na cláusula GROUP BY.

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

Resultado da execução:

category     | prefecture | product_count
-------------|------------|---------------
Periféricos  | Kanagawa   | 1
Periféricos  | Tokyo      | 1
Roupas       | Okayama    | 1
Roupas       | Tokyo      | 2
Cozinha      | Niigata    | 2
Livros       | Tokyo      | 2

Você pode ver que "Roupas em Okayama" e "Roupas em Tokyo" são tratados como grupos separados. Isso permite uma análise mais detalhada, como "existem 2 tipos de produtos de roupas em Tokyo".


[Seção prática] Execute SQL livremente no navegador!

A teoria já foi o suficiente! Agora é sua vez de escrever SQL, executá-lo e verificar os resultados.

Copie todo o código HTML abaixo, salve-o no seu PC com um nome de arquivo como sql_groupby_test.html e abra-o num navegador web. Você terá seu próprio ambiente de execução SQL com a tabela products integrada.

<!DOCTYPE html>
<html lang="pt">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Ambiente de Execução SQL com 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>Experimente SQL no seu navegador!</h1>
  <p>Digite instruções SQL na área de texto abaixo e clique no botão “Executar”. Experimente as consultas apresentadas neste artigo!</p>

  <textarea id="sql-input">-- Mostrar preço médio e preço máximo por prefeitura
SELECT
  prefecture,
  AVG(price) AS "Preço Médio",
  MAX(price) AS "Preço Máximo"
FROM
  products
GROUP BY
  prefecture;</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;
          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 Incrível', 'Roupas', 'Tóquio', 3000, 50),
(2, 'Jeans Perfeitos', 'Roupas', 'Okayama', 12000, 30),
(3, 'Teclado Mágico', 'Acessórios de PC', 'Tóquio', 8500, 20),
(4, 'Mouse Iluminado', 'Acessórios de PC', 'Kanagawa', 4500, 45),
(5, 'Faca Artesanal', 'Utensílios de Cozinha', 'Niigata', 9800, 15),
(6, 'Frigideira Suprema', 'Utensílios de Cozinha', 'Niigata', 6200, 25),
(7, 'Introdução ao SQL', 'Livros', 'Tóquio', 2800, 100),
(8, 'Design Web para Iniciantes', 'Livros', 'Tóquio', 3200, 80),
(9, 'Tênis Confortáveis', 'Roupas', 'Tóquio', 7800, 60);

        `;
        db.run(setupSql);
        
        executeBtn.disabled = false;
        executeBtn.textContent = 'Executar';
        resultOutput.innerHTML = '<p>Pronto! Sinta-se à vontade para experimentar com 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 não retornou resultados (ex: 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 = 'Erro SQL: ' + err.message;
        console.error(err);
      }
    }

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

[Desafio]


Erro comum entre iniciantes: regras da cláusula `SELECT`

Ao usar GROUP BY, um dos erros mais comuns entre iniciantes é: “só é possível colocar na cláusula SELECT colunas que estejam no GROUP BY ou que estejam envolvidas por funções de agregação”.

Por exemplo, o seguinte SQL causará um erro:

-- Exemplo que gera erro
SELECT
  category,
  name -- ← coluna que não está no GROUP BY nem em função agregada
FROM
  products
GROUP BY
  category;

Por que ocorre esse erro? Pense nisso: ao agrupar por GROUP BY category como "Roupas", a coluna name pode conter múltiplos valores como "Camiseta Incrível", "Melhores Jeans" e "Tênis Confortáveis". O mecanismo SQL não sabe qual deles deve mostrar e, por isso, retorna um erro.

Uma linha agregada por GROUP BY representa várias linhas originais. Por isso, só é possível usar na cláusula SELECT colunas que identificam o grupo (neste caso, category) ou resultados de funções de agregação como COUNT(*) ou SUM(price).


Técnica relacionada: ordenando resultados com `ORDER BY`

Frequentemente, você pode querer ordenar os resultados de uma agregação com GROUP BY. Para isso, use a cláusula ORDER BY. Ela deve ser colocada após GROUP BY e HAVING.

Por exemplo, vamos ordenar o número de produtos por categoria em ordem decrescente:

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

Resultado da execução:

category           | product_count
------------------|---------------
Roupas            | 3
Acessórios de PC  | 2
Utensílios de Cozinha | 2
Livros            | 2

Com ORDER BY product_count DESC, ordenamos a coluna product_count de forma decrescente. Assim, fica fácil ver qual categoria tem mais produtos.


Resumo

Neste artigo, aprendemos sobre a poderosa cláusula GROUP BY para agrupar e analisar dados.

Dominar o uso de GROUP BY abre as portas para o mundo da análise de dados. Não se trata apenas de buscar dados, mas de interpretar padrões escondidos. Experimente na seção prática deste artigo e adicione o poderoso recurso de “agregação de dados” ao seu conjunto de habilidades como desenvolvedor web.