问题
I need to make a query to search and filter on multiple terms.
I have a table with weapons and all can have multiple tags. I want to be able to create a filter option for the user that shows only weapons with e.g. 'tag1' AND 'tag2'.
Until now:
I was filtering them using a GROUP_CONCAT
with HAVING
until now but this has performance issues! As I am forced to do a GROUP BY w.id
But I want to do ORDER BY
something else. And GROUP BY doesn't play nicely with ORDER BY....
I tried to create something with EXISTS (SELECT …) to be able to filter on multiple values, however I’m not sure to do this when there is a “join-table” in the middle… (so with 2 left joins)
weapons:
id | name
----------
1 | sword
2 | shield
weapon_tag_links:
tag_id | weapon_id
-------------------
62 | 1
80 | 1
80 | 2
60 | 2
weapon_tags:
tag_id | tag
--------------
60 | red
62 | blue
80 | old
Search query:
SELECT DISTINCT * FROM weapons as w
LEFT JOIN weapon_tag_links AS l ON l.weapon_id = w.id
INNER JOIN weapon_tags AS t ON l.tag_id = t.tag_id
WHERE EXISTS (
****** Something to go here *******
WHERE t.tag = ‘blue’
) AND EXISTS (
****** Something to go here *******
WHERE t.tag = ‘old’
)
I’m just missing the link you need in EXISTS
. But I’m not sure how to add this…
The Question:
Say I want to search for a record in weapons that is blue
AND old
, (a sword in this case) how do I do this?
I'm not saying I MUST use "EXISTS" but I want the best optimised way to search for posts with certain tags connected by AND
!
回答1:
Try this:
SELECT * FROM wp_posts AS p
LEFT JOIN wp_term_relationships AS tr ON p.ID = tr.object_id
LEFT JOIN wp_terms AS t ON tr.term_taxonomy_id = t.term_id
WHERE p.id IN
(
SELECT p2.id FROM wp_posts AS p2
LEFT JOIN wp_term_relationships AS tr2 ON p2.ID = tr2.object_id
LEFT JOIN wp_terms AS t2 ON tr2.term_taxonomy_id = t2.term_id
GROUP BY p2.id
HAVING FIND_IN_SET('blue', GROUP_CONCAT(t2.term)) AND FIND_IN_SET('old', GROUP_CONCAT(t2.term))
)
回答2:
Turn it around. That is start with tag IN ('blue', 'old')
in the innermost subquery. From that, find the tag_ids
. From them, find the weapon_ids
HAVING COUNT(*) = 2
. Last, in the outermost query, get the weapon.name
.
No EXISTS()
. No LEFT JOINs
; I'm not sure you needed LEFT
in the first place.
Your original attempt needed to join to tag
twice with different aliases. I avoid that by using the IN
and HAVING
.
More
Yeah, that gets a bit tricky...
SELECT
FROM
( SELECT weapon_id
FROM weapon_tags
JOIN weapon_tag_links USING(tag_id)
WHERE tag IN ('blue', 'old')
GROUP BY weapon_id
HAVING COUNT(*) >= 2
) a
JOIN weapons w ON w.id = a.weapon_id;
It would be easier if you had not normalized tags.
来源:https://stackoverflow.com/questions/36246152/finding-posts-with-tag1-and-tag2-using-a-join-table-exists-having-subquer