我正在使用MYSQL的全文搜索功能(在MYSQL 5.6.33中)。
如果我在自然语言模式下进行匹配,对于一个邮政编码,一个字符的输入错误,我会得到一些不错的结果,包括带有“右”邮政编码的结果,但它们不在顶部。
例如,有10所学校的邮政编码"BN2 1TL"
。我故意将其拼错为"BN2 1TM"
,并按如下方式进行搜索:
SELECT record_id, address_string,
MATCH (address_string) AGAINST ("BN2 1TM" IN NATURAL LANGUAGE MODE) AS score
FROM schools
WHERE MATCH (address_string) AGAINST ("BN2 1TM" IN NATURAL LANGUAGE MODE) > 0
ORDER BY score DESC;
仔细观察,这是因为搜索已经买回了所有
"BN2"
或"1TM"
在他们的address_string
列中的结果,而且他们都有完全相同的分数,所以是随机的,有效的。.这是完全合理的行为,但如果我能把“亲密度”考虑进去,那就太好了,也就是说,搜索
"BN2 1TM"
,"BN2 1TL"
的得分会比"BN2 3PQ"
高。有办法吗?编辑:我记得这种亲密在技术上被称为“Levenshtein距离”,它是一个Levenshtein algorithm的引用,用于确定将一个字符串转换为另一个字符串需要多少替换。所以我想我的问题可能是“我能不能让MYSQL全文自然语言模式评分考虑到Levenshtein距离”?
最佳答案
首先,MySQL-fulltext在开放式搜索方面不如Lucene这样的专用系统。
有一个叫做Levenshtein距离的算法,它计算字符转换的次数——距离——把一个字符串转换成另一个字符串。
因此,将“BN2 1TM”改为“BN2 1MT”(换位)的距离是2。把它改成“BN2 1TX”的距离是1。
Levenshtein距离对于短语来说不是很有用,除非它们几乎完全相同。将“Apache Sphinx”改为“MySQL-FULLTEXT”可以得到14的距离,即较长字符串的长度。但它对邮政编码、零件号和其他短结构单词很有用。
你可以试试这样的方法,先得到最接近的值。
SELECT city, county, postcode
FROM table
ORDER BY levenshtein(postcode, 'BN2 1MT') ASC
然后,您只需要一个存储函数来计算Levenshtein距离。(这不是全文本。)
从this source开始,这里有一个这样的存储函数。但请注意,它不快,不能使用索引。因此,如果在执行此操作之前可以缩小搜索范围,则可以获得更好的性能。
DELIMITER $$
CREATE FUNCTION levenshtein( s1 VARCHAR(255), s2 VARCHAR(255) )
RETURNS INT
DETERMINISTIC
BEGIN
DECLARE s1_len, s2_len, i, j, c, c_temp, cost INT;
DECLARE s1_char CHAR;
-- max strlen=255
DECLARE cv0, cv1 VARBINARY(256);
SET s1_len = CHAR_LENGTH(s1),
s2_len = CHAR_LENGTH(s2),
cv1 = 0x00,
j = 1,
i = 1,
c = 0;
IF s1 = s2 THEN
RETURN 0;
ELSEIF s1_len = 0 THEN
RETURN s2_len;
ELSEIF s2_len = 0 THEN
RETURN s1_len;
ELSE
WHILE j <= s2_len DO
SET cv1 = CONCAT(cv1, UNHEX(HEX(j))), j = j + 1;
END WHILE;
WHILE i <= s1_len DO
SET s1_char = SUBSTRING(s1, i, 1), c = i, cv0 = UNHEX(HEX(i)), j = 1;
WHILE j <= s2_len DO
SET c = c + 1;
IF s1_char = SUBSTRING(s2, j, 1) THEN
SET cost = 0; ELSE SET cost = 1;
END IF;
SET c_temp = CONV(HEX(SUBSTRING(cv1, j, 1)), 16, 10) + cost;
IF c > c_temp THEN SET c = c_temp; END IF;
SET c_temp = CONV(HEX(SUBSTRING(cv1, j+1, 1)), 16, 10) + 1;
IF c > c_temp THEN
SET c = c_temp;
END IF;
SET cv0 = CONCAT(cv0, UNHEX(HEX(c))), j = j + 1;
END WHILE;
SET cv1 = cv0, i = i + 1;
END WHILE;
END IF;
RETURN c;
END$$
DELIMITER ;