问题
I have a SQL database in which I store users and tags associated to users (many to many relationship). I have the classic schema with users
table, tags
table and the "bridge" table usertag
which links users with tags:
users table:
+---------+---------+
| Id | Name |
+---------+---------+
| 1 | Alice |
| 2 | Bob |
| 3 | Carl |
| 4 | David |
| 5 | Eve |
+---------+---------+
tags table:
+---------+---------+
| Id | Name |
+---------+---------+
| 10 | Red |
| 20 | Green |
| 30 | Blue |
+---------+---------+
usertag table:
+---------+---------+
| UserId | TagId |
+---------+---------+
| 2 | 10 |
| 2 | 20 |
| 1 | 30 |
| 4 | 20 |
| 4 | 10 |
| 4 | 30 |
| 5 | 10 |
+---------+---------+
Now, I made a query to retrieve all the users and their tags as a comma separated field, using the GROUP_CONCAT()
function:
SELECT u.*, GROUP_CONCAT(ut.tagid) as tags FROM users as u LEFT JOIN usertag as ut ON u.id = ut.userid GROUP BY u.id
which gives me the correct output:
output:
+---------+---------+----------+
| Id | Name | Tags |
+---------+---------+----------+
| 1 | Alice | 30 |
| 2 | Bob | 10,20 |
| 3 | Carl | (null) |
| 4 | David | 10,30,20 |
| 5 | Eve | 10 |
+---------+---------+----------+
The problem is that now I want to implement tag filtering on top of that, i.e. being able to query the users by tag (or multiple tags). The filter should work using the AND operator.
For example: Get users with tag Red (10) AND Green (20):
output:
+---------+---------+----------+
| Id | Name | Tags |
+---------+---------+----------+
| 2 | Bob | 10,20 |
| 4 | David | 10,30,20 |
+---------+---------+----------+
Another example: Get users with tag Red (10):
output:
+---------+---------+----------+
| Id | Name | Tags |
+---------+---------+----------+
| 2 | Bob | 10,20 |
| 4 | David | 10,30,20 |
| 5 | Eve | 10 |
+---------+---------+----------+
Another example: Get users with tag Red (10), Green (20) and Blue (30):
output:
+---------+---------+----------+
| Id | Name | Tags |
+---------+---------+----------+
| 4 | David | 10,30,20 |
+---------+---------+----------+
How can I implement such query? This question on SO is very similar and it actually works but it doesn't deal with the GROUP_CONCAT()
field which is something I'd like to keep as it is
Here the SQL fiddle http://sqlfiddle.com/#!9/291a5c/8
EDIT
One may imagine that this query works:
Retrieve all users with tag Red (10) and Blue (20):
SELECT u.name, GROUP_CONCAT(ut.tagid)
FROM users as u
JOIN usertag as ut ON u.id = ut.userid
WHERE ut.tagid IN (10,20)
GROUP BY u.id
HAVING COUNT(DISTINCT ut.tagid) = 2
Which gives:
output:
+---------+---------+----------+
| Id | Name | Tags |
+---------+---------+----------+
| 2 | Bob | 10,20 |
| 4 | David | 10,20 |
+---------+---------+----------+
which username-wise is correct (Bob and David) but the Tags
field is missing the tag 30 from David's list!
回答1:
left join
the tags
table and include the id's being searched for in the join clause and check for counts in having
.
SELECT u.id,u.name,GROUP_CONCAT(ut.tagid) as tags
FROM users u
LEFT JOIN usertag as ut ON u.id = ut.userid
LEFT JOIN tags t ON t.id=ut.tagid AND t.ID IN (10,20,30) --change this as needed
GROUP BY u.id,u.name
HAVING COUNT(ut.tagid) >= COUNT(t.id) AND COUNT(t.id) = 3 --change this number to the number of tags
One more option is to use FIND_IN_SET
if there are limited values. For example,
SELECT * FROM (
SELECT u.*, GROUP_CONCAT(ut.tagid) as tags
FROM users as u
LEFT JOIN usertag as ut ON u.id = ut.userid
GROUP BY u.id
) T
WHERE FIND_IN_SET('10',tags) > 0 AND FIND_IN_SET('20',tags) > 0
来源:https://stackoverflow.com/questions/44378087/sql-tag-list-and-tag-filtering