part
在数据库查询中,根据用户输入条件进行模糊查询是最常用到的,但是当今的应用中,仅利用数据库的模糊查询远远不够。
但是一般企业也无法使用到类似GOOGLE或BAIDU等搜索引擎的分词方式查询,毕竟建立一个庞大的词库并进行精心的分词算法对很多应用来说是有点大炮打蚊子了。
那么介于数据库的模糊查询和搜索引擎的查询,个人思考用分字查询,既然没有词库,没有算法,那么我们就改进模糊查询,让他更模糊写。
思路:
比模糊更模糊,一般的模糊查询是将用户的输入字符前后加上通配符进行查询,这样可以查询到用户输入字符前后之外的数据如“中文”
那么进行数据库查询的时候变成这样 '%中文%' ,于是搜索到类似 ‘XXX中文XXX’的数据,但是如果用户输入了 “世界 中文”,也只能搜索到“xxxx世界 中文xxxx”的数据,而不能搜索到 “XXX世界XXX”,“XXX中文XXXX”的数据。
因此我们要将用户输入的字符串切割成若干个子字符串。
英文的查询不在话下,根据英文的习惯,词与词之间是有空格作为间隔符号,单中文一句话里词与词是没有字面上的间隔的,另外在没有词库的前提下,我们只好将所有中文字都切割成单个的字符串,然后将用户输入的字符串如“世界hello中文good”整理成类似这样的字符串
“世 界 hello 中 文 good ”
之后,我们将该字符串作为查询参数传递给数据库查询,在数据库方面,要将每个字和英文词作为查询条件,进行查询,这里要用到联合查询得到一个统一的结果集。在查询的同时,分析查询出来的每条数据,分析其内容与用户输入的查询字符串匹配程度进行排序。
如何分析匹配程度,在这里我以数据中包含查询字符串中字符数量作为依据,越多越匹配。
最后,我们就可以获得一个以用户输入的字符串匹配字数最多为顺序的数据集合了。
与搜索引擎匹配词相比,我退而求其次,以匹配字以及字数来进行查询,同样对普通的以数据为基础的应用起到了非常好的效果。
重要代码如下
将字符串拆分成但个中文字和英文单词的数组(英文单词以用户输入空格为准,也就是不做字母分割处理,认为用户自己已给英文单词以空格分开)
public static string[] PartWords(string Querystring)
{
string String4Temp = ""; //临时字符串 存储被分割后的字符串
int chfrom = Convert.ToInt32("4e00", 16); //中文字符起始量
int chend = Convert.ToInt32("9fff", 16); //中文字符结束量
foreach (char c in Querystring)
{
//如果是中文字,前后加空格
if (Char.ConvertToUtf32(c.ToString(), 0) >= chfrom && Char.ConvertToUtf32(c.ToString(), 0) <= chend)
String4Temp += " " + c + " ";
else //如果不是中文字,而是英文字母,则不处理
String4Temp += c;
}
char[] charSeparators = new char[] { ' ', ',' }; //以空格和逗号作为字符串分割符
string[] Words = String4Temp.Split(charSeparators, StringSplitOptions.RemoveEmptyEntries); //将字符串分割并忽略掉空白的项
return Words;
}
将分词后得到的字符串数组再次整理成字符串,以空格间隔开
void ResultBind(string key)
{
DataOperation dataop = new DataOperation();
DataView dv = dataop.SearchResult(key);//将字符串传递给数据库处理获得结果集。
dataop.ParameterClear();
ResultList.DataSource = dv;
ResultList.DataBind();
dataop.Clear();
}
protected void btn_search_Click(object sender, EventArgs e)
{
string key = Functions.SQLTrans(Functions.WordTrans(txt_key.Text.Trim(),TransEnum.Char2Html));//获得用户输入并对输入做字符屏蔽处理
string[] keys = AppClass.PartWords(key);//调用分字,获得分字后的字符串数组
string key2 = string.Empty; //定义另一个字符串用于存储重新组合后的字符
foreach (string s in keys)
{
key2 += s + " " ;
}
ResultBind(key2);
}
数据层代码略,也就是创建个数据库连接和数据命令对象,然后连接数据库,指定存储过程,传递参数,然后执行存储过程。
查询存储过程
ALTER PROCEDURE dbo.ResultSearch
@key nvarchar(500) --参数
AS
declare @kstring nvarchar(500) --定义一个字符串,因为传递进来的参数要保持完整,因此再定义个字符串用来存储传递进来的参数,用来执行循环抽字
declare @okey nvarchar(255) --用于存储没次抽出来的字
declare @qstring nvarchar(4000) --用来存储组合查询语句
SET NOCOUNT ON
set @kstring = @key --获得查询参数
set @qstring= 'select *,INDID as r from[IND] where INDID=0' --为方便组合查询语句,先赋值一条没有作用的查询字符串
--循环组合出一个联合查询的查询字符串,直到字符都被抽离完
while len(@kstring)>0
begin
set @okey = SubString(@kstring,1,charindex(' ',@kstring)-1) --获取第一个空格之前的字符
set @kstring = SubString(@kstring,charindex(' ',@kstring)+1,len(@kstring)) --获取第一个空格之后的字符重新赋值到@kstring中,即 抽出原先参数字符串中已经获取了的字符和与之间隔的空格
set @qstring=@qstring+' union ' --联合查询关键字
set @qstring=@qstring + '(select * ,dbo.Relating(Title,'''+@key+''') as r from [IND] where Title like ''%' + QUOTENAME(@okey) + '%'' )' --构建个子查询 ,以之前抽离出来的字符作为查询关键字,同时为查询出来的每条数据计算权重,用自定义的Relating函数
end
--加上排序条件
set @qstring = @qstring + ' order by r Desc'
--设置好查询截取数量
set rowcount 10
--执行查询
exec(@qstring)
SET NOCOUNT OFF
RETURN
计算权重的Relating函数 标量值函数
ALTER FUNCTION dbo.Relating
(
@title nvarchar(255), --参数1,被计算权重的字符串
@key nvarchar(255) --关键字组合成的字符串
)
RETURNS int
AS
BEGIN
declare @r int --权重,数字类型
declare @kstring nvarchar(255) --用来保存关键字字符串的
declare @okey nvarchar(255) --用来存储抽离出来的字符
set @r = 0
set @kstring=@key
--循环抽离关键字并计算权重
while len(@kstring)>0
begin
set @okey = SubString(@kstring,1,charindex(' ',@kstring)-1) --抽离
set @kstring = SubString(@kstring,charindex(' ',@kstring)+1,len(@kstring)) --抽离后其余的关键字字符串
--如果在被计算的字符串中发现有该关键字,权重加1,否则不操作
if CHARINDEX(@okey,@title)>0
set @r=@r+1
end
RETURN @r --返回权重数字
END
以上就是关键部分的代码。
其中部分注释不太清楚,因为也不太好描述。
存储过程最终没用UNION语法,采用内存表,原理是一样的,将分词后的单个查询条件逐个存储到内存表里,再输出内存表。因为后来还加了很多权重和排序判断,所以用UNION已经不是很直观了,采用内存表比较好操作,效率上也不差,而且分页起来也比较方便。