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

redlik's avatar

Optimising query (loop inside loop)

Is there a better way to optimise 2 loops running, one inside another. I have a list of categories and each category has bunch of topics. I'm pulling all categories belonging to given parent element (subject) and all topics that have relationship to a category.

Inside my view I'm running a loop of categories and inside it I have another foreach loop with if statement checking if topic belongs to given category - if yes - show its details.

I had a look at the queries in debugbar and I can see one query for all topics, then inside a loop another 'topic' query for each category id, 15 queries in total just for topics.

Is there a better way to structure it so I don't have so many queries?

0 likes
8 replies
tykus's avatar

Yes, eager-load the topics associated with the category(ies), you can do this all in a single Eloquent statement assuming the appropriate relationships are present and correct:

$subject = Subject::with('categories.topics')->find($subjectId);

Now, the Subject instance will have a categories property which is a Collection of Category instances; and further, each Category instance will have a topics property which is a Collection of the associated Topic instances. This will result in exactly 3 queries, one for Subject one for all of the Categories, and one for all of the Topics - Laravel will put together the Category instances with their associated Topics.

In your view:

@foreach($subject->categories as $category)
	{{ $category->name }}
	@foreach($category->topics as $topic)
		{{ $topic->name }}
	@endforeach

@endforeach
redlik's avatar

I get that, however I'm not sure how would I display all of them inside a loop. I want to show all categories, and under each one all topics.

tykus's avatar

under each one all topics

Do you mean every topic - not just the topics associated with the current Category? You could make one extra query for all Topics:

$topics = Topic::all();

Then what; you want to display every topic under each displayed Category, and highlight the associated ones? You can check the current Category's topics Collection includes the current topic (from the all topics Collection):

@foreach ($categories as $category)

	@foreach ($topics as $topic)

		{{ $topic->name}} 
		
        @if ($category->topics->includes($topic)) 
			// display a check mark or whatever
		@endif	
		
	@endforeach

@endforeach
1 like
redlik's avatar

I mean to show the appropriate ones - with eager load I'm grabing subject with categories and topics. But in my view do I still have collections $categories and $topics even though I'm not using it in my controller?

tykus's avatar

No, if you were to use the following:

$subject = Subject::with('categories.topics')->find($subjectId);

then everything you need is inside the $subject object, i.e. $subject->categories, and each $category has $category->topics - like I described earlier.

redlik's avatar

All worked fine - I was down to 3 queries - until I've added a tag to count lessons under each category and topic.

I tried using withCount but it doesn't calculate individual categories, only overall number of lessons belonging to subject.

If i do count like this $category->lessons->count() the number of queries jump to 45!

Please or to participate in this conversation.