This should be common enough and I'm looking for the "best" way to perform this in one SQL query (MySQL).
I have three tables, an items
table, a linker
table and a tags
table. Items can be tagged multiple times, so the linker is a simple foreign key linker table:
items | linker | tags
--------+---------+-------
item_id | item_id | tag_id
... | tag_id | name
--------+---------+-------
I can search items
for single tags easily, how would I go to search items that have 2 or more specific tags?
SELECT *, `tags`.`name`
FROM `items`
LEFT OUTER JOIN `linker` USING (`item_id`)
LEFT OUTER JOIN `tags` USING (`tag_id`)
WHERE `tags`.`name` = "tag-a"
How does a sane person perform search for 2 or more tags, an item must have ALL the tags, ie an AND
query?
Edit: What I have so far is the following, which works and doesn't seem to be slow, but looks crazy:
SELECT `items`.* FROM `items`
LEFT OUTER JOIN `linker` USING (`item_id`)
LEFT OUTER JOIN `tags` USING (`tag_id`)
WHERE (
`item_id` IN (SELECT item_id FROM linker LEFT JOIN tags USING (tag_id) WHERE name = "tag-a")
AND `item_id` IN (SELECT item_id FROM linker LEFT JOIN tags USING (tag_id) WHERE name = "tag-b")
AND `item_id` IN (SELECT item_id FROM linker LEFT JOIN tags USING (tag_id) WHERE name = "tag-c")
AND `item_stuff` = "whatever"
)
Assuming the PK for the linker table is (item_id,tag_id), I would use the following:
select *
from items
where item_id in (
select item_id
from linker
join tags using(tag_id)
where name in ('tag1', 'tag2', 'tag3')
group by item_id
having count(tag_id)=3
)
;
The above query should be easy to maintain. You can easily add or subtract required tag names. You just need to make sure the having count matches the number of names in the list.
If the linker table PK is not (item_id,tag_id), then the having clause would have to change to having count(distinct tag_id)=3
, though that query may not perform so well, depending on how many duplicate (item_id,tag_id) pairs exist.
Another nice feature about the above is you can easily answer questions like, which items are associated with at least 2 of the following list of tags ('tag1','tag2','tag3'). You just need to set the having count to the correct value.
If I understand correctly (which I'm not sure I do :) ... ), you want to find results that contain a certain string (like a regular expression search).
you could try the RLIKE
function
SELECT *, `tags`.`name`
FROM `items`
LEFT OUTER JOIN `linker` USING (`item_id`)
LEFT OUTER JOIN `tags` USING (`tag_id`)
WHERE `tags`.`name` RLIKE("tag-a"|"tag-b")
I think this is what you mean, but maybe not:
http://dev.mysql.com/doc/refman/5.0/en/regexp.html
Or if each entry has only one tag per entry, what about using IN
:
SELECT *, `tags`.`name`
FROM `items`
LEFT OUTER JOIN `linker` USING (`item_id`)
LEFT OUTER JOIN `tags` USING (`tag_id`)
WHERE `tags`.`name` IN ("tag-a","tag-b")
http://dev.mysql.com/doc/refman/5.0/en/comparison-operators.html#function_in
And why not just a basic OR
WHERE `tags`.`name` = "tag-a" OR `tags`.`name` = "tag-b"
I hope I'm understanding your goal correctly, please let me know if I don't.
edit I mis-read a part of your question...I may not be sane, but hope this doesn't disqualify me :P
To restate your question, you want all columns from table items
that have all the tags
in some list, is that correct? If so, I think you need to join to your tags
table for each and use an INNER JOIN
instead of a LEFT OUTER JOIN
. Something like this:
SELECT DISTINCT `items`.*
FROM `items` a
JOIN `linker` b
ON b.item_id=a.item_id
JOIN `tags` c1
ON c1.tag_id=b.tag_id
and c1.name = "tag-a"
JOIN `tags` c2
ON c2.tag_id=b.tag_id
and c2.name = "tag-a"
JOIN `tags` c3
ON c3.tag_id=b.tag_id
and c3.name = "tag-c"
Using an INNER JOIN
will select only rows that have all three tags. I'm not sure how you would do this with a variable number of tags (which I think is what you really want).
Of course this has been asked already: How to filter SQL results in a has-many-through relation
Turns out my interim solution is one of the fastest (number 4 in the linked question), here it is:
SELECT *
FROM `items`
WHERE (
`item_id` IN (SELECT item_id FROM linker INNER JOIN tags USING (tag_id) WHERE name = "tag-a")
AND `item_id` IN (SELECT item_id FROM linker INNER JOIN tags USING (tag_id) WHERE name = "tag-b")
AND `item_id` IN (SELECT item_id FROM linker INNER JOIN tags USING (tag_id) WHERE name = "tag-c")
AND `item_stuff` = "whatever"
)
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.