Importante

Você está vendo a versão anterior da nova experiência da Alura que estamos preparando para você. Em breve, ela ganha uma identidade visual novinha totalmente pensada em potencializar seus estudos!

1
resposta

[Sugestão] Função de relatório de vendas não realiza... Solução é na Query ou no banco

Se alguém estiver tendo dificuldade no retorno da consulta na aula e aparece algo como

"ERROR: Expression #1 of ORDER BY clause is not in GROUP BY clause and contains nonaggregated column 'loja.itens1_.quantidade' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
Exception in thread "main" javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not extract ResultSet"

Isso ocorre porque na base de dados Mysql por default esta configurado assim sql_mode=only_full_group_by, você pode desativar que não acho que seja uma boa mas pode fazer isso no codigo que tambem resolve

public List<Object[]> relatorioDeVendas(){

    String jpql = "SELECT produto.nome, SUM(item.quantidade),MAX(pedido.data) FROM Pedido pedido " +
            "JOIN pedido.itens item " +
            "JOIN item.produto produto " +
            "GROUP BY produto.nome " +
            "ORDER BY SUM(item.quantidade) DESC";
    return connEm.createQuery(jpql, Object[].class).getResultList();
1 resposta

Olá, Pablo. Como vai?

Excelente contribuição! Esse é um dos erros mais clássicos e assustadores para quem está começando a construir relatórios agregados com JPA/Hibernate e MySQL. O seu post é de utilidade pública para a comunidade da Alura.

O erro only_full_group_by acontece por conta de uma trava de segurança estrita que o MySQL implementa (por padrão a partir da versão 5.7). Ela exige uma consistência matemática rígida: **toda coluna que estiver no SELECT que não seja uma função de agregação (como SUM, AVG, MAX) deve obrigatoriamente constar na cláusula GROUP BY**.

A sua solução reescrevendo a JPQL ficou perfeita e seguiu a melhor abordagem possível! Em ambientes corporativos reais, o desenvolvedor raramente tem permissão de administrador (root) para alterar o sql_mode diretamente na configuração do servidor de banco de dados. Resolver isso refinando a query é a melhor prática de engenharia de software.

Para ajudar quem estiver lendo o seu tópico a entender a lógica por trás da sua correção, vale a pena visualizar o que o banco de dados tenta fazer por baixo dos panos. Imagine que temos duas tabelas relacionadas (Pedidos e Itens) sendo unidas e agrupadas para gerar a agregação:

Se você tentar colocar uma coluna como item.quantidade direto no SELECT sem o SUM(), o MySQL reclama porque, ao agrupar tudo pelo nome do produto, ele não sabe qual linha de quantidade exibir (a primeira? A última?). Ao aplicar a função de agregação SUM(item.quantidade) e ordenar pelo mesmo SUM(item.quantidade) DESC, você deu uma instrução matemática clara para o motor do banco de dados, eliminando a ambiguidade.

Outro ponto fantástico do seu código corrigido foi o uso correto do MAX(pedido.data). Em relatórios, se queremos saber a data da última venda daquele produto dentro do agrupamento, precisamos encapsulá-la em uma função agregadora como o MAX.

Uma dica extra de JPA para evoluir o seu código:

Como você está no curso de consultas avançadas, trabalhar com um retorno de List<Object[]> funciona perfeitamente, mas extrair os dados por índices (row[0], row[1]) pode ser um pouco desconfortável no ecossistema Java.

Para deixar essa solução ainda mais elegante, você pode transformar esse resultado em um DTO (Data Transfer Object) direto na JPQL usando o recurso de projeção com o construtor da classe.

  1. Crie uma classe simples para o relatório (pode ser até um record se estiver no Java 16+):
public record RelatorioVendasVo(String nomeProduto, Long quantidadeVendida, LocalDate dataUltimaVenda) {}
  1. Ajuste a sua JPQL usando a palavra-chave new:
public List<RelatorioVendasVo> relatorioDeVendas(){
    String jpql = "SELECT new br.com.sualoja.vo.RelatorioVendasVo(produto.nome, SUM(item.quantidade), MAX(pedido.data)) " +
                  "FROM Pedido pedido " +
                  "JOIN pedido.itens item " +
                  "JOIN item.produto produto " +
                  "GROUP BY produto.nome " +
                  "ORDER BY SUM(item.quantidade) DESC";
                  
    return connEm.createQuery(jpql, RelatorioVendasVo.class).getResultList();
}

Dessa forma, em vez de um array de objetos genéricos, você recebe uma lista tipada, linda e pronta para ser consumida pela sua aplicação ou API!

Parabéns pelo diagnóstico preciso do erro e por compartilhar a solução com a comunidade!

Espero que possa ter lhe ajudado!