搜尋結果按匹配欄位進行排序
談到搜尋,一般就想起了SQL Server的FullTextSearch(全文搜尋)功能,它確實強大,但使用起來也要做一些較為繁瑣的準備工作,一般小型的專案或者對搜尋要求(包括效能需求)不是很高的情況下實用它還是有點太重型了。簡單的搜尋用SQL查詢即可,但一般面臨的一個問題就是如何對搜尋結果按匹配欄位進行優先順序排序。
例如有個產品表(Products),它的欄位包:括產品ID、產品名稱、產品類別、產品品牌、產品簡介、產品詳細介紹。 欄位 型別 ProdID int ProdName nvarchar CategoryName nvarchar ProdBrand nvarchar ProdIntro nvarchar ProdDescription nvarchar
現在我們要求通過某個關鍵字從Products表中搜索包含該關鍵字的記錄,凡是以下任何一個欄位包含該關鍵字的記錄都列出來:ProdName, CategoryName , ProdBrand, ProdIntro, ProdDescription。 並且搜尋結果按照前述欄位的匹配優先順序進行排序:
1)先列出欄位ProdName匹配關鍵字的記錄,然後列出欄位CategoryName匹配關鍵字的記錄,依此類推,最後列出欄位ProdDescription匹配關鍵字的記錄;
2)在欄位ProdName匹配關鍵字的所有記錄中,先列出欄位CategoryName也匹配關鍵字的記錄,然後列出欄位ProdBrand也匹配關鍵字的記錄,依次類推…
3)按照規則2遞迴排序每個記錄分組……
搜尋匹配該關鍵字的所有記錄的SQL語句倒很簡單:
SELECT * from Products WHERE ProdName like ‘%KeyWord%' or CategoryName like ‘%KeyWord%' or ProdBrand like ‘%KeyWord%' or ProdIntro like ‘%KeyWord%' or ProdDescription like ‘%KeyWord%'
但對搜尋出的結果進行匹配優先順序排序稍微有點困難。在用簡單的SQL進行搜尋時有兩種方式來達到這個排序的目的:加權法和多欄位排序法(我瞎取的名字^-^)。
一、加權法
對搜尋的每條記錄計算出一個排序權值來,然後將所有搜尋結果按照這個排序權值進行降序排列即可。每條被搜尋出的記錄的排序權值為該記錄所有欄位的權值之和。某個欄位的權值取決於該欄位是否匹配關鍵字,如果不匹配則為0,如果匹配則為改欄位的匹配權值。欄位的匹配權值計算方式為:
fieldPriority = 2的i次冥(i為該欄位在所有被搜尋的欄位優先順序排序中倒排的位置)
例如,在我們示例中各欄位的匹配權值為:
欄位 倒排位置 匹配權值
ProdName 4 16
CategoryName 3 8
ProdBrand 2 4
ProdIntro 1 2
ProdDescription 0 1
之所以採用這種演算法,是為了確保某個欄位匹配的記錄的排序權值不會低於另外一條不匹配該欄位但後續欄位都匹配的記錄的排序權值。例如記錄A中僅僅ProdName匹配關鍵字,所以它的排序權值為16,而記錄B中除了欄位ProdName外其他欄位都匹配,則其排序權值為15(8+4+2+1=15)。但記錄A仍然會排在記錄B前面。
相應的SQL大致如下:
SELECT *, (
(case when charIndex(ProdName,KeyWord)>-1 then 16 else 0 end) +
(case when charIndex(CategoryName,KeyWord)>-1 then 8 else 0 end) +
(case when charIndex(ProdBrand,KeyWord)>-1 then 4 else 0 end) +
(case when charIndex(ProdIntro,KeyWord)>-1 then 2 else 0 end) +
(case when charIndex(ProdDescription,KeyWord)>-1 then 1 else 0 end)
) as OrderPriority
from Products
WHERE ProdName like ‘%KeyWord%' or
CategoryName like ‘%KeyWord%' or
ProdBrand like ‘%KeyWord%' or
ProdIntro like ‘%KeyWord%' or
ProdDescription like ‘%KeyWord%'
Order by OrderPriority desc
二、多欄位排序法
加權法實在是有點囉嗦,倒不如直接利用SQL可以對多個欄位進行排序來實現更清晰更直接。實際上我們把每個欄位是否匹配的權值分散到SQL的Order裡即可,大致SQL如下:
SELECT *
from Products
WHERE ProdName like ‘%KeyWord%' or
CategoryName like ‘%KeyWord%' or
ProdBrand like ‘%KeyWord%' or
ProdIntro like ‘%KeyWord%' or
ProdDescription like ‘%KeyWord%'
Order by (case when charIndex(ProdName,KeyWord)>-1 then 0 else 1 end),
(case when charIndex(CategoryName,KeyWord)>-1 then 0 else 1 end),
(case when charIndex(ProdBrand,KeyWord)>-1 then 0 else 1 end),
(case when charIndex(ProdIntro,KeyWord)>-1 then 0 else 1 end),
(case when charIndex(ProdDescription,KeyWord)>-1 then 0 else 1 end)
順便貼一段在NHibernate裡採用這種思路來實現搜尋的程式碼:
public List<Products> SearchProducts( string keyWord, int currentPageIndex, int pageSize, out int recordCount)
{
string likeKeyWord = String.Format("%{0}%",keyWord);
string[] matchFields = new string[] { "Prodname", "Prodmodel", "Trademarkname", "Keywords", "Producter", "Prodintro", "Proddescription" /*, "cate.Catename"*/ };
ICriteria crit = Session.CreateCriteria(typeof(Products));
// Inner Join ProductCategory to search CategoryName
//crit.CreateAlias("Cateid", "cate", NHibernate.SqlCommand.JoinType.InnerJoin);
// Set query condition.
crit.Add(Restrictions.Eq("Isdisabled", true));
// Add 'or' SQL
if (matchFields.Length > 0)
{
Disjunction orExpression = new Disjunction();
foreach (string strField in matchFields)
{
orExpression.Add(Expression.Like(strField, likeKeyWord));
}
crit.Add(orExpression);
}
// Copy current ICriteria instance to the new one for getting the pagination records.
ICriteria pageCrit = CriteriaTransformer.Clone(crit);
// Get the total record count
recordCount = Convert.ToInt32(crit.SetProjection(Projections.RowCount()).UniqueResult());
// Set order parameter.
foreach (string strField in matchFields)
{
pageCrit.AddOrder(Order.Asc(Projections.Conditional(Expression.Like(strField, likeKeyWord), Projections.Constant(0), Projections.Constant(1))));
}
pageCrit.AddOrder(new Order("Createdate", false));
//Set pagination
pageCrit.SetFirstResult((currentPageIndex - 1) * pageSize).SetMaxResults(pageSize);
return pageCrit.List<Products>().ToList<Products>();
}