我可以在[0;中获得MySQL FULLTEXT排名吗? 1]范围?

时间:2013-10-17 21:56:58

标签: mysql search full-text-search range rank

我正在为任意文本/短语实现类似autosuggest的内容。我想向用户提供一些绝对相关性,而不仅仅是相对于彼此的已找到项目的排名。 即,如果DB中的某些行包含确切形式和顺序的输入文本,我需要在“1”附近排名。

例如,让我们搜索“我喜欢水果”:

  • 包含内容的行“肯定我喜欢水果和蔬菜”我希望排名 1 或少一点。
  • 对于内容为“我喜欢新鲜水果”的行,我希望结果小于1但仍然非常高,例如 0.7

这可以用MySQL和FULLTEXT完成吗? 而不是[0,1]我得到的值如2.7或1.2甚至0.6为完全匹配。它出什么问题了?

这是我的测试表:

表格数据

id  text
1   Lorem ipsum dolor
2   You can search an index, and organize and present search results.
3   The Search API can index any number of documents.
4   Each field has a name and a type.
5   Each field is required.
7   Cras dapibus. Vivamus elementum semper nisi. 
8   Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. 
9   Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. 
10  Nam eget dui. Etiam rhoncus. 

查询1:

SET @str := 'Lorem ipsum dolor';
SELECT id, TEXT, MATCH (TEXT) AGAINST (@str) rank FROM test WHERE MATCH (TEXT) AGAINST (@str);

- >

 id text    rank
 1  Lorem ipsum dolor   1.280059814453125

这个'1.28'是什么意思?很高兴在这里拥有'1'。

查询2:

SET @str := 'Each field is required.';
SELECT id, TEXT, MATCH (TEXT) AGAINST (@str) rank FROM test WHERE MATCH (TEXT) AGAINST (@str); 

- >

 id text    rank
 5  Each field is required. 1.7639520168304443
 4  Each field has a name and a type.   0.8533731698989868

还有一场比赛,我在这里也期待'1'。

查询3:

SET @str := 'Aenean leo ligula, porttitor eu';
SELECT id, TEXT, MATCH (TEXT) AGAINST (@str) rank FROM test WHERE MATCH (TEXT) AGAINST (@str);

- >

id  text    rank
8   Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim.  3.5851094722747803
9   Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi.     0.4266865849494934

同样完全匹配,并返回神奇的'3.58'。

我不需要精确的数字,只需要[0,1]之间的数字,这样我就可以确定是否存在完全(或几乎完全)的匹配。如果结果完全包含搜索字符串,我需要大约0.8..1的东西。或者它可能只是一个错误的工具? FULLTEXT实际上不是那么全文,因为我无法弄清楚它是否完全匹配。

2 个答案:

答案 0 :(得分:3)

MySQL使用an n-dimensional vector product with some fudge factors为全文匹配生成相关值,这意味着除了给定的数据集和查询之外,这些值无法以一般方式进行规范化。 (那么,为什么你需要它们呢?对同一数据集的类似查询已经产生了类似的相关值,不管怎样,不同查询的结果之间的标准化也无济于事。)

也就是说,在查询返回的结果集中,使用每行的简单计算,没有什么可以阻止你自己规范排名值:

row_normalized_rank = row_returned_rank / highest_returned_rank

这将需要对结果集进行两次扫描,一次用于识别返回的最高排名值,另一次用于将每行的排名值与最高返回值进行标准化;你可以用一个充满神经色的嵌套查询来做到这一点,但你最好在代码中做这件事。

您也可以自己添加一个软糖因子,以提高完全匹配的排名;考虑绝对等级调整,例如:

SELECT id, text, (MATCH (text) AGAINST (@str) +
                  IF(text LIKE CONCAT("%", @str, "%"), 1, -1)) AS rank
FROM test
WHERE MATCH (text) AGAINST (@str);

或扩大幅度如:

SET @fudge := 2;
SELECT id, text, (MATCH (text) AGAINST (@str) *
                  IF(text LIKE CONCAT("%", @str, "%"), @fudge, 1/@fudge)) AS rank
FROM test
WHERE MATCH (text) AGAINST (@str);

当然,调整品味,但这有助于为您提供更多类似于您在排名行为方面所寻求的东西。

答案 1 :(得分:3)

看起来只能用MySQL将等级标准化为[0,1]是不可能的。通过归一化到[0,1],我的意思是为(几乎)完全匹配的行获得接近1的值,而不仅仅是对于具有最高等级的行。例如,如果我搜索"one apple, two oranges",我可能会收到"one two three"这样的单行。使用公式rank=row_rank/highest_rank在这种情况下我会得到rank = 1。但这不是一场完整的比赛。我宁愿期望价值在0.5左右或更低(找到一半的搜索词)。

我也研究过Lucene和Sphinx。几乎没有笨蛋,但似乎rank = 1的定义可能完全取决于应用程序要求。例如,如果搜索的短语完全包含在DB中,我需要rank = 1,但是当搜索的短语与DB中的整个内容匹配时,有人可能期望它为1。


所以,我通过三个步骤 来解决这个问题:

<强> 1。使用FULLTEXT和Aaron建议的x2/fudge方法从数据库中获取排名前100位的行

SELECT id, TEXT, (MATCH (TEXT) AGAINST (@str) *
                  IF(TEXT LIKE CONCAT("%", @str, "%"), 2, 1)) AS rank
FROM test
WHERE MATCH (TEXT) AGAINST (@str) ORDER BY rank DESC LIMIT 100;

这样做可以检索大多数相关行,减少下一步的数据量。事实上,MySQL的排名值完全被忽略了。

<强> 2。对于100行中的每一行,根据应用程序要求(如[0,1]范围)

,在Java / Groovy端以编程方式计算规范化排名

这非常具有挑战性,但我能够基于简单的数学公式和很少的规则创建相对简单的算法。经过一些优化后,计算所有100行的排名需要大约6ms。

第3。按新排名对结果进行排序,向用户显示前10个结果。

我没有显示排名&lt;的结果0.5,并且在UI中我另外强调具有高等级(0.8-1)的结果

我测试了它并且效果很好。但是,对于某些情况,从步骤#1开始的FULLTEXT搜索根本不会返回结果。当搜索短语通常存在于DB中时会发生这种情况,但很少有单词结尾不同。我的排名算法可能会将其评估为0.3-0.7等级,但它不是来自第1步。所以,现在我将继续采用这种方法,但后来可能会考虑在步骤#1中用其他东西(可能是Lucene)替换MySQL。