I asked an earlier question but kind of messed it up, so I'm reformulating it again here.
I have 3 related tables:
media
media_tags
(bridge table)tags
Here's the code I have so far:
SELECT m.media_id, m.name, array_agg(distinct t.tag) filter (WHERE t.date_deleted IS NULL) AS tags
FROM media m
LEFT JOIN media_tags mt USING (media_id)
LEFT JOIN tags t USING (tag_id)
WHERE m.date_deleted IS NULL AND t.tag = ANY(array['dog','cat'])
GROUP BY m.media_id, m.name
This is getting very close to what I want, but it's not quite correct. Imagine there are these media records:
My query above does return Media 1, 2 & 3 but the tags shown only consist of dog
& cat
. What I want is for tags
to always contain ALL of its tags if there is any match with any of the values in the array. So, if ['dog','cat'] are in the query, then Media 1, 2, & 3 should be returned with all of their respective tags, but Media 4 should not because there's no match.
I sense that I'm pretty close to a solution but just can't figure it out.
The ANSI standard way of doing this (sans the use of Postgres arrays) would be to first find all matching tags, then use that result to restrict your current query.
WITH cte AS (
SELECT m.media_id
FROM media m
INNER JOIN media_tags mt ON mt.media_id = m.media_id
INNER JOIN tags t ON t.tag_id = mt.tag_id
WHERE t.tag IN ('dog', 'cat')
GROUP BY m.media_id
HAVING COUNT(DISTINCT t.tag) = 2
)
SELECT m.media_id, m.name, ARRAY_AGG(DISTINCT t.tag) FILTER (WHERE t.date_deleted IS NULL) AS tags
FROM media m
INNER JOIN media_tags mt ON mt.media_id = m.media_id
INNER JOIN tags t ON t.tag_id = mt.tag_id
WHERE m.media IN (SELECT media_id FROM cte)
GROUP BY m.media_id, m.name;