如何操纵 MySQL 全文搜索相关性以使一个字段比另一个字段更“有价值”?
Posted
技术标签:
【中文标题】如何操纵 MySQL 全文搜索相关性以使一个字段比另一个字段更“有价值”?【英文标题】:How can I manipulate MySQL fulltext search relevance to make one field more 'valuable' than another? 【发布时间】:2010-10-07 13:07:52 【问题描述】:假设我有两列,关键字和内容。我对两者都有一个全文索引。我希望关键字中包含 foo 的行比内容中包含 foo 的行具有更高的相关性。我需要做些什么才能使 mysql 将关键字中的匹配权重高于内容中的匹配?
我正在使用“匹配”语法。
解决方案:
能够通过以下方式完成这项工作:
SELECT *,
CASE when Keywords like '%watermelon%' then 1 else 0 END as keywordmatch,
CASE when Content like '%watermelon%' then 1 else 0 END as contentmatch,
MATCH (Title, Keywords, Content) AGAINST ('watermelon') AS relevance
FROM about_data
WHERE MATCH(Title, Keywords, Content) AGAINST ('watermelon' IN BOOLEAN MODE)
HAVING relevance > 0
ORDER by keywordmatch desc, contentmatch desc, relevance desc
【问题讨论】:
【参考方案1】:创建三个全文索引
a) 关键字列上的一个 b) 一个在内容列 c) 关键字和内容列中的一个然后,您的查询:
SELECT id, keyword, content,
MATCH (keyword) AGAINST ('watermelon') AS rel1,
MATCH (content) AGAINST ('watermelon') AS rel2
FROM table
WHERE MATCH (keyword,content) AGAINST ('watermelon')
ORDER BY (rel1*1.5)+(rel2) DESC
关键是rel1
仅在keyword
列中为您提供查询的相关性(因为您仅在该列上创建了索引)。 rel2
做同样的事情,但对于 content
列。您现在可以应用您喜欢的任何权重将这两个相关性分数相加。
但是,您没有使用这两个索引中的任何一个进行实际搜索。为此,您使用位于两列上的第三个索引。
(关键字,内容)上的索引控制您的回忆。也就是返回什么。
两个独立的索引(一个仅针对关键字,一个仅针对内容)控制您的相关性。您可以在此处应用自己的权重标准。
请注意,您可以使用任意数量的不同索引(或者,可能会根据其他因素改变您在查询时使用的索引和权重……如果查询包含停用词,则仅搜索关键字……减少如果查询包含超过 3 个单词...等),则关键字的加权偏差。
每个索引都会占用磁盘空间,因此索引越多,磁盘就越多。反过来,mysql的内存占用更高。此外,插入需要更长的时间,因为您需要更新更多索引。
您应该根据您的情况对性能进行基准测试(小心关闭 mysql 查询缓存以进行基准测试,否则您的结果将出现偏差)。这不是谷歌级别的效率,但它非常简单且“开箱即用”,而且几乎可以肯定比您在查询中使用“喜欢”要好得多。
我觉得效果很好。
【讨论】:
运行良好且有意义。谢谢! 我似乎无法让它工作(可能是因为我没有添加第三个索引),但将 where 条件更改为:rel1 > 0 OR rel2 > 0 解决了我的问题,非常感谢。 @mintywalker 是否应该将 Order By 不是ORDER BY (rel1*1.5)+(rel2) DESC
以获得最高分,从而首先获得更高的相关性?
@PanPipes 是的,它应该是 DESC
,因为更高的相关性是更好的匹配
@mintywalker 我只想说声谢谢,这个确切的查询(适应我们的模式)已经在社区网站上运行了至少五年,其中包含数万篇新闻文章和数百篇成千上万的注册用户(以及更多未注册的访问者)。一直都能很好地满足我们的需求,而且我们从来没有遇到过性能问题。【参考方案2】:
仅使用 2 个全文索引的更简单版本(摘自 @mintywalker):
SELECT id,
MATCH (`content_ft`) AGAINST ('keyword*' IN BOOLEAN MODE) AS relevance1,
MATCH (`title_ft`) AGAINST ('keyword*' IN BOOLEAN MODE) AS relevance2
FROM search_table
HAVING (relevance1 + relevance2) > 0
ORDER BY (relevance1 * 1.5) + (relevance2) DESC
LIMIT 0, 1000;
这将针对 keyword
搜索两个完整索引列,并将匹配的相关性选择到两个单独的列中。我们将排除不匹配的项目(相关性 1 和相关性 2 都为零),并通过增加content_ft
列的权重对结果进行重新排序。我们不需要复合全文索引。
【讨论】:
通过使用“HAVING”而不是 WHERE(使用复合或其他东西),您会遇到必须进行全表扫描才能获得结果的问题。意思是,我不相信这个解决方案可以很好地扩展。更具体地说,在极端情况下,如果您有一个包含 10M 行的表,并且只有 999 个匹配项(或您设置的任何限制的 n-1 个),因为所有行都会在您的查询中返回结果,尽管大多数为 0,您不仅需要加载整个表,还必须遍历所有 10M 行。 @conrad10781 拥有子句仅对匹配的结果集进行操作。 正确,但实际上表中的每条记录都将在该查询中匹配,因为没有任何东西可以过滤它。意思是,您正在 选择 值 来自 表,但没有 where,您正在检索 all记录,然后对它们执行过滤器。为了澄清,请从本地搜索中删除 having 语句。返回所有记录。想象一下,在一个有 10M 记录的表上。运行一个解释,它可能会说使用临时;使用文件排序。 mintywalker 响应中的 where like 允许首先在服务器上过滤记录。 @conrad10781 是的,你是对的 - 如果没有 where 子句,它会扫描整个结果集。这个想法是为了避免复杂的全文索引,这可能会导致密集写入的大量开销。通过在 FROM ... HAVING 之间添加 WHERE 子句可以轻松解决此问题,但是整个查询看起来不再那么简单 + 重复 fullindex 匹配。上面的查询可能适用于小型数据集,例如最多 10k-100k 条记录 - 取决于。【参考方案3】:我需要类似的东西并使用了 OP 的解决方案,但我注意到全文与部分单词不匹配。因此,如果“西瓜”作为单词的一部分出现在关键字或内容中(如 watermelonsalesmanager),则它不匹配,并且由于 WHERE MATCH 不包含在结果中。 所以我玩弄了一下,将 OP 的查询调整为:
SELECT *,
CASE WHEN Keywords LIKE '%watermelon%' THEN 1 ELSE 0 END AS keywordmatch,
CASE WHEN Content LIKE '%watermelon%' THEN 1 ELSE 0 END AS contentmatch,
MATCH (Title, Keywords, Content) AGAINST ('watermelon') AS relevance
FROM about_data
WHERE (Keywords LIKE '%watermelon%' OR
Title LIKE '%watermelon%' OR
MATCH(Title, Keywords, Content) AGAINST ('watermelon' IN BOOLEAN MODE))
HAVING (keywordmatch > 0 OR contentmatch > 0 OR relevance > 0)
ORDER BY keywordmatch DESC, contentmatch DESC, relevance DESC
希望这会有所帮助。
【讨论】:
【参考方案4】:在布尔模式下,MySQL 支持“>”和“
我想知道这样的事情是否可行?
SELECT *,
MATCH (Keywords) AGAINST ('>watermelon' IN BOOLEAN MODE) AS relStrong,
MATCH (Title,Keywords,Content) AGAINST ('<watermelon' IN BOOLEAN MODE) AS relWeak
FROM about_data
WHERE MATCH(Title, Keywords, Content) AGAINST ('watermelon' IN BOOLEAN MODE)
ORDER by (relStrong+relWeak) desc
【讨论】:
【参考方案5】:几年前我做过这个,但没有全文索引。我手头没有代码(前雇主),但我记得很清楚这种技术。
简而言之,我从每一列中选择了一个“权重”。例如:
select table.id, keyword_relevance + content_relevance as relevance from table
left join
(select id, 1 as keyword_relevance from table_name where keyword match) a
on table.id = a.id
left join
(select id, 0.75 as content_relevance from table_name where content match) b
on table.id = b.id
请原谅这里的任何伪劣 SQL,我已经好几年没有写任何 SQL 了,而且我正在做这件事...
希望这会有所帮助!
J.Js
【讨论】:
【参考方案6】:嗯,这取决于你到底是什么意思:
我想在关键字中有 foo 的一行 比一行更相关 foo 在内容中。
如果您的意思是关键字中包含 foo 的行应该出现在 any 内容中包含 foo 的行之前,那么我将执行两个单独的查询,一个用于关键字,然后(可能是懒惰的,仅在请求时)内容上的另一个关键字。
【讨论】:
【参考方案7】:其实用case语句来做一对flag可能是更好的解决方案:
select
...
, case when keyword like '%' + @input + '%' then 1 else 0 end as keywordmatch
, case when content like '%' + @input + '%' then 1 else 0 end as contentmatch
-- or whatever check you use for the matching
from
...
and here the rest of your usual matching query
...
order by keywordmatch desc, contentmatch desc
同样,这仅适用于所有关键字匹配的排名高于所有仅内容匹配的情况。我还假设关键字和内容的匹配是最高排名。
【讨论】:
使用 like 语句不是运行搜索的好方法。首先,除非您拆分字符串,否则您只会按照确切的顺序进行匹配。即搜索LIKE '%t-shirt red%'
将不会匹配您数据库中的“红色 T 恤”。其次,由于 LIKE 会进行全表扫描,因此执行查询的时间会更长。
@ChrisG LIKE
用于FROM
子句而不是SELECT
时会进行全表扫描【参考方案8】:
如果指标只是所有关键字匹配比所有内容匹配更“有价值”,那么您可以只使用带有行数的联合。类似的东西。
select *
from (
select row_number() over(order by blahblah) as row, t.*
from thetable t
where keyword match
union
select row_number() over(order by blahblah) + @@rowcount + 1 as row, t.*
from thetable t
where content match
)
order by row
对于比这更复杂的事情,如果您想对每一行应用实际重量,我不知道如何提供帮助。
【讨论】:
我试过这个,结果出现语法错误。我不认为我知道在 blahblah 点的订单中放什么。有什么建议吗? 对不起,这并不是一个复制粘贴示例。 over 子句中的 order by 是您应用行号的顺序,因此它应该是您通常对结果进行排序的任何顺序。 现在想想,这个会复制同时匹配关键字和内容的记录。 我无法找到任何方法来完成这项工作。其实我觉得mysql不支持row_number【参考方案9】:据我所知,MySQL全文搜索不支持此功能,但您可以通过在关键字字段中多次重复该单词来达到效果。 不要使用关键字“foo bar”,而是使用“foo bar foo bar foo bar”,这样 foo 和 bar 在关键字列中同样重要,并且由于它们出现多次,因此它们与 mysql 更相关。
我们在我们的网站上使用它并且它有效。
【讨论】:
以上是关于如何操纵 MySQL 全文搜索相关性以使一个字段比另一个字段更“有价值”?的主要内容,如果未能解决你的问题,请参考以下文章