[转载]Lucene.Net:构造搜索表达式简化搜索 – 努力,天赋,笑容,自信 – 博客园.
我们知道利用Lucene.Net的不同的Query(常见如BooleanQuery,RangeQuery等等),可以有针对性地进行各种不同 类型的搜索。利用QueryParser(或MultiFieldQueryParser),配合构造好的搜索关键字(搜索表达式),也可以实现不同类型 的搜索。本文重点就是简单介绍一下搜索表达式和不同类型的Query之间的简单对比。本文最后的demo,QueryApp工程下有文章里贴出的大部分示 例代码,代码自己会说话,有时候它可能更好地表达了文章作者的思路。您可以下载对照着本文进行阅读。
一、与或非
1、与
举例:搜索contents既包含“jeffreyzhao”,又有“ 老赵”的记录:
1 |
public static void NormalQueryParserTest(Analyzer analyzer, string field, string keyword) |
3 |
QueryParser parser = new QueryParser(Version.LUCENE_29, field, analyzer); |
4 |
Query query = parser.Parse(keyword); |
5 |
ShowQueryExpression(analyzer, query, keyword); |
调用的时候,我们构造一个搜索关键词“+jeffreyzhao +老赵”:
1 |
string field = "contents" ; |
2 |
keyword = "+jeffreyzhao +老赵" ; |
3 |
LuceneSearch.NormalQueryParserTest(analyzer, field, keyword); |
搜索结果中我们可以看到,通过加号(+)可以表达与(AND)的关系(+contents:jeffreyzhao +contents:”老 赵” )。
特点:不同关键字越多,匹配的结果可能越少。
2、或
输入多个关键字,任何包含其中一个关键字的记录都被搜索出来:
1 |
string keyword = "jeffreyzhao 老赵" ; |
2 |
string field = "contents" ; |
3 |
LuceneSearch.NormalQueryParserTest(analyzer, field, keyword); |
特点:不同关键字越多,匹配的结果可能越多。
3、非(!)
1 |
keyword = "+jeffreyzhao -老赵" ; |
2 |
LuceneSearch.NormalQueryParserTest(analyzer, field, keyword); |
4 |
keyword = "+jeffreyzhao !老赵" ; |
5 |
LuceneSearch.NormalQueryParserTest(analyzer, field, keyword); |
上面的两种写法,转换成表达式都是+contents:jeffreyzhao -contents:”老 赵” 。
根据我们的测试结果,与或非的关系可以总结如下:
a & b => +a +b
a || b => a b
a !b => +a -b
这种与或非的关系,我们还可以通过BooleanQuery表达同样的搜索:
01 |
public static void BooleanQueryTest(Analyzer analyzer, string field, string keyword, BooleanClause.Occur[] flags) |
03 |
Console.WriteLine( "====BooleanQuery====" ); |
04 |
string [] arrKeywords = keyword.Trim().Split( new char [] { ' ' , ',' , ',' , '、' }, StringSplitOptions.RemoveEmptyEntries); |
05 |
QueryParser parser = new QueryParser(Version.LUCENE_29, field, analyzer); |
06 |
BooleanQuery bq = new BooleanQuery(); |
08 |
foreach ( string item in arrKeywords) |
10 |
Query query = parser.Parse(item); |
11 |
bq.Add(query, flags[counter]); |
14 |
ShowQueryExpression(analyzer, bq, keyword); |
其中BooleanClause.Occur(MUST:+ MUST_NOT:- SHOULD:无符号)的选择至关重要:
01 |
string field = "contents" ; |
02 |
IList<Analyzer> listAnalyzer =LuceneAnalyzer. BuildAnalyzers(); |
03 |
BooleanClause.Occur[] occurs = new BooleanClause.Occur[] { BooleanClause.Occur.MUST, BooleanClause.Occur.MUST }; |
04 |
foreach (Analyzer analyzer in listAnalyzer) |
12 |
LuceneSearch.BooleanQueryTest(analyzer, field, "jeffreyzhao 老赵" , occurs); |
二、范围
01 |
string rangeField = "createdate" ; |
02 |
string start = "20101010" ; |
03 |
string end = "20110101" ; |
04 |
IList<Analyzer> listAnalyzer =LuceneAnalyzer. BuildAnalyzers(); |
05 |
foreach (Analyzer analyzer in listAnalyzer) |
08 |
LuceneSearch.RangeQueryTest(analyzer, rangeField, start, end); |
同样道理,RangeQuery(或者TermRangeQuery)也可以实现范围搜索。
三、多字段组合搜索
搜索时,对两个或多个字段进行匹配的时候,可以用下面的方法:
01 |
public static void MulFieldsSearchTest(Analyzer analyzer, string [] fields, string keyword, BooleanClause.Occur[] flags) |
03 |
Console.WriteLine( "====MultiFieldQueryParser====" ); |
04 |
MultiFieldQueryParser parser = new MultiFieldQueryParser(Version.LUCENE_29, fields, analyzer); |
06 |
Query query = MultiFieldQueryParser.Parse(Version.LUCENE_29, keyword, fields, flags, analyzer); |
07 |
ShowQueryExpression(analyzer, query, keyword); |
简单调用如下:
1 |
string [] fieldArr = new string [] { field, "title" }; |
2 |
IList<Analyzer> listAnalyzer =LuceneAnalyzer. BuildAnalyzers(); |
3 |
foreach (Analyzer analyzer in listAnalyzer) |
5 |
LuceneSearch.MulFieldsSearchTest(analyzer, fieldArr, "博 园" , occurs); |
如果我们把搜索关键字改为“博 -园”,则表达式就是“+(contents:博 -contents:园) +(title:博 -title:园)”,这也符合单个字段搜索。
注意:如你所知,与或非和范围不是搜索关系的全部。实际上,通过Lucene,你可以根据 +-!():^[]{}~*? 这几种符号,合理构造出表达真实意图的复杂表达式来代替不同类型的Query。我在示例代码中做了几个针对StandardAnalyzer的简单尝试,测试结果符合预期。
我在参考网上不少文章的时候,发现很多提到的问题都没有重现,再看他们的lucene的版本都低于2.0,我大胆猜测Lucene.Net的类库已经改进了不少,一开始还以为自己的测试不到位,囧。
四、分词效果
Analyzer选择不同,搜索结果也不同,尤其是对于中文。用下面的函数可以测试分词效果:
04 |
/// <param name="listAnalyzer"></param> |
05 |
/// <param name="input"></param> |
06 |
public static void TestAnalyzer(IList<Analyzer> listAnalyzer, string input) |
08 |
foreach (Analyzer analyzer in listAnalyzer) |
10 |
Console.WriteLine( string .Format( "{0}:" , analyzer.ToString())); |
12 |
using (TextReader reader = new StringReader(input)) |
14 |
TokenStream stream = analyzer.ReusableTokenStream( string .Empty, reader); |
15 |
Lucene.Net.Analysis.Token token = null ; |
16 |
while ((token = stream.Next()) != null ) |
18 |
Console.WriteLine(token.TermText()); |
不同的Analyzer,分词效果可以总结如下:
StandardAnalyzer 对中文单字拆分;
WhitespaceAnalyzer 按空格拆分,对中文的支持不好;
KeywordAnalyzer 输入什么,分词就是什么;
SimpleAnalyzer 按标点和空格拆分,对中文的支持不好
StopAnalyzer 和SimpleAnalyzer类似;
选来选去,StandardAnalyzer 的效果还是很不错的,一般的应用差不多就够用了。 您可以使用不同的Analyzer,然后对比它们的搜索表达式并找出它们的不同之处。
demo下载:LuceneNetApp