views:

64

answers:

6

Hello. Say there is such table:

mysql> SELECT * FROM tags;
+---------+--------+
| post_id | tag_id |
+---------+--------+
|       1 |      2 |
|       1 |      3 |
|       1 |      1 |
|       2 |      1 |
|       2 |      2 |
+---------+--------+
5 rows in set (0.00 sec)

Field names are pretty self-explanatory. I want to select post_ids that have both 1 and 3 tag_ids, so in this example it's only 1. I thought of something like SELECT post_id FROM tags GROUP BY post_id HAVING ... After having I'd like to list tag_ids that are present in this group. How do I do that?

+1  A: 

I've made some assumptions about your other tables. (i.e. that you have a table for posts that I have called posts and one with tag_id as the PK which I have called tag_table to avoid a nameclash with the posts/tags table that I can see you already call tags)

You want posts where there does not exist a tag in the list {1,3} for which there does not exist a matching record with the corresponding post_id/tag_id so you can use a double NOT EXISTS construct as below.

SELECT post_id
FROM posts p
WHERE NOT EXISTS 
    (SELECT * FROM tag_table tt
    WHERE tag_id IN (1,3)
    AND NOT EXISTS
        (SELECT * FROM tags t
        WHERE t.tag_id = tt.tag_id  and
        p.post_id = t.post_id)        
    )

Another alternative approach is to use Group By and Count. A review of approaches to this problem is here.

Martin Smith
+1 for the awesomest link)
htf
+2  A: 

You could try a self join (N tag_id -> N join) but probably it's not fast

SELECT t1.post_id 
FROM tags t1 INNER JOIN tags t2 ON t1.post_id = t2.post_id 
WHERE t1.tag_id = 1 AND t2.tag_id = 3
Keeper
+1 but i'll add a select distinct to your request.
Fred
Doesn't really scale for an arbitrary amount of tags though.
Martin Smith
A: 

How about

SELECT * 
FROM tags 
WHERE post_id in 
  (SELECT post_id AS pid 
   FROM tags 
   WHERE 1 IN (SELECT tag_id FROM tags WHERE post_id = pid) 
   AND 3 IN (SELECT tag_id FROM tags WHERE post_id = pid)
  );
Chris
A: 

WHERE version of @Keeper's solution

SELECT DISTINCT t1.post_id 
FROM tags t1, tags t2
WHERE 
  t1.post_id = t2.post_id  AND 
  t1.tag_id = 1 AND t2.tag_id = 3
Amarghosh
Don't think that the second clause of your OR is useful.
Fred
@Fred hmm.. yeah, you're right - will update
Amarghosh
+1  A: 
SELECT post_id
  FROM ( SELECT post_id,
                count(tag_id) AS counter
           FROM tags
          WHERE tag_id IN (1,3)
          GROUP BY post_id
       )
 WHERE counter = 2

Use GROUP_CONCAT() for the second part of your question

SELECT post_id,
       GROUP_CONCAT(tag_id ORDER BY tag_id ASC SEPARATOR ',')
  FROM tags
Mark Baker
+3  A: 

If post_id and tag_id both have an unique constraint, that should work too:

SELECT post_id 
FROM tags 
WHERE tag_id = 1 OR tag_id = 3 
GROUP BY post_id 
HAVING count(*) = 2;

If there aren't any unique constraints try:

SELECT DISTINCT post_id 
FROM tags 
WHERE tag_id = 1 OR tag_id = 3 
GROUP BY post_id 
HAVING count(DISTINCT tag_id) = 2;
rudi-moore
Thanks, I decided to go with `SELECT post_id FROM tags WHERE tag_id IN (1,3) GROUP BY post_id HAVING COUNT(1) = 2;`, which scales well and is the closest to your solution
htf