Laravel count column of related model

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Laravel Eloquent: Counting Related Model Data Efficiently

As developers working with relational databases in Laravel, one of the most common tasks we face is calculating aggregates—counting, summing, or averaging data across related models. The scenario you presented, needing to count upvotes and downvotes for a specific script, is a perfect illustration of how Eloquent relationships can be leveraged, but it also highlights where performance optimization comes into play.

This post will walk you through the different ways to approach this problem, comparing simple eager loading against leveraging database aggregation methods, which is the most performant solution.

The Setup: Understanding the Relationships

Let’s first review the structure you've established. You have a one-to-many relationship between Script and ScriptRating, with the pivot table (script_rating) holding the actual vote data.

Models:

  • Script: Has many ScriptRating records.
  • ScriptRating: Belongs to a Script.

The key to counting votes lies in aggregating the rating column in the script_rating table, grouped by script_id.

Method 1: Eager Loading vs. Direct Counting

You asked about the difference between loading the relationship using with() and simply accessing it, and how this relates to counting.

A. Accessing the Relationship (Fetching Details)

When you use $script->ratings, Eloquent executes a separate query to fetch all related ScriptRating records for that script. This is excellent for displaying all the individual ratings:

$script = Script::where('title', '=', $title)->get();
// Accessing the relationship loads the collection of related models
{{ $script->ratings }} // Returns [{"id":1,"script_id":1,"rating":1}, ...]

B. Eager Loading with with() (Fetching Data)

When you use $script = Script::where('title', '=', $title)->with('ratings')->get();, Eloquent performs two queries: one for the Script and a second query to fetch all related ScriptRating records. This is highly efficient when you need to display nested data, as it minimizes the number of database round trips compared to loading them individually in a loop.

The Catch: While with('ratings') loads the data, it does not automatically provide the sum or count directly on the parent model. You still have to iterate over the loaded collection in PHP to get the total:

$script = Script::where('title', '=', $title)->with('ratings')->first();

// Manual counting in PHP (less efficient for large datasets)
$upvotes = 0;
foreach ($script->ratings as $rating) {
    if ($rating->rating === 1) {
        $upvotes++;
    }
}

Method 2: The Eloquent and Database Way (Aggregation)

For counting, the most efficient approach is to let the database handle the aggregation using SQL's SUM() function. This avoids fetching potentially thousands of rows into PHP memory just to count them. This aligns perfectly with best practices in data-intensive applications like those built on Laravel.

Using Aggregate Functions

You can use Eloquent's query builder methods combined with sum() or count() directly on the relationship, or by performing a direct join:

Option 1: Using withSum (The Easiest Way)
If you want to load the script and its aggregated rating totals in a single query, use withSum():

$script = Script::where('title', '=', $title)
    ->withSum('ratings', 'rating') // Calculates the sum of the 'rating' column from the related ratings
    ->first();

// Accessing the result:
// $script->ratings_sum will contain the total sum of all ratings.

Option 2: Using Custom Scopes (The Most Flexible Way)
For highly specific counts, like counting only upvotes or downvotes (where rating is 1 or -1), creating custom local scopes on your models is a powerful technique. This encapsulates complex database logic cleanly and makes your code extremely readable.

Your provided scope examples are excellent:

public function scopeTotalRating($query, $scriptId) {
    // Calculates the sum of all ratings for a specific script ID
    return $query->where('script_id', $scriptId)->sum('rating');
}

public function scopeThumbsUp($query, $scriptId) {
    // Filters for rating=1 and sums those results
    return $query->where('script_id', $scriptId)->where('rating', 1)->sum('rating');
}

public function scopeThumbsDown($query, $scriptId) {
    // Filters for rating=-1 and sums those results
    return $query->where('script_id', $scriptId)->where('rating', -1)->sum('rating');
}

To use these scopes, you would call them on the relevant model:

// To get total upvotes for a script ID:
$upvotes = ScriptRating::where('script_id', $script->id)->thumbsUp()->first()->sum; 
// Note: The exact syntax depends on how you structure your final query, but the principle is to apply the calculated logic directly to the relationship.

Conclusion

When dealing with relational data in Laravel, remember that the database is your most powerful tool. Avoid loading unnecessary data into PHP memory just to perform simple calculations.

For retrieving full related records, use with('relationship'). For calculating aggregates like total upvotes or downvotes, utilize Eloquent's relationship methods like withSum() or define custom scopes. This approach ensures that your application remains fast, scalable, and adheres to the principles of efficient data retrieval taught by frameworks like Laravel. Keep building those powerful relationships!