Laravel搜索功能优化:拼写容错查询问题解决方案咨询
嘿,这个问题太常见了——用户搜东西时手滑打错字简直是日常操作,咱们得让搜索功能更“懂人”一点才行。下面给你几个在Laravel里实现容错拼写匹配的方案,从简单易用到进阶优化都有:
方案1:用SQL的发音匹配函数(快速上手)
SQL自带的SOUNDEX和DIFFERENCE函数可以基于发音来匹配字符串,刚好能解决“拼写错但发音像”的问题:
SOUNDEX会把字符串转换成代表发音的编码DIFFERENCE对比两个字符串的SOUNDEX编码,返回0-4的数值,4代表发音几乎完全一致
你可以直接修改现有的查询,保留原有模糊匹配的同时,加上发音匹配的条件:
use DB; public function search(Request $request){ $query = $request->input('query'); $products = DB::table('products') // 保留原有的模糊匹配逻辑 ->where('product_title', 'like', '%'.$query.'%') // 新增发音匹配:允许相似度≥3(可根据需求调整) ->orWhereRaw("DIFFERENCE(product_title, ?) >= 3", [$query]) ->get(); }
这样像deemoo和Demo、prooductt和Product这类发音相近的错误拼写,就能匹配到目标结果了。
方案2:MySQL全文搜索(适合大数据量场景)
如果你的产品数据比较多,LIKE和发音函数的性能会越来越差,这时候推荐用MySQL的全文搜索功能,它不仅支持拼写容错,还能处理语义相关性,性能也更优。
第一步:给字段创建全文索引
先给products表的product_title字段创建全文索引,可以通过迁移文件或者手动执行SQL:
ALTER TABLE products ADD FULLTEXT INDEX ft_product_title (product_title);
第二步:在Laravel中使用全文搜索
Laravel 5.6+原生支持全文搜索,你可以用Eloquent模型或者DB门面来实现:
// 用DB门面的写法 $products = DB::table('products') ->whereRaw("MATCH(product_title) AGAINST(? IN NATURAL LANGUAGE MODE)", [$query]) ->get(); // 如果用Product模型的写法更简洁 $products = \App\Models\Product::whereFullText('product_title', $query)->get();
全文搜索会自动处理拼写相近的词汇,而且查询效率比LIKE高很多,数据量大的话优先选这个方案。
方案3:基于编辑距离的精准匹配(自定义容错程度)
如果需要精确控制允许多少个拼写错误(比如最多允许2个字符的差异),可以用Levenshtein距离——它计算的是两个字符串之间需要多少次插入、删除或替换操作才能完全一致,数值越小越相似。
注意:部分数据库默认没有这个函数
如果你的MySQL没有LEVENSHTEIN函数,先手动创建它:
DELIMITER // CREATE FUNCTION LEVENSHTEIN(s1 VARCHAR(255), s2 VARCHAR(255)) RETURNS INT DETERMINISTIC BEGIN DECLARE s1_len, s2_len, i, j, c, c_temp INT; DECLARE s1_char CHAR; DECLARE cv0, cv1 VARBINARY(256); SET s1_len = CHAR_LENGTH(s1), s2_len = CHAR_LENGTH(s2); IF s1_len = 0 THEN RETURN s2_len; END IF; IF s2_len = 0 THEN RETURN s1_len; END IF; SET cv0 = 0x00; FOR i FROM 1 TO s2_len DO SET cv0 = CONCAT(cv0, UNHEX(HEX(i))); END FOR; FOR i FROM 1 TO s1_len DO SET s1_char = SUBSTRING(s1, i, 1), cv1 = UNHEX(HEX(i)), j = 1; FOR j FROM 1 TO s2_len DO SET c = IF(s1_char = SUBSTRING(s2, j, 1), 0, 1); SET c_temp = CONV(HEX(SUBSTRING(cv0, j, 1)), 16, 10) + c; IF CONV(HEX(SUBSTRING(cv1, j-1, 1)), 16, 10) + 1 < c_temp THEN SET c_temp = CONV(HEX(SUBSTRING(cv1, j-1, 1)), 16, 10) + 1; END IF; IF CONV(HEX(SUBSTRING(cv0, j+1, 1)), 16, 10) + 1 < c_temp THEN SET c_temp = CONV(HEX(SUBSTRING(cv0, j+1, 1)), 16, 10) + 1; END IF; SET cv1 = CONCAT(cv1, UNHEX(HEX(c_temp))); END FOR; SET cv0 = cv1; END FOR; RETURN CONV(HEX(SUBSTRING(cv0, s2_len+1, 1)), 16, 10); END // DELIMITER ;
然后在Laravel中使用
use DB; public function search(Request $request){ $query = $request->input('query'); $products = DB::table('products') ->select('*', DB::raw('LEVENSHTEIN(product_title, ?) as distance', [$query])) // 允许最多2个字符的错误(可根据需求调整) ->having('distance', '<=', 2) // 把最相似的结果排在前面 ->orderBy('distance') ->get(); }
根据你的需求选就行——如果只是小体量数据,方案1最快;数据多的话方案2性能最优;要是想精确控制允许多少个拼写错误,方案3更灵活。
内容的提问来源于stack exchange,提问作者styloz ashik
相关产品推荐
相关产品推荐

