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

samy_b's avatar
Level 3

Good practice for querying multilevel relationships

Hello,

I'm working on a project that has 3 models:

  • Plane
  • Flight
  • Leg

A plane can have many flights and a flight can have many legs, so they are linked by HasMany / BelongsTo relationships. A flight object can contain some information on the mission, but doesn't have a date per say, the "when" is only determined by the legs (a flight can span across several days).

I need to build a view to show a general planning on at least 120 days.

I tried to implement some method inside the Plane model to get all the flights belonging to a specific plane that would have legs on a specific day, like so:

public function flightsOnDays(Carbon $day);

I did manage to implement this method, and use it inside the planning controller to loop over a given number of days but the loading time of the page is far too slow (more than 10 seconds) and the laravel debug bar shows an important amount of queries, even with eager loading.

Is there a good way / practice that I am completely missing ?

Thanks in advance!

0 likes
8 replies
LaryAI's avatar
Level 58

To optimize querying multilevel relationships in Laravel, especially when dealing with a large dataset over multiple days, you should consider the following strategies:

  1. Eager Loading with Constraints: Use eager loading to reduce the number of queries. You can apply constraints to eager loading to only fetch the necessary data.

  2. Query Optimization: Instead of fetching all flights and then filtering them in PHP, try to filter them directly in the database query.

  3. Database Indexing: Ensure that your database tables are properly indexed, especially on columns that are frequently used in WHERE clauses.

  4. Chunking: If you are dealing with a large dataset, consider using chunking to process records in smaller batches.

Here's an example of how you might implement these strategies in your Plane model:

use Illuminate\Database\Eloquent\Builder;
use Carbon\Carbon;

class Plane extends Model
{
    public function flights()
    {
        return $this->hasMany(Flight::class);
    }

    public function flightsOnDays(Carbon $day)
    {
        return $this->flights()
            ->whereHas('legs', function (Builder $query) use ($day) {
                $query->whereDate('date', $day);
            })
            ->with(['legs' => function ($query) use ($day) {
                $query->whereDate('date', $day);
            }])
            ->get();
    }
}

Explanation:

  • whereHas: This method filters the flights that have legs on the specified day directly in the database query, reducing the amount of data retrieved.

  • with: Eager loads the legs relationship, but only for the specified day, which reduces the amount of data loaded into memory.

  • Indexing: Ensure that the date column in the legs table is indexed to speed up the query.

Additional Tips:

  • Caching: If the data doesn't change frequently, consider caching the results to avoid repeated database queries.

  • Database Optimization: Analyze your database queries using tools like Laravel Debugbar or Laravel Telescope to identify slow queries and optimize them.

By applying these techniques, you should be able to significantly reduce the loading time and improve the performance of your application.

CorvS's avatar

@samy_b Can you show us your code of the flightsOnDays method and your view? If your query takes more than 10 seconds and the debug bar shows more queries than expected, you probably forgot to eager load some relationship.

1 like
Thunderson's avatar

please show us what the result of your query should look like, of course I agree with @corvs.

samy_b's avatar
Level 3

Thanks very much for your replies !

I can't show you exactly what I was refering to in the original post, because I've done it differently in order for it to work, but it's neither elegant nor maintainable. (I implemented this some weeks ago, and I'm posting now cause I'm trying to clean up the code).

In the mean time, the problem has also evolved to be a bit more complex: now, Flights can be of two types (charter or operational) and are linked with Legs via a HasMany / MorphTo relationship. There are also MedevacPeriods that can be assigned to a Plane on a particular day, and MaintenanceSlots that have a start and an end, and are linked to the Plane model with a HaveMany / BelongsTo relationship.

I hope it doesn't look too confusing.

I tried to re-code the part that caused the big amount of queries. So here is the complete code of the Plane model:

And here is how I would like to implement it:

$this->planes = Plane::with([
            'operation_flights',
            'charter_flights',
            'maintenance_slots',
            'medevac_periods',
        ])->get();

        $this->activities = [];

        $operation_flights = OperationFlight::with('legs', 'plane')->get();
        $charter_flights = CharterFlight::with('legs', 'plane')->get();
        $maintenance_slots = MaintenanceSlot::with('plane')->get();

        for($i = 0; $i < $this->numberOfDays; $i++)
        {
            foreach ($this->planes as $plane)
            {
                $this->activities[$plane->id][$i] = $plane->dayActivities($date);
            }
		}

/* Some other logic then loading the view */

But I feel like I'm doing this completely wrong, and the amount of queries generated by the exemple is already through the roof !

Is there a way to cache / eager load differently ?

Thanks in advance :)

Thunderson's avatar

show us the fields or properties do you want to get in the final result. i see where your are a problem

// in dayActivities methods you use 
...
$this->operation_flights() ... 
$this->charter_flights() ...
$this->maintenance_slots() ...
// you have 3 queries here

if timeConsumedOnDay is in a loop of N elements each time this function is called dayActivities is also called. you get N*3 queries. why not use scopes. show us the query you wrote, what result it gives, and what result you would have wanted to get

samy_b's avatar
Level 3

Thanks again for your reply :)

What do you mean by the properties I want to get in the final result ? I'd like to get every field of the model, because I use them to display on the planning.

The quieries I wrote are those in my reply, and the result is excately what I'd like to get, the only problem is that it's far too slow. What's bothering me is I feel like if there was a way to "cache" the result of some queries, the code would be faster, but maybe I'm mistaken.

What are the "scopes" that you are referring to ?

Please or to participate in this conversation.