PHP+MySQL数据库之中文全文检索解决方案

来源:互联网 发布:vb下载官方 编辑:程序博客网 时间:2024/06/10 15:16

由于MYSQL仅支持英文的全文索引FULLTEXT,不支持中文,因为中文不能像英文那样通过空格来准确的判断单词,而需要通过语义来判断,这就需要我们对中文进行切词。

但是我们可以通过另一种方式来曲线解决这一问题。


项目需求:

1.根据关键词搜索内容,

2.搜索结果排序按匹配度降序排列(类似于搜索引擎)


一般情况下我们做搜索的时候是对关键词进行分词后,采用LIKE或REGEXP的方式检索,但是这样在效率上是非常低的,特别是数据量大了过后,并且需求2基本上就很难实现了

如果通过MATCH的方式检索就会非常的快,也能根据匹配度进行排序,但正如前面说到MYSQL对中文是不支持的,所以即便你将字段设置为FullText也不行的。


所以我们的解决方案来了,我们可以将需要搜索的内容通一个定方法转换为英文字符组,就可以使用MATCH特性了。。 如:

手机钱包为何迟迟没能在全球范围普及?

对应字符编码:

E6898BE69CBA E69CBAE992B1 E992B1E58C85 E58C85E4B8BA E4B8BAE4BD95 E4BD95E8BF9F E8BF9FE8BF9F E8BF9FE6B2A1 E6B2A1E883BD E883BDE59CA8 E59CA8E585A8 E585A8E79083 E79083E88C83 E88C83E59BB4 E59BB4E699AE E699AEE58F8A E6898B E69CBA E992B1 E58C85 E4B8BA E4BD95 E8BF9F E6B2A1 E883BD E59CA8 E585A8 E79083 E88C83 E59BB4 E699AE E58F8A

我今天将手机放在钱包里了。

对应字符编码:

E68891E4BB8A E4BB8AE5A4A9 E5A4A9E5B086 E5B086E6898B E6898BE69CBA E69CBAE694BE E694BEE59CA8 E59CA8E992B1 E992B1E58C85 E58C85E9878C E9878CE4BA86 E68891 E4BB8A E5A4A9 E5B086 E6898B E69CBA E694BE E59CA8 E992B1 E58C85 E9878C E4BA86

如果我们现在要搜索“手机钱包”,并要需要同时返回包含 手机和钱包 的两条数据。 

并且有“手机钱包”这个关键词的排前面的话。就可以如下写了:

SELECT *,(MATCH (subject) AGAINST ('E6898BE69CBA E69CBAE992B1 E992B1E58C85 +E6898B +E69CBA +E992B1 +E58C85' IN BOOLEAN MODE) ) AS rate FROM kx_goods_search WHERE (MATCH (subject) AGAINST ('E6898BE69CBA E69CBAE992B1 E992B1E58C85 +E6898B +E69CBA +E992B1 +E58C85' IN BOOLEAN MODE) ) ORDER BY rate DESC

这样就实现我们的全部要求了。

注意:

E6898BE69CBA E69CBAE992B1 E992B1E58C85 +E6898B +E69CBA +E992B1 +E58C85

为“手机钱包”进行分词后对应的字符编码,其中包含这么几个词

Array(    [0] => 手机钱包    [1] => 手机    [2] => 机钱    [3] => 钱包)

当然这个分词不是特别准确,但是实现一般的网站需求还是足够了。


下面是所有演示代码:

class FullText {public function __construct(){}/** * 将中文转换为字母 *  * @param string $subject * @param string $tg * @param string $deep * @param string $split * @return Ambigous <string, mixed> */public static function change_chinese($subject, $tg = '', $deep = false, $split = false){$subject = strtolower($subject);preg_match_all("/([\x{4e00}-\x{9fa5}]+)/u",$subject,$matchs);$words = self::pad_two_words($matchs[0],$deep);preg_match_all("/([\x{4e00}-\x{9fa5}]{1})/u",$subject,$matchs);$words = array_merge($words,$matchs[0]);preg_match_all("/([a-z0-9]+)/", $subject, $matchs);$words = array_merge($words,self::pad_word($matchs[0]));preg_match_all("/([a-z]+)|([0-9]+)/",$subject,$matchs);$words = array_merge($words,self::pad_word($matchs[0]));$words = array_unique($words);foreach($words as $s => $v){if($v == 'both'){$words[$s] = 'b__h';} elseif ( $tg == '+' && mb_strlen($v) <= 4 ){$words[$s] = '!'.$v;}}$outstr = urlencode(implode(' ',$words));if( $tg == '+' ){$outstr = str_replace(array('+', '%21', '%'), array(' ', '+', ''), $outstr);} else {$outstr = str_replace('%', '', $outstr);$outstr = $tg.str_replace('+', ' ', $outstr);}return $outstr;}/** * 分离中文 *  * @param unknown $subject * @param string $deep * @return multitype: */public static function split_chinese($subject,$deep = false){$words = array();preg_match_all("/([\x{4e00}-\x{9fa5}]+)/u",$subject,$matchs);$words = $matchs[0];$words = array_merge($words,self::pad_two_words($matchs[0],$deep));preg_match_all("/([a-zA-Z]+)|([0-9]+)/",$subject,$matchs);$words = array_merge($words,$matchs[0]);return $words;}/** * 二元组合文字 *  * @param unknown $words_arr * @param string $deep * @return Ambigous <multitype:, multitype:string unknown > */public static function pad_two_words($words_arr,$deep = false){$words = array();$single = '';foreach($words_arr as $k){$len = strlen($k)/3;if($len > 1){for($i = 0;$i < $len - 1; $i++){$words[] = substr($k,$i*3,6);}if($deep && $len == 3){$words[] = substr($k,0,3).substr($k,6,3);}}else{$words[] = $k;$single .= $k;}}$singlen = strlen($single)/3;if($singlen == 1){$words[] = $single;}elseif($singlen > 1){$adds = self::pad_two_words(array($single));$words = array_merge($words,$adds);}return $words;}/** * 填充关键字 * @param string $str_arr * @param boolean $end_text * @return string */public static function pad_word($str_arr, $end_text = true){foreach($str_arr as $s => $v){if(strlen($v) < 4){$v = str_pad($v, 4, '_');$str_arr[$s] = $v;} else {$end_text && $str_arr[$s] = $v.'_';}}return $str_arr;}/** * 添加测试数据 * @param int $gid //商品ID * @param string $subject //商品名称 */public static function insert( $gid, $subject ){$data = array();$data['gid'] = $gid;$data['subject'] = self::change_chinese($subject);$data['src_subject'] = $subject;DB::insert('goods_search', $data, false, true);}/** * 搜索 *  * @param string $keyword //搜索关键词 *  */public static function search( $keyword ){$keystr = self::change_chinese( $keyword, '+');echo "SELECT * FROM {TPRE}goods_search WHERE (MATCH (subject) AGAINST ('{$keystr}' IN BOOLEAN MODE) )";$query = DB::query("SELECT * FROM {TPRE}goods_search WHERE (MATCH (subject) AGAINST ('{$keystr}' IN BOOLEAN MODE) )");while( $arr = DB::fetch_array( $query ) ){print_r( $arr );}}}FullText::search( '手机钱包' );


0 0