简体   繁体   中英

SELECT matching multiple WHERE criterias (from linking table)

I might be missing something obvious, but I need a query that returns only records that match multiple criteria on the same column.

The table is a simple linking table that links tags to photos, ie a tag can be applied to several photos and a photo can have several tags.

photoid | tagid
----------------
 343    |    2
 343    |    5
 343    |    8
 522    |    5
 522    |    1
 522    |   10
 522    |    8
 118    |    8
 118    |    5
etc...

As seen the photoid 343 has 3 tags.

Now, what I need is a query that gives me the photoid for all photos that have certain tags. The photos shall have either tagid 1 or tagid 2, and shall have both tagid 5 and tagid 8, but must not have tagid 10. Ie something like (tagid=1 OR tagid=2) AND (tagid=5 AND tagid=8) AND tagid!=10 . In the example above, only 343 would match.

I have tried with WHERE IN with GROUP BY and HAVING COUNT

SELECT PhotoTags.photoid, FROM PhotoTags
WHERE (PhotoTags.tagid) IN ((5),(8)) AND (PhotoTags.tagid) NOT IN (10)
GROUP BY PhotoTags.photoid
HAVING count(distinct PhotoTags.tagid)=2

This do only a part of the work, it gives me all photos matching tagid 5 and 8 but not 10. How do I add the AND (tagid=1 OR tagid=2) criteria to the query?

I don't know if this is the best solution, but it seems to work. You can use GROUP_CONCAT to list all tags for a picture, then you can use HAVING to filter based on what is in that value.

SELECT photoid, GROUP_CONCAT(tagid ORDER BY tagid ASC) AS tags
FROM PhotoTags
GROUP BY photoid DESC
HAVING tags RLIKE '(^|,)5,.*8(,|$)'
AND tags NOT RLIKE '(^|,)10(,|$)';

You can also try using FIND_IN_SET instead of a regex ( RLIKE ):

SELECT photoid, GROUP_CONCAT(tagid ORDER BY tagid ASC) AS tags
FROM PhotoTags
GROUP BY photoid DESC
HAVING FIND_IN_SET(5, tags) AND FIND_IN_SET(8, tags)
AND NOT FIND_IN_SET(10, tags);

EDIT : If you want to add the OR clause, try this:

SELECT photoid, GROUP_CONCAT(tagid ORDER BY tagid ASC) AS tags
FROM PhotoTags
GROUP BY photoid DESC
HAVING (FIND_IN_SET(1, tags) OR FIND_IN_SET(2, tags))
AND FIND_IN_SET(5, tags) AND FIND_IN_SET(8, tags)
AND NOT FIND_IN_SET(10, tags);

if you do this

WHERE (PhotoTags.tagid) IN ((5),(8)) AND (PhotoTags.tagid) NOT IN (10)

This AND won't be taken in account because is already checking if the tagId is 5 OR 8

AND (PhotoTags.tagid) NOT IN (10)

IN can be done only comma separate value, remember that AND have priority over the OR, try this and check if it gives you the result you want, it will only select the tagid that are 5 or 8 or 1 or 2

SELECT PhotoTags.photoid, FROM PhotoTags
WHERE (PhotoTags.tagid) IN (5,8,1,2)
GROUP BY PhotoTags.photoid
HAVING count(distinct PhotoTags.tagid)=2

It seems like you want to find all photos that:

  • have at least one of the tags (1,2)
  • do not have tag 10
  • have both of the tags (5,8)

The first requirement can be easily handled with an IN clause and SELECT DISTINCT .

The second requirement is probably best handled using NOT EXISTS .

The third requirement can be handled several ways: with sub-queries, NOT EXISTS , GROUP BY / HAVING , etc. I'll give you an example using sub-queries, but this may not be ideal for you, especially if the number of required tags is not always 2.

Hopefully this gets you started on the right path to a solution that works well for you:

 select distinct t1.photoid 
 from PhotoTags t1
   inner join (
     select distinct photoid 
     from PhotoTags 
     where tagid = 5
   ) t2 on t2.photoid = t1.photoid
   inner join (
     select distinct photoid 
     from PhotoTags 
     where tagid = 8
   ) t3 on t3.photoid = t1.photoid 
 where t1.tagid in (1,2)
   and not exists (
     select NULL 
     from PhotoTags t4
     where t4.photoid = t1.photoid
     and t4.tagid = 10
)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM