Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

ctrlaltdelme's avatar

Collections vs Query Builder? When to use each?

I'm progressing on my project and realized I've been using query builder for things that I may have been able to use just Eloquent Collections using my existing relationships.

From what I understand calling a Model relationship like posts() uses Query Builder, but calling posts uses Eloquent and returns a Collection. It seems they both do similar things, so I'm wondering what the actual difference is because I don't understand it from the docs. I need more simple examples. When would you use each and is there a benefit to one over the other?

0 likes
11 replies
tykus's avatar

calling a Model relationship like posts() uses Query Builder, but calling posts uses Eloquent and returns a Collection

Correct. The choice of which method is preferred really depends on the operation(s) you need. Often the Query Builder is a better choice, but sometimes you need all of the related records to be hydrated as a Collection of Eloquent Models.

One reason to use the Query Builder over a Collection is memory usage and operation efficiency. If you get the Collection, PHP needs to iterate over that Collection to perform filtering, sorting, aggregation which will typically be slower and more costly in terms of memory usage.

The Collection approach requires Eloquent to retrieve and hydrate all associated records; whereas, the database is really good at filtering records. So if your operation requires filtering/ordering, then the Query Builder is preferred. Similarly, if you simply want to aggregate some property of the associated records, the database is preferred.

If, however, you need all of the records grouped/partitioned by a particular property, you need the Collection approach.

If you give a concrete example, we can discuss a "preferred" approach

2 likes
ctrlaltdelme's avatar

@tykus Thanks! That helps! Basically anything in any of my Controllers, I have no idea if I'm doing things the "right" way. My biggest so far is this one

It works (minus maybe one piece right now, but I've fixed it in a different commit I havent pushed lol) and doesn't seem to be loading slowly but the verbosity of it isn't the most appealing. Some places I'm using Query Builder and some I'm using Eloquent, but not sure if both are being properly utilized

martinbean's avatar

@ctrlaltdelme Some notes from your code sample:

  1. Selecting specific fields is a micro-optimisation. It barely has any effect on query times to the point that it’s usually not worth it.

  2. Concepts like “newest” and “popular” could be wrapped up into local scopes on your Movie model:

$newestMovies = Movie::query()->newest()->take(5)->get();
$popularMovies = Movie::query()->popular()->take(5)->get();
  1. I have no idea what your popular controller action is doing, given it just returns a JSON response saying success is true. Seems pretty pointless?

  2. Your castAndCrew action should definitely be in a different controller. Most likely the index method of a nested resource controller:

Route::get('movies/{movie}/people', [MoviePeopleController::class, 'index']);
class MoviePeopleController extends Controller
{
    public function index(Movie $movie)
    {
        // Return list of people associated with movie...
    }
}
1 like
ctrlaltdelme's avatar

@martinbean Thank you! I made these changes and the scopes help clean up the Controller a lot. One question about order of operations. Does it matter what order you perform certain methods?

For example (and this may be a bad example), but from the same MovieController as before, but scoped out the latestReviews

public function scopeLatestReviews($query)
    {
        return $query->whereHas('reviews', function ($query) {
            $query->whereNotNull('content');
        })
            ->with([
                'reviews' => function ($query) {
                    $query->latest()->take(1) // Only the latest review for that movie
                    ->with('user');
                },
            ])
            ->latest();
    }

Is there a difference in how it is there versus calling, say, the with first, then the whereHas? I'm sure there is a difference but I just don't know how to read these statements out to myself when writing them. It has been really hard.

martinbean's avatar

@ctrlaltdelme Collections and the query builder aren’t interchangeable at all. You’re comparing apples to oranges.

A collection is a data type. It’s essentially a bag of items, or a fancy array The query builder is for, well, building queries that, when executed, may return a result set as a collection.

You need to be mindful when executing relationship queries. For example, if you have a user who’s published over a 1,000 posts, then you don‘t want to be loading every post they’ve written into memory as a collection, if all you want is the count of their posts:

// Bad, as loads every post
$postsCount = $user->posts->count();

// Better, as only returns the actual of related posts
$postsCount = $user->posts()->count();

But even with this approach, you still need to be mindful of potential N+1 issues. If you have a collection of users, and you want the post count for each, then the above would cause an N+1 issue, so you’d be better off using the dedicated withCount method when retrieving your initial list of users:

// Bad, as has N+1 problem
$users = User::query()->get();

foreach ($users as $user) {
    $postsCount = $user->posts()->count(); // Executes query in each iteration of loop, ergo N+1
}

// Better, as only two queries are generated:
//   1. To get the initial list of users
//   2. To get the counts of posts for all loaded users
$users = User::query()->withCount('posts')->get();

foreach ($users as $user) {
    $postsCount = $users->post_count;
}
3 likes
ctrlaltdelme's avatar

@martinbean This is very helpful! So is there a good rule of thumb when to use either I could try to remember? It seems just using Query Builder and not Eloquent is better in both of your cases you provided because of the N+1 issue lol

martinbean's avatar

@ctrlaltdelme I think you’re misunderstanding. I am using Eloquent in my examples? I didn’t use “Query Builder” and “not Eloquent“.

JussiMannisto's avatar

@ctrlaltdelme You shouldn't think in terms of rules of thumb. Instead, try to understand what query builders and collections are. The best place to learn about them is Laravel's documentation, and @martinbean already gave a good summary, but here's my attempt at explaining it:

  • Query builder is a tool for building database queries, which can then be executed
  • Collection is a list of items

The only connection between the two is that when you execute a SELECT statement using a query builder, the results are often returned in a collection object. If you're fetching a single row, e.g. using ->first(), a single object or null is returned instead of a collection.

I'm not sure which parts you find confusing, but here are some possible sources of confusion:

  1. Laravel's syntax. Calling $user->posts() returns a query builder. It does not execute a database query yet. Calling $user->posts builds and executes a database query, returning the results as a collection. In other words, it's equivalent to $user->posts()->get(). Importantly, when you call $user->posts, it stores the retrieved posts on that model object, and subsequent calls to $user->posts on the same model won't execute additional database queries.

  2. The collection and query builder classes have some methods with the same names, but they are completely different. For example, User::where(...) adds a WHERE SQL condition to the query builder, whereas $users->where(...) filters the $users collection in memory.

  3. There are two query builders: the DB query builder and the Eloquent query builder. Without getting too technical, they're almost the same. Eloquent's builder is the one used through model classes, and it has additional context about the model (such as table name and relations), making it more convenient. The DB query builder is generic and requires you specify everything manually, including table names.

So when should you use $user->posts vs. $user->posts()? It depends on what you're doing. If you want an analogy, a query builder is like a restaurant menu and a collection is like a prepared meal:

  • If you plan to eat one item, you don't order everything on the menu and then pick one item to eat. That would be equivalent to calling $menu->items->where('id', 5)->first(), where you retrieve every single row from the database to memory, and then pick one of them on the PHP side using collection methods. The correct call is $menu->items()->where('id', 5)->first(), where you retrieve a single row from the database.

  • If you plan to order every item on the menu and take a bite of each, you should't place 30 separate orders. That would be equivalent to calling $menu->items()->where('id', $id)->first() in a loop, making a separate database query for each ID. This is an example of the N+1 issue mentioned earlier. Instead, you should retrieve all items at once by calling $menu->items and then loop over them.

In short: retrieve only what you need, and do so in as few database queries as possible. Usually that means a single query per table.

2 likes
ctrlaltdelme's avatar

@JussiMannisto That's absolutely fair. I'm sorry I wasn't able to elaborate on what I was getting confused by. I myself didn't really know either. I just knew I had questions rattling in my head and some general confusion on both.

You answered part of my confusion with your provided list with 1 and 2. As for when to use either, I think it just comes down to knowing when to apply it to my use case and the project I'm currently working on. I have plenty of calls to the Model method (i.e. $user->reviews()) where I can then apply other logic such as ordering, joining with with, etc. and it just seemed that was the dominant use-case in my project and it felt like it never seemed optimal to use $user->reviews for example.

ctrlaltdelme's avatar

@JussiMannisto I'm coming back to this after some time stewing and playing more and I still am not sure I understand when to use either/or. Maybe it's just something intuitive that comes with time and seeing the queries that are made?

I know about Debugbar, but is there another Package that can help me see what DB queries are made? Is Telescope the only one?

Please or to participate in this conversation.