Laravel: how to express HAVING COUNT in eloquent

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Laravel: How to Express HAVING COUNT in Eloquent Queries

As developers working with relational databases, one of the most common and powerful operations we perform is filtering results based on aggregated data—using the HAVING clause in SQL. When you start using an ORM like Eloquent, translating these complex aggregation filters into fluent, readable code can sometimes feel tricky.

The specific challenge you encountered—trying to use having('count', '=', $tags_amount) and receiving an error about an unknown column—stems from how Eloquent methods map directly to the underlying SQL structure. While Eloquent provides beautiful abstractions for CRUD operations, complex aggregations often require dropping down to the raw power of the Query Builder to handle GROUP BY and HAVING clauses correctly.

This post will walk you through exactly how to express HAVING COUNT logic in Laravel, moving from a failed attempt to a robust, production-ready solution.

Understanding the SQL Requirement

Let's first look at the target SQL structure you were aiming for:

SELECT projects_issues.*
FROM projects_issues
JOIN projects_issues_tags ON projects_issues_tags.issue_id = projects_issues.id
JOIN tags ON tags.id = projects_issues_tags.tag_id
WHERE project_id = '1' AND tags.tag IN ('tagname1', 'tagname2')
GROUP BY projects_issues.id
HAVING COUNT(DISTINCT tags.tag) = 2;

This query requires:

  1. Joins: Linking issues to their tags via the pivot table.
  2. Filtering (WHERE): Restricting the results based on initial criteria (e.g., specific project and tag names).
  3. Grouping (GROUP BY): Grouping the results by the issue ID so we can count the associated tags per issue.
  4. Aggregation Filtering (HAVING): Filtering these groups to only include issues that have a specific number of related tags (in this case, exactly 2).

The Pitfall of Mixing Eloquent and Query Builder

Your attempt involved mixing Eloquent relationships (with('tags')) with manual query builder methods (join, where_in, group_by, having). While possible, when dealing with complex multi-table aggregations that require grouping on joined tables, it is often cleaner and more reliable to construct the logic primarily through the Query Builder.

The error you received, SQLSTATE[42S22]: Column not found: 1054 Unknown column 'count' in 'having clause', clearly indicates that Laravel was trying to apply the having() method to a context where the necessary aggregate column (COUNT(...)) wasn't correctly defined or scoped within the immediate query chain.

The Correct Approach: Mastering the Query Builder

For scenarios involving joins, grouping, and filtering based on aggregates, the most effective approach in Laravel is to start with the DB facade or the Eloquent model's newQuery() method. This gives you full control over the SQL structure.

Here is how you can adapt your requirements into a clean Eloquent query:

use App\Models\ProjectIssue;
use Illuminate\Support\Facades\DB;

class IssueService
{
    public function findIssuesWithSpecificTagCount(int $projectId, array $requiredTags)
    {
        // 1. Start the base query and join necessary tables
        $issues = DB::table('projects_issues')
            ->join('projects_issues_tags', 'projects_issues_tags.issue_id', '=', 'projects_issues.id')
            ->join('tags', 'tags.id', '=', 'projects_issues_tags.tag_id')

            // 2. Apply WHERE constraints (filtering before grouping)
            ->where('projects_issues.project_id', $projectId)
            ->whereIn('tags.tag', $requiredTags)

            // 3. Group the results
            ->groupBy('projects_issues.id')

            // 4. Apply the HAVING constraint (filtering based on aggregate count)
            ->having(DB::raw('COUNT(DISTINCT tags.tag)'), '=', count($requiredTags));

        // 5. Select the desired columns and execute
        return $issues->select('projects_issues.*')->get();
    }
}

Explanation of the Fix

  1. DB::table(...): We start directly with the table we want to select from, which is crucial when performing complex joins that don't map perfectly to a single Eloquent model relationship setup.
  2. join(...): We explicitly define all the necessary joins required by your original SQL structure.
  3. where(...) and whereIn(...): These handle the standard filtering applied before aggregation occurs.
  4. groupBy('projects_issues.id'): This tells the database to aggregate the results based on unique issue IDs.
  5. having(DB::raw('COUNT(DISTINCT tags.tag)'), '=', count($requiredTags)): This is the key step. We use DB::raw() to inject the exact SQL expression (COUNT(DISTINCT tags.tag)) into the HAVING clause, ensuring the database executes the aggregation correctly before filtering the groups.

Conclusion

Expressing complex relational logic like HAVING COUNT in Laravel requires understanding the separation between Eloquent's object-oriented approach and the raw SQL power provided by the Query Builder. For operations involving joins and aggregate filtering, leaning on methods like DB::table() and having(DB::raw(...)) provides the most direct, robust, and maintainable solution. By mastering these tools, you unlock the full potential of Laravel to manage sophisticated data queries efficiently. As we build powerful applications with Laravel, remember that the Query Builder is your Swiss Army knife for any complex database requirement.