???????? 在CMS開發中,經常會有類似這樣的需求:
???????? 提問——回答模式,最經典的例子就是百度提問。
???????? 提問者提出問題,由其他人回答,其他人可以是用戶,也可以是服務商。
???????? 在這個模式中,如何充分利用歷史數據是最關鍵的技術。很多時候,由于客戶不擅長使用搜索功能,一上來就提問,而這些問題往往早已經有近乎完美的答案,但沒有充分利用。這樣一來,不僅加大了勞動量,又增加了數據冗余。
???????? 如果在提問的時候能充分調動歷史數據,提交問題之前先看看歷史問題能不能解決客戶疑問,解決了,最好不過,解決不了,再提交。百度提問就是采用的這種方案:
???????? 模式固然好,可怎么實現就有些困難了,畢竟這是百度作為搜索引擎的看家本領。
???????? 從上圖可以看出“CSDN網站如何注冊用戶”這句話被拆成了N個詞,然后分開去數據庫中匹配,為什么?因為直接去匹配“CSDN網站如何注冊用戶”這句話,漢語博大精深,稍微變動一下:“如何在CSDN網站注冊用戶”,意思完全一樣,但直接匹配絕對匹配不到!
???????? 因此,我們需要把一句話拆分成詞組,這個在網上有現成的組件,比如“庖丁解牛”等,它們大多數是免費開源的。拆成詞組之后,應該還要有一個關鍵詞篩選詞庫,用這個詞庫確定出有效的詞組,比如上圖中,“CSDN”、“注冊”、“用戶”是有效的,而“網站”顯然沒有匹配,因為它在這句話中沒有實際意義。
???????? 有點跑題了,拆詞、選詞不是本文的重點,但卻是本文的前提。拿到關鍵詞之后,怎么去數據庫中匹配呢?
???????? 大家都知道T-SQL中的LIKE語句,通過類似LIKE “%abc%”這樣的語法,可以進行模糊匹配,但是它僅僅能進行一次模糊匹配。舉個例子:
???????? 假如我們確定了a,b,c三個關鍵詞,要查找的記錄當然是匹配的越多越好,于是可以這樣寫:LIKE “%a%b%c%”,這樣匹配出的就是包含a,b,c三個關鍵詞的記錄,但是如果根本沒有包含這三個關鍵詞的記錄,最多只有包含兩個的,甚至是只包含一個,那么如何寫LIKE語句呢?這樣LIKE “%a%b%”?這樣LIKE “%a%c%”?這樣LIKE “%b%c%”?這樣LIKE “%a%”?這樣LIKE “%b%”?這樣LIKE “%c%”?
???????? 顯然,需要判斷的情況太多,簡單的LIKE語句已經無法滿足需求。需要注意的是,千萬不要試圖選出范范的記錄,返回到程序中去處理,在程序中處理雖然簡單,但是范范的記錄,在一個中型系統中,往往能達到千萬級別,這么大的數據量,從數據庫返回到程序,無疑會給服務器造成相當大的壓力。
???????? 經過探索,本小菜總結了一個比較簡單的方法,暫且稱為“LIKE語句多條件貪婪匹配算法”。
???????? 算法思想: 先用LIKE選出每一組符合一個條件的記錄,只選擇表的主鍵。然后把這些記錄合并在一起,通過主鍵分組、統計數量,數量最多的,也就是匹配最多的,最后根據數量降序排序,越靠上的記錄,匹配的越多。選出匹配的多的記錄主鍵字段,再根據主鍵去表中選出內容即可。
???????? 為了方便大家使用,已經把算法封裝成存儲過程(直接把下邊代碼在查詢分析器中執行即可)。
存儲過程(函數)如下:
GO CREATE function Get_StrArrayLength ( @str varchar(1024), --要分割的字符串 @split varchar(10) --分隔符號 ) returns int as begin declare @location int declare @start int declare @length int set @str=ltrim(rtrim(@str)) set @location=charindex(@split,@str) set @length=1 while @location<>0 begin set @start=@location+1 set @location=charindex(@split,@str,@start) set @length=@length+1 end return @length end GO CREATE function Get_StrArrayStrOfIndex ( @str varchar(1024), --要分割的字符串 @split varchar(10), --分隔符號 @index int --取第幾個元素 ) returns varchar(1024) as begin declare @location int declare @start int declare @next int declare @seed int set @str=ltrim(rtrim(@str)) set @start=1 set @next=1 set @seed=len(@split) set @location=charindex(@split,@str) while @location<>0 and @index>@next begin set @start=@location+@seed set @location=charindex(@split,@str,@start) set @next=@next+1 end if @location =0 select @location =len(@str)+1 --這兒存在兩種情況:1、字符串不存在分隔符號 2、字符串中存在分隔符號,跳出while循環后,@location為0,那默認為字符串后邊有一個分隔符號。 return substring(@str,@start,@location-@start) end GO CREATE PROCEDURE proc_Common_SuperLike --要查詢的表的主鍵字段名稱 @primaryKeyName varchar(999), --要查詢的表名 @talbeName varchar(999), --要查詢的表的字段名稱,即內容所在的字段 @contentFieldName varchar(999), --查詢記錄的個數(TOP *),匹配的個數越多,排名越靠前 @selectNumber varchar(999), --匹配字符分隔標記 @splitString varchar(999), --匹配字符組合字符串 @words varchar(999) AS declare @sqlFirst varchar(999) declare @sqlCenter varchar(999) declare @sqlLast varchar(999) BEGIN set @sqlCenter='' declare @next int set @next=1 while @next<=dbo.Get_StrArrayLength(@words,@splitString) begin --構造sql查詢條件(中間部分) set @sqlCenter = @sqlCenter+'SELECT '+@primaryKeyName+' FROM '+@talbeName+' WHERE '+@contentFieldName+' like ''%'+dbo.Get_StrArrayStrOfIndex(@words,@splitString,@next)+'%'' UNION ALL ' set @next=@next+1 end --處理sql語句中間部分,去除最后無用語句 set @sqlCenter=left(@sqlCenter,(len(@sqlCenter)-10)) --構造sql語句開頭部分 set @sqlFirst='SELECT TOP '+@selectNumber+' '+@primaryKeyName+',COUNT(*) AS showCout FROM (' --構造sql語句結尾部分 set @sqlLast=') AS t_Temp GROUP BY '+@primaryKeyName+' ORDER BY showCout DESC' --拼接出完整sql語句,并執行 execute(@sqlFirst+@sqlCenter+@sqlLast) END
調用示例:
executeproc_Common_SuperLike 'id','t_test','content','20','|','i|o|c'
???????? id表的主鍵字段名稱。
???????? t_test表名。
???????? content匹配內容字段名稱。
???????? 20選出20個記錄(從頂至下匹配度越來越低)。
???????? |關鍵字的分隔符號。
???????? i|o|c一共有i,o,c三個關鍵字,通過|分隔。
?
???????? 注意這個存儲過程選出來的是匹配度高的記錄的主鍵,還需要根據主鍵去表中查一下,查詢出需要的內容(問題名稱)。
???????? 有問題歡迎和我交流,原創算法,可用于任何場合,注明出處即可。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
