有人能告诉我在数据库中进行此类搜索的方法吗?
我得到了这些表格:
posts (id, tags_cache)
tags (id, name)
posts_tags (post_id, tag_id)
用户输入搜索查询(例如“ water blue ”),我想显示包含这两个标签的帖子。 我能想到搜索的唯一方法是使用 FIND_IN_SET ,这样:
SELECT p.*, GROUP_CONCAT(t.name) AS tags_search
FROM posts p
LEFT JOIN posts_tags pt ON p.id = pt.post_id
LEFT JOIN tags t ON pt.tag_id = t.id
GROUP BY p.id
HAVING FIND_IN_SET('water', tags_search) > 0
AND FIND_IN_SET('blue', tags_search) > 0
posts.tags_cache
文字列存储其所属标记的名称和ID(这样:water:15 blue:20
)。
要使用此列进行搜索以避免加入,我尝试了 LIKE 和 INSTR ,但由于您可以搜索“ ter”,因此会产生不准确的结果“并且您将获得标记为' water '和' termal '的帖子。我也尝试了 REGEXP ,它可以提供准确的结果,但这是一个缓慢的过程。
我不能使用 MATCH ,因为表格使用InnoDB。
那么......是否有其他方法可以实现这一目标?
[编辑]
我忘了提到用户可以搜索许多标签(不仅仅是2个),甚至可以排除标签:搜索帖子标记为'water'而不是'blue'。使用FIND_IN_SET这对我有用:
HAVING FIND_IN_SET('water', tags_search) > 0
AND NOT FIND_IN_SET('blue', tags_search) > 0
[EDIT2]
我做了一些性能测试(即只检查了查询花了多长时间,缓存了),这是ypercube建议的,结果如下:
muists | Bill K | ypercu | includes:excludes
--------------------------
0.0137 | 0.0009 | 0.0029 | 2:0
0.0096 | 0.0081 | 0.0033 | 2:1
0.0111 | 0.0174 | 0.0033 | 2:2
0.0281 | 0.0081 | 0.0025 | 5:1
0.0014 | 0.0013 | 0.0015 | 0:2
我不知道这个信息是否是有效的资源......但是它表明ypercube的方法每个标签的JOIN是最快的。
答案 0 :(得分:4)
我不明白为什么你不想使用JOIN,也不知道你为什么要尝试使用LEFT JOIN。你正在寻找那里的东西(而不是那里),所以摆脱LEFT JOIN并加入。摆脱tags_cache
专栏,你只是在问这类事情。
这就是你要找的东西:
select p.id
from posts p
join posts_tags pt on p.id = pt.post_id
join tags t on pt.tag_id = t.id
where t.name in ('water', 'blue')
group by p.id
having count(t.id) = 2
HAVING子句中的2
是您要查找的标签数。
如果要排除某些标记,可以将其添加到WHERE子句中,如下所示:
select p.id
from posts p
join posts_tags pt on p.id = pt.post_id
join tags t on pt.tag_id = t.id
where t.name in ('water', 'blue')
and p.id not in (
select pt.post_id
from posts_tags pt
join tags t on pt.tag_id = t.id
where t.name in ('pancakes', 'eggs') -- Exclude these
)
group by p.id
having count(t.id) = 2
答案 1 :(得分:3)
查找与不同行中几个条件的所有匹配的帖子是一个常见问题。
以下是两种方法:
SELECT p.*
FROM posts p
INNER JOIN posts_tags pt ON p.id = pt.post_id
INNER JOIN tags t ON pt.tag_id = t.id
WHERE t.name IN ('water', 'blue')
GROUP BY p.id
HAVING COUNT(DISTINCT t.name) = 2;
或者:
SELECT p.*
FROM posts p
INNER JOIN posts_tags pt1 ON p.id = pt1.post_id
INNER JOIN tags t1 ON pt1.tag_id = t1.id
INNER JOIN posts_tags pt2 ON p.id = pt2.post_id
INNER JOIN tags t2 ON pt2.tag_id = t2.id
WHERE (t1.name, t2.name) = ('water', 'blue');
重新评论和编辑:
HAVING解决方案的问题在于它必须执行表扫描,搜索表中的每一行。这通常比JOIN慢得多(当你有适当的索引时)。
为了支持标签排除条件,以下是我的编写方式:
SELECT p.*
FROM posts p
INNER JOIN posts_tags pt1 ON p.id = pt1.post_id
INNER JOIN tags t1 ON pt1.tag_id = t1.id AND t1.name = 'water'
LEFT OUTER JOIN (posts_tags pt2
INNER JOIN tags t2 ON pt2.tag_id = t2.id AND t2.name = 'blue')
ON p.id = pt2.post_id
WHERE t2.id IS NULL;
避免使用JOIN,因为你在某个地方看到它们是坏的是毫无意义的。您必须了解JOIN是关系数据库中的基本操作,您应该在作业调用它的地方使用它。
答案 2 :(得分:1)
对于您的其他请求,不包括某些标记,您可以使用下一种方法。它将为您提供所有包含水和蓝色标签但没有黑色,白色或红色的帖子:
SELECT p.*
FROM posts p
INNER JOIN posts_tags pt1 ON p.id = pt1.post_id
INNER JOIN tags t1 ON pt1.tag_id = t1.id
INNER JOIN posts_tags pt2 ON p.id = pt2.post_id
INNER JOIN tags t2 ON pt2.tag_id = t2.id
WHERE (t1.name, t2.name) = ('water', 'blue') --- include
AND NOT EXISTS
( SELECT *
FROM posts_tags pt
INNER JOIN tags t ON pt.tag_id = t.id
WHERE p.id = pt.post_id
AND t.name IN ('black', 'white', 'red') --- exclude
)