前段时间看过一个帖子,是关于关键字模糊匹配的问题,最近我在做一个项目,刚好也需要这个功能,所以就把这个功能给完成了。在这里把我的思路分享给大家 如果有兴趣的朋友,请认真看完我下面的陈述,由于我的能力所限,其中涉及的有些问题,我是暂时忽略掉的,也希望就一些要点大家可以一起讨论。 |
例子:比如有关键字“打篮球” 数据库里的有四条记录,其中某个字段的值分别为“打篮球”,“篮球”,“打球”,“球”。 1、希望通过关键字,列出这四条记录。 2、匹配关键字最多的记录列在前面(比如,值是“打篮球”的记录就必须列在“篮球”和“打球”的前面,这就是记录的价值性),同时对记录集进行分页显示。 要只做到第一条并不难,主要第二条,思考:该如何来处理价值性顺序的问题呢? |
一、关键字如何分割 |
汉语中,有这么一个规律,对于一个复合名词或者动宾结构的短语,一般来说,其中连在一起的几个字往往就是一个完整的名词或动词。比如 “打篮球”,其中“篮球”是一个名词,“打”是动词,“球”是名词。当然,这个例子会出现“打球”也是一个完整的动宾结构的现象,但 对于站内搜索,也没必要考虑处理自然语义的问题,所以,这里采取关键字的分割只是一种简单的遍历。也就是,3个3个的取,2个2个的取,1个1个的取, 且取出来的词都是连在一起的。例如“打篮球”可以分割成“打篮球”,“打篮”,“篮球”,“打”,“篮”,“球”一共6个关键字。
之后考虑价值性的问题,很显然,如果能完全匹配“打蓝球”,那么该条记录的价值性是最高的,如果匹配了“篮球”,那么价值性要比匹配 “打篮球”的低,这样依次类推,也就是能够匹配关键字的字数越多,显然价值性就越高。当然,还有实际的情况是“打球”的价值性要比“篮子”的高,但是如果按如山方法来分割, 那么“打球”的价值性和“篮子”就变成一样的了。这个其实就是处理语义的问题,这里还是忽略掉(如果这个我也能处理好,那我就取百度了……)。
这部分处理关键字分割的代码如下: |
DIM SearchKey,keyLength,key,i,j SearchKey="打篮球" keyLength=Len(SearchKey)
FOR i=1 TO keyLength FOR j=keyLength-i+1 TO 1 STEP -1 key=Mid(SearchKey,i,j) '截取关键字 NEXT NEXT
|
'如此,就依次获得了关键字“打篮球”,“打篮”,“篮球”,“打”,“篮”,“球” | |
二、记录集的排序以及分页 |
关键字价值性问题解决后,就要处理怎样让记录来按价值性的高低来排序,同时仍旧要处理分页。关于这点补充一些知识。 |
1、脱机记录集 对于刚接触asp或者未仔细学习过ADO的朋友,并不会知道脱机记录集是什么。所谓脱机记录集就是,通过断开与数据库 的连接来操作记录的集合。此记录集是保存在客户端的,即游标的操作是在客户端进行的,并不在服务器端的数据库上进行。脱机记录集的好处是减轻服务器端 处理数据的压力。 2、临时表 在使用脱机记录集的情况下,如果向该记录集中添加或修改记录,并且不马上重新连接数据库更新数据,此时的记录集就是临时表。 对临时表的更改不会影响到服务器端数据库里的数据,只有当用UpdateBatch提交后,才会全部更新。 3、操作脱机记录集和临时表的代码如下:
DIM conn,rs SET conn=Server.CreateObject("Adodb.Connection") conn.ConnectionString="......." conn.CursorLocation=adUseClient '必须把Connection的CursorLocation属性设置成adUseClient(值为3) conn.Open()
SET rs=Server.CreateObject("Adodb.RecordSet") rs.Open strSql,conn,adOpenStatic,adLockBatchOptimistic,adCmdText '必须把RecordSet的CursorType属性设置成adOpenStatic(值为3),把LockType属性设置成adLockBatchOptimistic(值为4) SET rs.ActiveConnection=NOTHING '最后把RecordSet的ActiveConnection对象设置成NOTHING以断开与数据库的连接
| |
回到主题上,我们就要利用脱机记录集和临时表的特性来处理排序和分页的问题。大致思路如下: 1、依次对每个分割好关键字进行模糊查询,用"LIKE '%"&key&"%'" 2、没进行一次查询,把查询获得的记录集添加到临时表中 3、执行完所有查询后,针对临时表来进行分页处理 关于记录重复必须注意的细节:假如字段的值为“打篮球”,那么在每次查询中,这条记录都会被查询到,所以在进行每次查询时必须把之前已经查询到的记录给过滤掉, 如此可以用"NOT id IN ("&之前已经查询到的记录的id值集合&")" |
三、准备工作 |
需要获得脱机记录集,还需要有个前提工作,就是在数据库建立一个表,该表的所有字段定义和需要查询的那个表一样,但是,不要把id设置成自动编号,设置成整形或长整形即可。 例:有如下表定义 TABLE user (name AS varchar(255),info AS text,id AS int) 建立一个空表 TABLE temp_user (name AS varchar(255),info AS text,id AS int) 其中info字段是需要进行模糊匹配查询的字段 |
四、完整代码 |
DIM conn,rs,strSql,temp_rs DIM SearchKey,keyLength,key,i,j,ids SET conn=Server.CreateObject("Adodb.Connection") conn.ConnectionString="......." conn.CursorLocation=adUseClient conn.Open()
strSql="SELECT * FROM temp_user" '查询空表temp_user来获得临时表 SET temp_rs=Server.CreateObject("Adodb.RecordSet") temp_rs.Open strSql,conn,adOpenStatic,adLockBatchOptimistic,adCmdText SET temp_rs.ActiveConnection=NOTHING
SearchKey="打篮球" keyLength=Len(SearchKey) SET rs=Server.CreateObject("Adodb.RecordSet")
ids="0" '已经查询到的记录的id值 FOR i=1 TO keyLength FOR j=keyLength-i+1 TO 1 STEP -1 key=Mid(SearchKey,i,j) '截取关键字
strSql="SELECT * FROM user WHERE NOT id IN ("&ids&") AND info LIKE '%"&key&"%'" rs.Open strSql,conn,adOpenFowardOnly,adLockReadOnly,adCmdText
DO UNTIL rs.Eof temp_rs.AddNew() temp_rs("name")=rs("name") temp_rs("info")=rs("info") temp_rs("id")=rs("id") '假如temp_user的id字段仍旧是自动编号的话,这样就不允许赋值了 ids=ids+","+cstr(rs("id")) '把已经查询到的记录的id值保存到集合内 rs.MoveNext() LOOP
rs.Close() NEXT NEXT
'TODO 对temp_user进行分页处理,并列出数据
SET rs=NOTHING temp_user.Close() SET temp_user=NOTHING conn.Close() SET conn=NOTHING | |