Question

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 images
tags 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.

Was it helpful?

Solution

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'

OTHER TIPS

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

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;

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
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top