2Lovelaces
From SQL Joins to Laravel Closures: How I Simplified My Queries and My Code

From SQL Joins to Laravel Closures: How I Simplified My Queries and My Code

By Mwangi Brian

When I started working with Laravel, I wrote queries like I was still in pure SQL mode.

They looked something like this:

$projects = DB::table('projects')
    ->join('tasks', 'projects.id', '=', 'tasks.project_id')
    ->join('comments', 'tasks.id', '=', 'comments.task_id')
    ->select(
        'projects.*',
        DB::raw('COUNT(DISTINCT tasks.id) as total_tasks'),
        DB::raw('COUNT(DISTINCT comments.id) as total_comments')
    )
    ->where('projects.status', 'active')
    ->groupBy('projects.id')
    ->get();

It worked — but maintaining these queries became painful. Every time the database changed, I had to edit multiple joins, aliases, and counts.

That’s when I realized Laravel had a cleaner, more expressive way: Eloquent relationships with closures and constraints.


The Turning Point

One day, I asked myself:

“Why am I manually joining tables that already have relationships defined in the models?”

That thought changed everything.

I started defining relationships properly in my models:

// app/Models/Project.php
class Project extends Model
{
    public function tasks()
    {
        return $this->hasMany(Task::class);
    }

    public function comments()
    {
        return $this->hasManyThrough(Comment::class, Task::class);
    }
}


Now, instead of chaining multiple joins, I could simply write:

$projects = Project::query()
    ->withCount('tasks')
    ->withCount([
        'comments as approved_comments_count' => function ($query) {
            $query->where('status', 'approved');
        }
    ])
    ->where('status', 'active')
    ->orderBy('approved_comments_count', 'desc')
    ->get();

Why Closures Changed Everything

That small function ($query) — the closure — became my new best friend.

It allowed me to filter related data directly within relationships without polluting the main query. I could apply any condition inside it — date ranges, user roles, approval status, anything.

For example, to count only completed tasks and sum their total hours:

$projects = Project::withCount([
    'tasks as completed_tasks_count' => function ($q) {
        $q->where('status', 'completed');
    }
])->withSum([
    'tasks as total_hours' => function ($q) {
        $q->where('status', 'completed');
    }
], 'hours')
->get();

This felt cleaner, faster, and easier to read.

Realizing the Power of Constraints

Closures aren’t just for counts — they’re also powerful filters for whereHas queries.

Let’s say I want only active projects with tasks assigned to verified users:

$projects = Project::whereHas('tasks', function ($taskQuery) {
    $taskQuery->whereHas('assignedUser', function ($userQuery) {
        $userQuery->where('verified', true);
    });
})->get();

This would have been a nightmare with raw SQL joins — but with Eloquent, it reads almost like English.


What I Gained by Letting Go of Manual Joins

1. Readability

I stopped scanning lines of joins and conditions. Now, withCount('tasks') tells me exactly what’s happening.

2. Maintainability

When I renamed a column or table, I only needed to update it in one place — the model.

3. Reusability

The same relationships worked in analytics, filters, dashboards — everywhere.

4. Performance

Laravel’s eager loading optimized everything behind the scenes. No more N+1 queries or redundant joins.

5. Confidence

My queries stopped breaking every time I added a new relationship.


The Lesson I Learned

I realized that writing clever SQL doesn’t always mean writing clean code. Closures and constraints gave me the best of both worlds — power and clarity.

Now, every time I start a new Laravel project, I think in terms of relationships, not joins.


My Advice to Developers

If you’re still manually joining tables in Laravel:

Define your relationships first.

Use withCount, withSum, and whereHas.

Experiment with closures to filter related data.

You’ll not only write better code — you’ll actually enjoy reading it later.


Final Thought

Switching from raw SQL joins to Laravel closures didn’t just improve my code — it made me a more expressive developer.

Less syntax. More meaning. That’s what clean Laravel feels like.

Comments

Be the first to share your thoughts!

Leave a Comment