Laravel — Eager Load only one record for one-to-many relationship

Photo by Evan Krause on Unsplash

When developing application, it is rather common that we need to load only one relation object even though the table relationships are one-to-many. For instance, most of the time we only need the latest status if a record has many historical statuses.

In that case, we can actually use hasOne relation method instead of hasMany, provided we add the correct ordering. Laravel will be smart enough to load only the first relation object.

// HasMany
public function statuses()
{
return $this->hasMany(Status::class);
}
// HasOne
public function latestStatus()
{
return $this->hasOne(Status::class)->latest();
}

Then, based on our needs, we could eager load either all statuses or only the latest status.

// Eager Load array of statuses object
Model::with('statuses')->get();
// Eager Load only the latest status
Model::with('latestStatus')->get();

What if we need more advanced selection conditions? We can still modify the relations and eager-load them, as long as we use query builder method within the relationship call.

As an example, if an User can have many Avatar images, it is common that our application allows User to select one to show as default, or else take the latest uploaded Avatar.

Instinctively, we might define the relation this way:

public function getDefaultAvatar()
{
$defaultAvatar = $this->avatars()->whereDefault(true)->first();
return $defaultAvatar ?? $this->avatars()->latest()->first();
}

Besides the fact that this is making 2 database calls, this relation could not be eager loaded. When we attempt to call getDefaultAvatar on a list with multiple Users, this could result in 2N+1 database calls and potentially hampers the application performance.

Instead, the above relation could be rewritten in this way:

public function defaultAvatar() {
return $this->hasOne(Avatar::class)
->orderBy('selected', 'desc')
->latest();
}
// Eager Load
User::with('defaultAvatar')->get();

By using the above, whenever we eager load defaultAvatar relation, Laravel will attempt to load the User’s Avatar where selected=true, or if not found, return the latest Avatar available.

In short, we could achieve a significant performance boost by fitting the logic within a single Laravel relation call and Eager Load it.

The example provided here is implemented in Laravel as it’s the framework I’m most familiar with, but the concept should be applicable to any other application with relational databases.

Thanks very much for reading through! Feel free to leave any comment or contact me on weihien90@gmail.com

Web Developer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store