如何实现实用的网站站内搜索?酒店名称模糊搜索优化求助
针对用户友好型酒店搜索的优化方案
一、解决前缀匹配问题(如"taj mum"匹配"Taj Wellington Mews Mumbai")
你的核心问题是当前布尔模式全文搜索仅支持完整词匹配,需要调整为前缀模糊匹配,同时修复停用词处理的逻辑bug:
1. 修改搜索词生成逻辑
把每个关键词改为+关键词*格式,这样数据库会匹配以该关键词开头的所有词汇;同时修复之前遇到停用词直接终止循环的错误,改为跳过停用词:
$q = sanitize($_POST['search']); // 用正则分割所有非单词字符,适配含连字符/特殊符号的输入 $qParts = preg_split('/\W+/', trim($q)); $fullTextQuery = ''; $filterWords = [ 'the', 'in', 'of', 'from', 'and', 'a', 'an' ]; foreach ($qParts as $key) { $keyLower = strtolower($key); // 跳过空字符串和停用词 if(empty($key) || in_array($keyLower, $filterWords)) { continue; } // 添加前缀通配符,支持部分匹配 $fullTextQuery .= '+'.$key.'* '; } // 处理空搜索场景 if(empty($fullTextQuery)) { $searchQuery = "SELECT name, link FROM hotels WHERE status = 2 LIMIT 10"; } else { // 增加相关性排序,让匹配度高的结果排在前面 $searchQuery = "SELECT name, link, MATCH(name) AGAINST ('$fullTextQuery' IN BOOLEAN MODE) AS relevance FROM hotels WHERE MATCH(name) AGAINST ('$fullTextQuery' IN BOOLEAN MODE) AND status = 2 ORDER BY relevance DESC"; }
2. 调整MySQL全文索引配置
MySQL默认全文索引的最小词长是4,如果你需要支持2-3个字符的前缀(如"mum"),需要修改配置:
- 编辑
my.cnf(或my.ini)文件,添加/修改:ft_min_word_len=2 - 重启MySQL服务
- 重建全文索引:
ALTER TABLE hotels DROP INDEX ft_name; -- 替换为你的全文索引名 ALTER TABLE hotels ADD FULLTEXT INDEX ft_name (name);
二、增强子词匹配能力(可选,适配更灵活的输入)
如果用户可能输入中间片段(如"wellington"的"well"),可以使用MySQL的n-gram全文索引(MySQL 5.7+ / MariaDB支持):
- 创建n-gram索引:
ALTER TABLE hotels ADD FULLTEXT INDEX ft_name_ngram (name) WITH PARSER ngram; - 搜索时直接使用自然语言模式,无需手动添加通配符:
SELECT name, link, MATCH(name) AGAINST ('taj mum' IN NATURAL LANGUAGE MODE) AS relevance FROM hotels WHERE MATCH(name) AGAINST ('taj mum' IN NATURAL LANGUAGE MODE) AND status = 2 ORDER BY relevance DESC;
n-gram会自动将词汇拆分为2个字符的片段(可通过ngram_token_size调整),能匹配任意位置的子词。
三、处理拼写错误问题
对于用户的拼写错误(如"taj mumm"),可以通过编辑距离函数匹配相似词汇:
- 先创建MySQL自定义编辑距离函数:
DELIMITER $$ CREATE FUNCTION LEVENSHTEIN(str1 VARCHAR(255), str2 VARCHAR(255)) RETURNS INT DETERMINISTIC BEGIN DECLARE len1, len2, i, j, cost, min_cost INT; DECLARE str1_char CHAR(1); SET len1 = CHAR_LENGTH(str1); SET len2 = CHAR_LENGTH(str2); IF len1 = 0 THEN RETURN len2; END IF; IF len2 = 0 THEN RETURN len1; END IF; CREATE TEMPORARY TABLE IF NOT EXISTS levenshtein (id INT AUTO_INCREMENT PRIMARY KEY, val INT NOT NULL); INSERT INTO levenshtein (val) VALUES (0); FOR i FROM 1 TO len1 DO INSERT INTO levenshtein (val) VALUES (i); END FOR; FOR j FROM 1 TO len2 DO SET str1_char = SUBSTRING(str2, j, 1); SET min_cost = j; FOR i FROM 1 TO len1 DO SET cost = IF(SUBSTRING(str1, i, 1) = str1_char, 0, 1); SET min_cost = LEAST((SELECT val FROM levenshtein WHERE id = i + 1) + 1, min_cost + 1, (SELECT val FROM levenshtein WHERE id = i) + cost); UPDATE levenshtein SET val = min_cost WHERE id = i; END FOR; UPDATE levenshtein SET val = j + 1 WHERE id = len1 + 1; END FOR; SET min_cost = (SELECT val FROM levenshtein WHERE id = len1 + 1); DROP TEMPORARY TABLE IF EXISTS levenshtein; RETURN min_cost; END$$ DELIMITER ; - 在代码中添加降级逻辑:如果前缀搜索无结果,就用编辑距离查找相似词汇:
// 先执行前缀搜索 $result = mysqli_query($conn, $searchQuery); if(mysqli_num_rows($result) == 0) { // 生成基于编辑距离的查询 $editQuery = "SELECT name, link, LEVENSHTEIN(name, '$q') AS distance FROM hotels WHERE status = 2 ORDER BY distance ASC LIMIT 10"; $result = mysqli_query($conn, $editQuery); }
内容的提问来源于stack exchange,提问作者Whip
相关产品推荐
相关产品推荐

