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

luddinus's avatar

Can I improve this Query? (USER, Album, Pictures, TAGS)

Hi.

I have users who has many albums, albums has many pictures and pictures has many tags.

I want to get those users who have pictures with some tags given. This is my current query:

$tags_ids = [1, 2, 3];

$filter = function($pictures) use($tags_ids) {
    $pictures->with('tags');

    // I'm doing this because I want to get those pictures which have ALL tags given (tags with ID 1, 2 and 3 in this case)
    foreach ($tags_ids as $tag_id) {
        $pictures->whereHas('tags', function($tags) use($tag_id) {
            $tags->where('tag_id', $tag_id);
        });
    }
};

return User::with(['albums', 'albums.pictures' => $filter]) // get those users which have pictures with the given tags
    ->whereHas('albums', function($albums) use($tags_ids, $filter) {
        $albums->whereHas('pictures', $filter); // load the pictures with the given tags
    });

It works well but it seems I'm repeating code.

0 likes
9 replies
pmall's avatar

Three nested wherehas cant be good. Cant you just go with joins ?

$query = User::select('users.*')
  ->join('albums', ...)
  ->join('pictures', ...)
  ->join('tags', ...);

foreach($tag_ids as $tag_id) $query->where('tag_id', '=', $tag_id);

$users = $query->get();
JarekTkaczyk's avatar

@luddinus Yes, you can.

I suppose MySQL?

// First add this relation to the  User model
public function pictures()
{
  return $this->hasManyThrough('Picture', 'Album');
}

// then use this:
$tags = [1,2,3];

User::whereHas('pictures.tags', function ($q) use ($tags) {
  $q->wherein('tags.id', $tags)->selectRaw('count(distinct tags.id)');
}, '>=', count($tags) )->get();
luddinus's avatar

@JarekTkaczyk That won't work. In addition, I want to get those users with pictures which have ALL the tags requested, not one of them.

@pmall I haven't try yet. The thing is I don't know which SQL I have to create (Eloquent really does a lot of magic...)

pmall's avatar
$query = User::select('users.*')
  ->join('albums', 'users.id', '=', 'albums.user_id')
  ->join('pictures', 'albums.id', '=', 'pictures.album_id')
  ->join('tags', 'pictures.id', '=', 'picture_tag.picture_id');

foreach($tag_ids as $tag_id) $query->where('picture_tag.tag_id', '=', $tag_id);

$users = $query->get();

Here I just assumed you have conventional foreign keys and you have a many to many relationship between pictures and tags with a conventional pivot table. Rename the tables / keys according to your schema.

JarekTkaczyk's avatar

@luddinus You haven't tried that, have you?

Yes it will work, and yes it will search for all the tags not just any of them. That was not not-tested-but-you-can-try- like suggestion.

luddinus's avatar

@JarekTkacyk I'm getting this error

Column not found: 1054 Unknown column 'tags.id' in 'field list' (SQL: select * from users where (select count(distinct tags.id) from pictures inner join albums on albums.id = pictures.album_id where users.id = albums.user_id and tags.id in (1, 2, 3)) >= 3)

If I get this working, the users would have "eager loaded" the pictures with ONLY those pictures with those tags, or I would have those users but loaded with ALL the pictures they have?

JarekTkaczyk's avatar

@luddinus whereHas('pictures.tags') check the edit - my bad, sorry for that.

If you want to load only matching pictures, then you need to duplicate that last condition:

with(['pictures' => function ($q) use ($tags) {
  $q->whereHas('tags', function ($q) use ($tags) {
    $q->wherein('tags.id', $tags)->selectRaw('count(distinct tags.id)');
  }, '>=', count($tags));
}]);

So depending on what you want to achieve, it might be easier to query Picture model instead.

luddinus's avatar
luddinus
OP
Best Answer
Level 6

@JarekTkaczyk thx, finally this is my code:

$tags_ids = [1, 10];
$filter = function($pictures) use($tags_ids) {
    $pictures->whereHas('tags', function($tags) use($tags_ids) {
        $tags->whereIn('tags.id', $tags_ids)->selectRaw('count(distinct tags.id)'); 
    }, '>=', count($tags_ids));
};
$users= User::with(['pictures' => $filter])
    ->whereHas('pictures', $filter)
    ->get();

Please or to participate in this conversation.