Laravel Eloquent: Using groupBy() to group results by a column

Updated: January 17, 2024 By: Guest Contributor Post a comment

Introduction

Laravel Eloquent ORM provides a beautiful, simple ActiveRecord implementation for working with your database. Each database table has a corresponding “Model” that is used to interact with that table. In this tutorial, we’ll delve into one of the powerful methods provided by the Eloquent ORM: groupBy().

The groupBy() method is used to group a result set by one or more columns. This method plays a key role in generating summaries from database records and is an essential tool for handling aggregated data.

Prerequisites

  • Basic understanding of Laravel and Eloquent
  • Laravel framework installed on your machine
  • A database connection configured in your Laravel environment

Using groupBy(): The Basics

Let’s start with a simple example. Consider a table named orders where each order is associated with a user_id. To get the number of orders per user, you’d group the results by the user_id column like so:

$orders = Order::select('user_id', DB::raw('count(*) as total'))
               ->groupBy('user_id')
               ->get();

Output:

[
    {"user_id": 1, "total": 10},
    {"user_id": 2, "total": 7},
    ...
]

The above query selects the user_id and a raw count of each user_id occurrence in the orders table, grouping the results by user_id.

Grouping by Multiple Columns

Now let’s say your orders also have a status column, and you want to get a count of each order status per user. You can group by multiple columns like this:

$orders = Order::select('user_id', 'status', DB::raw('count(*) as total'))
               ->groupBy('user_id', 'status')
               ->get();

Output:

[
    {"user_id": 1, "status": "pending", "total": 5},
    {"user_id": 1, "status": "completed", "total": 5},
    {"user_id": 2, "status": "pending", "total": 4},
    ...
]

This query groups the orders first by user_id and then by status, resulting in a count of each type of status for each user.

Advanced Grouping with Having

Sometimes, you might want to filter groups after they have been aggregated. Eloquent’s having() method complements groupBy() here.

$orders = Order::select('user_id', DB::raw('count(*) as total'))
               ->groupBy('user_id')
               ->having('total', '>', 5)
               ->get();

This will fetch all users who have more than 5 orders. The having() method is similar to the SQL HAVING clause.

Grouping with Relationships

If your model has relationships, you can also apply groupBy() in combination with joins. For example, if you want to count the orders for each user and join user information from the users table, you can do the following:

$orders = Order::select('users.name', DB::raw('count(*) as total'))
               ->join('users', 'orders.user_id', '=', 'users.id')
               ->groupBy('users.name')
               ->get();

This query joins the orders table with the users table, groups the results by user names, and counts the orders per user name.

Raw Expressions in Grouping

There might be occasions when you need to use raw expressions inside your groupBy() clause. Eloquent allows you to use DB::raw() for these cases. Here’s how you could group by the month of an created_at timestamp:

$orders = Order::select(DB::raw('YEAR(created_at) as year'), DB::raw('MONTH(created_at) as month'), DB::raw('count(*) as total'))
               ->groupBy(DB::raw('YEAR(created_at)'), DB::raw('MONTH(created_at)'))
               ->get();

This groups the orders by year and month and counts them.

Conclusion

groupBy() can significantly simplify the task of aggregating data based on certain criteria. It allows you to write clean and readable Eloquent queries without getting into the complexity of raw SQL queries. Whether you are grouping by a single column, multiple columns, or using advanced conditions, Eloquent’s groupBy() fits perfectly within the Laravel query building experience.