'SQL - Filtering in many to many relationships

I have an issue coming up with the correct query for filtering in a many-to-many relationship, I think it's best understood through example:

I have four tables: Post, PostTag, Tag, and User, where PostTag is the join table between Posts and Tags. The tags are User-specific - they belong to one user only and shouldn't be accessible by others.

I want to fetch the list of Posts with their Tags, but fetch only the Tags belonging to the logged in User.

SELECT *
    FROM post
    LEFT JOIN post_tag ON post.id = post_tag.post_id
    LEFT JOIN tag ON post_tag.tag_id = tag.id;

Above query returns all the Posts, but it also returns all of the Users tags, which is unwanted. My first instinct was to put it in a where query:

SELECT *
    FROM post
    LEFT JOIN post_tag ON post.id = post_tag.post_id
    LEFT JOIN tag ON post_tag.tag_id = tag.id
WHERE tag.user_id = :user_id;

But the above query basically turns the LEFT joins into INNER joins, and Posts without Tags do not get selected. So I tried putting it into JOIN condition:

SELECT *
    FROM post
    LEFT JOIN post_tag ON post.id = post_tag.post_id
    LEFT JOIN tag ON post_tag.tag_id = tag.id
        AND tag.user_id = :user_id;

The above does return all posts with only logged in user's tags, which is great, but I still have all the post_tags from other users which isn't ideal. So I found a way for excluding those with a simple WHERE:

SELECT *
    FROM post
    LEFT JOIN post_tag ON post.id = post_tag.post_id
    LEFT JOIN tag ON post_tag.tag_id = tag.id
        AND tag.user_id = :user_id
WHERE NOT (post_tag.id IS NOT NULL AND tag.id IS NULL)

The above query seems to work, but I'm not sure if it's the right and the simplest solution.



Solution 1:[1]

Alternative way, avoiding the post_tag junction-table in the result set. (I presume you are only interested in the fields from post and tag, and these already contain the post_id and tag_id columns, though they are both named id):


SELECT *
FROM post p
LEFT JOIN tag t 
        ON t.user_id = :user_id
        AND EXISTS (
                SELECT *
                FROM post_tag pt 
                WHERE pt.post_id = p.id
                AND  pt.tag_id = t.id
                )
        ;

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 wildplasser