我正在为任意文本/短语实现类似autosuggest的内容。我想向用户提供一些绝对相关性,而不仅仅是相对于彼此的已找到项目的排名。 即,如果DB中的某些行包含确切形式和顺序的输入文本,我需要在“1”附近排名。
例如,让我们搜索“我喜欢水果”:
这可以用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实际上不是那么全文,因为我无法弄清楚它是否完全匹配。
答案 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。