Lucene.Net:构造搜索表达式简化搜索

我们知道利用Lucene.Net的不同的Query(常见如BooleanQuery,RangeQuery等等),可以有针对性地进行各种不同类型的搜索。利用QueryParser(或MultiFieldQueryParser),配合构造好的搜索关键字(搜索表达式),也可以实现不同类型的搜索。本文重点就是简单介绍一下搜索表达式和不同类型的Query之间的简单对比。本文最后的demo,QueryApp工程下有文章里贴出的大部分示例代码,代码自己会说话,有时候它可能更好地表达了文章作者的思路。您可以下载对照着本文进行阅读。

一、与或非

1、与

举例:搜索contents既包含“jeffreyzhao”,又有“ 老赵”的记录:

1
2
3
4
5
6
7
8
public  static  void  NormalQueryParserTest(Analyzer analyzer, string  field, string  keyword)
{
     QueryParser parser = new  QueryParser(Version.LUCENE_29, field, analyzer);
     Query query = parser.Parse(keyword);
     ShowQueryExpression(analyzer, query, keyword);
     SearchToShow(query);
     Console.WriteLine();
}

调用的时候,我们构造一个搜索关键词“+jeffreyzhao +老赵”:

1
2
3
string  field = "contents" ; //搜索的对应字段
keyword = "+jeffreyzhao +老赵" ;
LuceneSearch.NormalQueryParserTest(analyzer, field, keyword); //+contents:jeffreyzhao +contents:"老 赵"

搜索结果中我们可以看到,通过加号(+)可以表达与(AND)的关系(+contents:jeffreyzhao +contents:"老 赵" )。

特点:不同关键字越多,匹配的结果可能越少。

 

2、或

输入多个关键字,任何包含其中一个关键字的记录都被搜索出来:

1
2
3
string  keyword = "jeffreyzhao 老赵" ; //搜索输入关键词
string  field = "contents" ; //搜索的对应字段
LuceneSearch.NormalQueryParserTest(analyzer, field, keyword); //contents:jeffreyzhao contents:"老 赵"

特点:不同关键字越多,匹配的结果可能越多。

 

3、非(!)
1
2
3
4
5
keyword = "+jeffreyzhao -老赵" ;
LuceneSearch.NormalQueryParserTest(analyzer, field, keyword); //+contents:jeffreyzhao -contents:"老 赵"
 
keyword = "+jeffreyzhao !老赵" ;
LuceneSearch.NormalQueryParserTest(analyzer, field, keyword); //+contents:jeffreyzhao -contents:"老 赵"

上面的两种写法,转换成表达式都是+contents:jeffreyzhao -contents:"老 赵" 。

根据我们的测试结果,与或非的关系可以总结如下:

a & b  =>   +a +b
a || b  =>   a    b
a  !b   =>   +a  -b

这种与或非的关系,我们还可以通过BooleanQuery表达同样的搜索:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public  static  void  BooleanQueryTest(Analyzer analyzer, string  field, string  keyword, BooleanClause.Occur[] flags)
     {
         Console.WriteLine( "====BooleanQuery====" );
         string [] arrKeywords = keyword.Trim().Split( new  char [] { ' ' , ',' , ',' , '、'  }, StringSplitOptions.RemoveEmptyEntries);
         QueryParser parser = new  QueryParser(Version.LUCENE_29, field, analyzer);
         BooleanQuery bq = new  BooleanQuery();
         int  counter = 0;
         foreach  ( string  item in  arrKeywords)
         {
             Query query = parser.Parse(item);
             bq.Add(query, flags[counter]);
             counter++;
         }
         ShowQueryExpression(analyzer, bq, keyword);
         SearchToShow(bq);
         Console.WriteLine();
     }

其中BooleanClause.Occur(MUST:+  MUST_NOT:-   SHOULD:无符号)的选择至关重要:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
string  field = "contents" ; //搜索的对应字段
IList<Analyzer> listAnalyzer =LuceneAnalyzer. BuildAnalyzers();
BooleanClause.Occur[] occurs = new  BooleanClause.Occur[] { BooleanClause.Occur.MUST, BooleanClause.Occur.MUST };
foreach  (Analyzer analyzer in  listAnalyzer)
{
 
     //NormalQueryTest(analyzer);
     //LuceneSearch.NormalQueryParserTest(analyzer, field, keyword);//直接通过QueryParser配合构造好的查询表达式搜索
 
     //LuceneSearch.TermQueryTest(analyzer, field, "高手");//contents:高手
 
     LuceneSearch.BooleanQueryTest(analyzer, field, "jeffreyzhao 老赵" , occurs); //+contents:jeffreyzhao +contents:"老 赵"
 
     //LuceneSearch.RangeQueryTest(analyzer, rangeField, start, end); // createdate:[20101010 TO 20110101]  createdate:[20101010 TO 20110101}
 
     //LuceneSearch.PrefixQueryTest(analyzer, field, "hell"); // contents:hell*  (可以找到hello world那一项)
 
     //LuceneSearch.WildcardQueryTest(analyzer, field, "高手"); //contents:高手
 
     //LuceneSearch.FuzzyQueryTest(analyzer, field, "牛"); //contents:牛~0.5
 
     //LuceneSearch.PhraseQueryTest(analyzer, field, "hello world", 1); //contents:"hello world"~1
 
     //LuceneSearch.MulFieldsSearchTest(analyzer, fieldArr, "博  园", occurs); //+(contents:博 contents:园) +(title:博 title:园)
}

 

二、范围

1
2
3
4
5
6
7
8
9
10
string  rangeField = "createdate" ; //范围搜索对应字段
string  start = "20101010" ;
string  end = "20110101" ;
IList<Analyzer> listAnalyzer =LuceneAnalyzer. BuildAnalyzers();
foreach  (Analyzer analyzer in  listAnalyzer)
{
 
     LuceneSearch.RangeQueryTest(analyzer, rangeField, start, end); // createdate:[20101010 TO 20110101]  createdate:[20101010 TO 20110101}
 
}

同样道理,RangeQuery(或者TermRangeQuery)也可以实现范围搜索。

 

三、多字段组合搜索

搜索时,对两个或多个字段进行匹配的时候,可以用下面的方法:

1
2
3
4
5
6
7
8
9
10
public  static  void  MulFieldsSearchTest(Analyzer analyzer, string [] fields, string  keyword, BooleanClause.Occur[] flags)
{
     Console.WriteLine( "====MultiFieldQueryParser====" );
     MultiFieldQueryParser parser = new  MultiFieldQueryParser(Version.LUCENE_29, fields, analyzer);
     //Query query = parser.Parse(keyword);
     Query query = MultiFieldQueryParser.Parse(Version.LUCENE_29, keyword, fields, flags, analyzer);
     ShowQueryExpression(analyzer, query, keyword);
     SearchToShow(query);
     Console.WriteLine();
}

简单调用如下:

1
2
3
4
5
6
string [] fieldArr = new  string [] { field, "title"  }; //两个字段
  IList<Analyzer> listAnalyzer =LuceneAnalyzer. BuildAnalyzers();
  foreach  (Analyzer analyzer in  listAnalyzer)
  {
      LuceneSearch.MulFieldsSearchTest(analyzer, fieldArr, "博  园" , occurs); //+(contents:博 contents:园) +(title:博 title:园)
  }

如果我们把搜索关键字改为“博 -园”,则表达式就是“+(contents:博 -contents:园) +(title:博 -title:园)”,这也符合单个字段搜索。

 

注意:如你所知,与或非和范围不是搜索关系的全部。实际上,通过Lucene,你可以根据 +-!():^[]{}~*?  这几种符号,合理构造出表达真实意图的复杂表达式来代替不同类型的Query。我在示例代码中做了几个针对StandardAnalyzer的简单尝试,测试结果符合预期。

我在参考网上不少文章的时候,发现很多提到的问题都没有重现,再看他们的lucene的版本都低于2.0,我大胆猜测Lucene.Net的类库已经改进了不少,一开始还以为自己的测试不到位,囧。

 

四、分词效果

Analyzer选择不同,搜索结果也不同,尤其是对于中文。用下面的函数可以测试分词效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/// <summary>
/// 测试不同的Analyzer分词效果
/// </summary>
/// <param name="listAnalyzer"></param>
/// <param name="input"></param>
public  static  void  TestAnalyzer(IList<Analyzer> listAnalyzer, string  input)
{
     foreach  (Analyzer analyzer in  listAnalyzer)
     {
         Console.WriteLine( string .Format( "{0}:" , analyzer.ToString()));
 
         using  (TextReader reader = new  StringReader(input))
         {
             TokenStream stream = analyzer.ReusableTokenStream( string .Empty, reader);
             Lucene.Net.Analysis.Token token = null ;
             while  ((token = stream.Next()) != null )
             {
                 Console.WriteLine(token.TermText());
             }
         }
 
         Console.WriteLine();
     }
}

不同的Analyzer,分词效果可以总结如下:

StandardAnalyzer       对中文单字拆分;

WhitespaceAnalyzer  按空格拆分,对中文的支持不好;

KeywordAnalyzer       输入什么,分词就是什么;

SimpleAnalyzer           按标点和空格拆分,对中文的支持不好

StopAnalyzer               和SimpleAnalyzer类似;

选来选去,StandardAnalyzer 的效果还是很不错的,一般的应用差不多就够用了。 您可以使用不同的Analyzer,然后对比它们的搜索表达式并找出它们的不同之处。

 

demo下载:LuceneNetApp

 

===========================分割线分割线=========================

    今天是2011年1月1日,照例需要追忆过往的似水流年。

    过去的一年,在博客园,我自己在浅谈和总结中絮絮叨叨,表现的还算勤勉。虽然中规中矩平淡无奇,但我很满意自己这种乐此不彼保持兴趣的状态。新的一年,低头沉默却坚定,我会继续保持旺盛的学习和提高技术的热情。衷心感谢博客园的辛勤园丁和众多谦虚低调技术出众的博友们,祝你们新年好运,天天好运。

    还要怀着感恩的心感谢所有关心和帮助我的人。衷心祝福我的家人和朋友们身体健康,平安幸福。感谢我的父母大人,一直支持我,以我为荣。新的一年,我要继续让我的父母以我为荣,关心我的人以我为荣,我爱的人以我为荣。

    祝所有人元旦快乐。





本文转自JeffWong博客园博客,原文链接:http://www.cnblogs.com/jeffwongishandsome/archive/2011/01/01/lucene-net-search-expression.html,如需转载请自行联系原作者

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值