Query builder GROUP BY, HAVING, COUNT in Laravel

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Mastering Aggregation: GROUP BY, HAVING, and COUNT in the Laravel Query Builder

As developers working with relational databases, mastering SQL concepts like GROUP BY and HAVING is fundamental. When migrating these complex queries into an ORM or Query Builder like Laravel's Eloquent or the fluent Query Builder, it’s easy to run into syntax errors that look like a mismatch between your code and the resulting SQL.

This post dives into the specific challenge you encountered—translating a query involving grouping and filtering aggregates (COUNT) in Laravel 6—and shows you exactly what went wrong and how to fix it correctly.

The SQL Foundation: GROUP BY vs. HAVING

Before we look at the Laravel implementation, let’s quickly review the core concepts from the original SQL query:

sql
SELECT * FROM feedback GROUP BY noTicket having count(`status`) < 2
  1. GROUP BY noTicket: This clause aggregates rows that have the same value in the noTicket column into a single summary row. All other selected columns must either be part of the GROUP BY clause or be an aggregate function (like COUNT(), SUM(), AVG()).
  2. HAVING count(status) < 2: The HAVING clause filters the results after the grouping has occurred. It is used to filter based on the results of aggregate functions.

Why Your Code Failed: The Non-Grouped Column Error

Your attempt to translate this directly into the Laravel Query Builder resulted in an error:

php
SQLSTATE[42000]: Syntax error or access violation: 1055 'sifora.feedback.idFeedback' isn't in GROUP BY

The error message is crystal clear: when you use GROUP BY noTicket, the database only knows how to group by that column. Since your original query used SELECT *, it implicitly tried to select every column, including idFeedback. Because idFeedback was not listed in the GROUP BY clause, the SQL engine rejected the query, as it doesn't know which idFeedback value to return for a group containing multiple rows.

The issue isn't with how you used groupBy() or having(), but rather with what you selected (*) in combination with the grouping instruction.

The Correct Laravel Implementation

To fix this, we must explicitly select only the columns we are grouping by and the aggregate results we need. We should stop using SELECT * when performing complex aggregations.

Here is the correct way to express your logic using the Laravel Query Builder:

php
use Illuminate\Support\Facades\DB;

$results = DB::table('feedback')
    ->select('noTicket', DB::raw('count(status) as status_count')) // Select only what we need
    ->groupBy('noTicket')                                     // Group by the ticket number
    ->having('status_count', '<', 2)                          // Filter the groups based on the count
    ->get();

Breakdown of the Fix:

  1. Explicit Selection (select(...)): Instead of SELECT *, we explicitly select 'noTicket' and use a raw expression (DB::raw('count(status) as status_count')) to calculate the required aggregate value.
  2. Grouping (groupBy()): We correctly apply groupBy('noTicket').
  3. Filtering Aggregates (having()): The having clause now correctly references the alias we created in the select statement (status_count).

This approach ensures that every column returned is either explicitly grouped or derived from an aggregate function, satisfying standard SQL rules and allowing Laravel to successfully build the complex query. This pattern is crucial when moving beyond simple CRUD operations and dealing with data aggregation, a skill essential for building robust applications on platforms like Laravel.

Conclusion: Best Practices for Complex Queries

When working with Laravel's Query Builder, always remember that you are translating structured SQL logic