I'm working with a tag system for an image repository. I have 3 tables: images
, tags
, and tapMap
.
images
houses the information for the imagestags
houses all the tags (each row is unique)tagMap
stores the association between images and tags
tagMap
looks something like this:
id | img_ID | tag_ID
1 22 51
2 22 55
3 25 55
4 27 56
5 30 70
6 30 71
7 31 70
What I want to do is be able to grab images that have multiple tags. So I could have a query with something like SELECT img_ID FROM tagMap WHERE tag_ID = 51 AND tag_ID = 55
.
The problem is that since each association is stored in a separate row, I can't do that.
Any ideas?
EDIT
To be clear, I only want to select images with all of the passed tags. So the query above should only return img_ID 22.
You can join on the same table more than once:
SELECT i.*
FROM images i
JOIN tagMap tm1
ON tm1.img_ID = i.img_ID
JOIN tagMap tm2
ON tm2.img_ID = i.img_ID
WHERE tm1.tag_ID = '51'
AND tm2.tag_ID = '55'
or, if you'd prefer:
SELECT i.*
FROM images i
JOIN tagMap tm1
ON tm1.img_ID = i.img_ID AND tm1.tag_ID = '51'
JOIN tagMap tm2
ON tm2.img_ID = i.img_ID AND tm2.tag_ID = '55'
You can achieve this through 2 subqueries and a INTERSECT of the two.
SELECT img_ID
FROM tagMap
WHERE tag_ID = 51
INTERSECT
SELECT img_ID
FROM tagMap
WHERE tag_ID = 52;
And if you are doing this in mysql, which doesn't support INTERSECT
, here's one way to do an intersect:
SELECT t3.img_ID from (
(SELECT img_ID FROM tagMap WHERE tag_ID = 51) AS t1
UNION ALL
(SELECT img_ID FROM tagMap WHERE tag_ID = 52) AS t2
) AS t3 GROUP BY img_ID HAVING count(*) >= 2;
Maybe something like:
SELECT img_ID FROM tagMap t1
WHERE tag_ID = 51
AND exists (select 1 from tagmap t2 where t2.tag_ID = 52 and t2.img_ID = t1.img_ID)
Edit: Another option is
select t1.img_ID
from (SELECT * FROM tagMap WHERE tag_ID = 51) t1
inner join (SELECT * FROM tagMap WHERE tag_ID = 52) t2 ON t1.img_id = t2.img_ID
Depending on the number of tags you want to select for and the data volumes one or the other might be easier/faster. For large data volumes in the tables and large numbers of tags to select for, neither are likely to scale well (for each additional tag you want to include as a condition you need an additional sub select in both cases)
Addition: Another possible solution:
SELECT Q.img_id FROM (
SELECT img_ID, count(1) as num
FROM tagMap t1
WHERE tag_ID in (51, 52)
GROUP BY img_id ) Q
WHERE Q.num = 2
I think this one might well scale best. To add extra tags simply add to the in()
condition and increase the number matched by the outer where condition. E.g.
SELECT Q.img_id FROM (
SELECT img_ID, count(1) as num
FROM tagMap t1
WHERE tag_ID in (51, 52, 59)
GROUP BY img_id ) Q
WHERE Q.num = 3
This will return all images that are having multiple tags
//assuming you have id in images table and tags table
SELECT images.id
FROM images
INNER JOIN tagMap
ON tagMap.imgId = images.id
INNER JOIN tags
ON tags.id = tagMap.tag_ID