Reputation: 11920
In my data model, a photo can have many tags associated with it.
So I have my photos
table with two photos.
id | file_name
----+----------------------
1 | DSCN0317.jpg
2 | DSCN0291.jpg
And I have my tags
table with two tags
id | name
----+----------------
5 | Vacation
6 | Work
And I have a mapping table that ties two together.
photo_id | tag_id
----------+--------
1 | 5
2 | 5
2 | 6
So in this example, photo 1 has a single tag (5) and photo 2 has multiple tags (5, 6)
In my app, I need to find all photos with a certain list of tags. More importantly, I need to do it in a single query if at all possible
(the reasoning for why it needs to be a single query is outside the scope of this question, and has to do with the specific implementation of my app).
For example, I want to "find all photos that have both tags 5 and 6"
SELECT DISTINCT "photos".*
FROM "photos"
INNER JOIN "photo_tags"
ON "photo_tags"."photo_id" = "photos"."id"
WHERE "photo_tags"."tag_id" IN (5, 6)
;
But this query is incorrect, because it returns all photos that have tags 6 OR 7. I'm looking for photos that have 6 AND 7.
Is there any way I can transform the above query to do that?
Thanks!
Upvotes: 0
Views: 40
Reputation: 125454
Aggregate the tags into arrays before joining. Then check if the required tags are contained by the array:
select distinct photos.*
from
photos
inner join (
select photo_id as id, array_agg(tag_id) as tags
from photo_tags
group by photo_id
) photo_tags using(id)
where array[5, 6] <@ tags
Upvotes: 1
Reputation: 39527
Try this using aggregation and condition distinct count on the tag_id:
select p.*
from photos p
join (
select
photo_id
from tags
where
tag_id in (5, 6)
group by
photo_id
having
count(distinct tag_id) = 2
) t on p.id = t.photo_id;
Or a correlated query:
select p.*
from photos p
where exists (
select
1
from tags t
where
t.tag_id in (5, 6)
and t.photo_id = p.id
group by
id
having
count(distinct tag_id) = 2
);
Upvotes: 1
Reputation: 24916
You can use EXISTS
, to find photos, that have both ID 5 and ID 6:
SELECT "photos".*
FROM "photos"
WHERE EXISTS (
SELECT "photo_tags"."tag_id"
FROM "photo_tags"
WHERE "photo_tags"."photo_id" = "photos"."id"
AND "photo_tags"."tag_id" = 5)
AND EXISTS (
SELECT "photo_tags"."tag_id"
FROM "photo_tags"
WHERE "photo_tags"."photo_id" = "photos"."id"
AND "photo_tags"."tag_id" = 6)
Upvotes: 1