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

Mick79's avatar

N+1 Error (although code doing what it should)

Scenario: I need to establish a list of all the assets being used in, lets call it am article for the sake of this explanation, and all the assets not being used in article that belong to the user.

So for example, a user has 10 images stored in their account and they create an article that uses 2 of those images. I am trying to create a list that shows the 2 images they have used and the 8 images they haven't.

This code works

// Get all the assets used
$usedassets = UsedAsset::query()->where('token', $token)->get();
// Get the IDs of all the assets used
$usedAssetIDs = collect($usedassets)->pluck('asset_id');

// Get all the assets this user has access to
 $allAssets = Asset::query()->where('token', $user_token)->get();
// Get the IDs of all assets available
$allAssetIDs = collect($allAssets)->pluck('id');

//Set up an empty collection
$unusedAssets = collect();

// in truth I can't make sense of what this is doing....
$commonIds = $usedAssetIDs->intersect($allAssetIDs);
$clashingIds = $usedAssetIDs->merge($allAssetIDs)->diff($commonIds);

foreach($clashingIds as $otherID)
        {
            $ot = Asset::query()->where('id', $otherID)->first();
            $unusedAssets->push($ot);
        }

As I said, this is giving me an N+1 error. The code works but is highly inefficient is my understanding.

Can anyone help me make this better???

0 likes
5 replies
LaryAI's avatar
Level 58

The N+1 error is occurring because the code is making a separate query for each unused asset in the foreach loop. To solve this, we can use eager loading to load all the assets in one query. Here's an updated version of the code that uses eager loading:

// Get all the assets used
$usedassets = UsedAsset::query()->where('token', $token)->get();
// Get the IDs of all the assets used
$usedAssetIDs = collect($usedassets)->pluck('asset_id');

// Get all the assets this user has access to, with eager loading
$allAssets = Asset::query()->with('usedAssets')->where('token', $user_token)->get();
// Get the IDs of all assets available
$allAssetIDs = collect($allAssets)->pluck('id');

//Set up an empty collection
$unusedAssets = collect();

foreach($allAssets as $asset)
{
    // Check if the asset is unused
    if(!in_array($asset->id, $usedAssetIDs->toArray()))
    {
        $unusedAssets->push($asset);
    }
}

In this updated code, we're using eager loading to load all the used assets for each asset in the $allAssets collection. Then, we're looping through each asset and checking if it's unused by checking if its ID is in the $usedAssetIDs array. If it's unused, we add it to the $unusedAssets collection.

This should eliminate the N+1 error and make the code more efficient.

tisuchi's avatar
tisuchi
Best Answer
Level 70

@mick79 How about this?

$usedAssetIDs = UsedAsset::where('token', $token)->pluck('asset_id');

$allAssets = Asset::where('token', $user_token)->select('id')->get();

$allAssetIDs = $allAssets->pluck('id');

$unusedAssetIDs = $allAssetIDs->diff($usedAssetIDs);
$unusedAssets = Asset::whereIn('id', $unusedAssetIDs)->get();
2 likes
Mick79's avatar

@tisuchi Trying this now. Thanks for your time. I'll let you know how it goes.

Mick79's avatar

@tisuchi This worked. Amazing.

And I learned a lot from this! thank you!

tisuchi's avatar

@Mick79 It's vice-versa. I am also learning.

Happy to assist šŸ™‚

1 like

Please or to participate in this conversation.