[セットトップ]Luceneのマルチドメインクエリ、結果内のクエリ、クエリ結果のページング、クエリ結果のハイライト、および結果スコア


1.複数のドメインに対する1回限りのクエリー
1.1.3つのシナリオ
luceneを使用して検索エンジンを構築する場合、複数のドメインに対して一括クエリーを行うには、一般的に3つの方法があります.
第1の実装方法は、多値の全パケットドメインを含むテキストをインデックス化することであり、このスキームが最も簡単である.しかし、この防犯には欠点があります.各ドメインの重み付けを直接制御することはできません.
2つ目の方法は、MultiFieldQueryParserを使用します.これはQueryParserのサブクラスです.バックグラウンド・プログラムでQueryParserオブジェクトをインスタンス化し、ドメインごとにクエリー式の解析を行い、BooleanQueryを使用してクエリー結果をマージします.プログラムがBooleanQueryにクエリ句を追加すると、デフォルトオペレータORが最も簡単な解析方法で使用されます.より良い制御を実現するために、ブールオペレータはBooleanClauseの定数を使用して各ドメインに割り当てることができます.指定が必要な場合はBooleanClauseを使用できます.Occur.MUSTは、指定が禁止されている場合はBooleanClauseを使用することができる.Occur.MUST_NOT、または通常はBooleanClauseである.Occur.SHOULD.次のプログラムでは、MultiFieldQueryParserクラスを作成する方法を示します.
//         
String[] fields = { "phoneType", "name", "category", "price" };
Query query = new MultiFieldQueryParser(Version.LUCENE_36, fields, analyzer).parse(keyword);
第3の方法は、1つ以上の任意のクエリーをカプセル化し、一致するドキュメントをOR操作する高度なDisjunctionMaxQueryクラスを使用することです.
1.2.シナリオの選択
以上の3つの案のうち、3つ目の案が最も良いわけではなく、1つ目の案が最も悪いわけでもない.どの実装があなたのアプリケーションに適していますか?答えは「状況を見る」です.ここには取捨選択があるからです.全パッケージドメインは単純なソリューションですが、このソリューションは検索結果を簡単にソートでき、ディスク領域を浪費する可能性があります(プログラムは同じテキストインデックスを2回使用する可能性があります).しかし、このソリューションは最高の検索性能を得る可能性があります.
MultiFieldQueryParserによって生成されたBooleanQueryは、すべてのクエリで一致するドキュメントのスコアの合計を計算し(DisjunctionMaxQueryは最大スコアのみを選択します)、各ドメインに対する重み付けを実現します.上記の3つのソリューションをテストするとともに、検索のパフォーマンスと検索の相関性を考慮して最適なソリューションを見つける必要があります.
2.結果の問合せ
2.1.2つのシナリオ
検索結果の再検索は、一般的に2つのシナリオから選択できる一般的なニーズです.
①QueryFilterを使用して最初のクエリをフィルタとして処理する.
②BooleanQueryで前後の2つのクエリを結合し、BooleanClauseを使用する.Occur.MUST.
最初の方法について説明する必要があります.QueryFilterはLuceneの2.xバージョンでは存在するが、3.xではluceneのAPIでこのクラスは廃棄されており,これ以上見つからない.もしあなたのプロジェクトがluceneを使用しているなら3.xですが、QueryFilterを使用する必要があります.では、自分でQueryFilterクラスを作成し、2.xのQueryFilterのソースコードをコピーします.直接工事でluceneを同時に使うと言うかもしれません.xと3.xのコアjarファイルでいいんじゃないですか.しかし残念なことに、1つのプロジェクトでは、異なるバージョンのluceneを同時に使用することはできません.
2.2.QueryFilterスキーム
先に述べたように、必ずQueryFilterを使うならlucene 2.xにはQueryFilterのAPIがないので、自分でQueryFilterを書きます.QueryFilterのソースコードはlucene 2にあります.xでは、
import org.apache.lucene.search.CachingWrapperFilter;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.QueryWrapperFilter;

public class QueryFilter extends CachingWrapperFilter {

	/**
	 * Constructs a filter which only matches documents matching
	 * <code>query</code>.
	 */
	public QueryFilter(Query query) {
		super(new QueryWrapperFilter(query));
	}

	public boolean equals(Object o) {
		return super.equals((QueryFilter) o);
	}

	public int hashCode() {
		return super.hashCode() ^ 0x923F64B9;
	}
}

第1のシナリオの例プログラムは以下の通りである.
       //     keyword   
        public static void search(String keyword) throws IOException, ParseException {
                QueryParser queryParser = new QueryParser("content",new SimpleAnalyzer());
                Query query = queryParser.parse(keyword.trim());
                QueryFilter filter = new QueryFilter(query);
                //  
                search(query, filter);
        }
        
        //   oldKeyword       newKeyword
        public static void searchInResult(String newKeyword, String oldKeyword) throws ParseException, IOException {                
                QueryParser queryParser = new QueryParser("content",new SimpleAnalyzer());
                Query query = queryParser.parse(newKeyword.trim());
                Query oldQuery = queryParser.parse(oldKeyword.trim());
                QueryFilter oldFilter = new QueryFilter(oldQuery);
                CachingWrapperFilter filter = new CachingWrapperFilter(oldFilter);
                //  
                search(query, filter);
        }
        
        private static void search(Query query, Filter filter) throws IOException, ParseException {
                IndexSearcher ins = new IndexSearcher("d:/tesindex");
                Hits hits = ins.search(query, filter);
                for (int i = 0; i < hits.length(); i++) {
                        Document doc = hits.doc(i);
                        System.out.println(doc.get("content"));
                }
        }

2.3.BooleanQueryスキーム
BooleanQueryを用いて結果検索を実現するプロセスは,まずキーワードkeyword 1で正常に検索し,ユーザが検索結果でキーワードkeyword 2で検索する必要がある場合,BooleanQueryを構築することで結果検索に対する効果を実現する.この2つのキーワードはBooleanClauseを使用することに注意してください.Occur.MUST.
//  BooleanQuery
BooleanQuery booleanQuery = new BooleanQuery();
//    ,        
String[] fields = { "phoneType", "name", "category","free" };
Query multiFieldQuery = new MultiFieldQueryParser(Version.LUCENE_36, fields, analyzer).parse(keyword);
// multiFieldQuery   BooleanQuery 
booleanQuery.add(multiFieldQuery, BooleanClause.Occur.MUST);
//  osKeyword   
if(osKeyword != null && !osKeyword.equals("") && !osKeyword.equals("null")){
	TermQuery osQuery = new TermQuery(new Term("phoneType",osKeyword)); 
	// osQuery   BooleanQuery 
	booleanQuery.add(osQuery, BooleanClause.Occur.MUST);
}

3.検索結果ページング
3.1.2つのシナリオ
キーワードの検索によりluceneが複数のレコードを返すと、1つのページにすべての検索結果が格納されないことが多く、自然にページ分けされます.私はここで2つの案を提供します.この2つの方法は私が使ったことがあります.
1つ目の方法は、検索結果をすべて1つのCollectionにカプセル化し、例えばリストに入れ、この結果をjspページなどのフロントに伝えることです.そしてこのlistでページング表示を行います.
2つ目の方法は、luceneが持参したページングツールpublic TopDocs topDocs(int start,int howMany)を使用することです.
1つ目の方法は2回のクエリーには関係ないと思います.そうすれば、クエリーの無駄を避けることができます.しかし、検索結果のデータ量が大きい場合、このように一度にこんなに多くのデータをクライアントに転送するが、ユーザーが検索した結果、第1ページの内容しか表示されず、第2ページ、第3ページ、および後の内容を表示することは少ないため、一度にすべての結果をフロントに伝えるという無駄が大きい.
第2の方法は,ページをめくるたびにクエリを意味し,表面的にはリソースを浪費するが,luceneの効率性のため,このような浪費がシステム全体に及ぼす影響は微々たるものであるが,この方法は方法1の欠陥を回避する.
3.2.ページング実装
/**
     *        n         
     * @param keyWord            
     * @param pageSize             
     * @param currentPage       
     */
    public void paginationQuery(String keyWord,int pageSize,int currentPage) throws ParseException, CorruptIndexException, IOException {
        String[] fields = {"title","content"};
        QueryParser queryParser = new MultiFieldQueryParser(Version.LUCENE_36,fields,analyzer);
        Query query = queryParser.parse(keyWord);
         
        IndexReader indexReader  = IndexReader.open(directory);
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
         
        //TopDocs        
        TopDocs topDocs = indexSearcher.search(query, 100);//    100   
        int totalCount = topDocs.totalHits; //        
        ScoreDoc[] scoreDocs = topDocs.scoreDocs; //          
         
        //        
        int begin = pageSize * (currentPage - 1) ;
        //        
        int end = Math.min(begin + pageSize, scoreDocs.length);
         
        //      
        for(int i=begin;i<end;i++) {
            int docID = scoreDocs[i].doc;
            Document doc = indexSearcher.doc(docID);
            int id = NumericUtils.prefixCodedToInt(doc.get("id"));
            String title = doc.get("title");
            System.out.println("id is : "+id);
            System.out.println("title is : "+title);
        }   
    }

4.検索結果をハイライト
検索結果のハイライト実装方法については、lucene-highlighter-3.6を用いるluceneに応答するツールが提供される.2.jarは検索結果のハイライト表示を実現する.
public void search(String fieldName, String keyword)throws CorruptIndexException, IOException, ParseException {
	searcher = new IndexSearcher(indexPath);
	QueryParser queryParse = new QueryParser(fieldName, analyzer); //   QueryParser,            
	Query query = queryParse.parse(keyword);
	Hits hits = searcher.search(query);
	for (int i = 0; i < hits.length(); i++) {
		Document doc = hits.doc(i);
		String text = doc.get(fieldName);
		SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<font color='red'>", "</font>");
		Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
		highlighter.setTextFragmenter(new SimpleFragmenter(text.length()));
		if (text != null) {
			TokenStream tokenStream = analyzer.tokenStream(fieldName,new StringReader(text));
			String highLightText = highlighter.getBestFragment(tokenStream,text);
			System.out.println("      " + (i + 1) + "          :");
			System.out.println(highLightText);
		}
	}
	searcher.close();
}
の前の1行の判断文は重要です:if(text!=null)、textが空の場合、表示結果はハイライトされていないだけでなく、得られた元の結果もフィルタされます.さらにコードに加えてtext=nullの場合、元の検索結果をtextに割り当て、結果を表示することができます.
5.検索結果のスコア
luceneの採点には独自のメカニズムがあり、キーワードを入力すると、luceneはヒットした記録を採点し、デフォルトでは点数が高いほど結果の上位にランクされます.インデックスを作成するときにドメインに重み付けが行われていない場合、デフォルトのスコアの上限は5点であり、ドメインに重み付けがある場合、検索結果のスコアが5点より大きい場合があります.
検索結果に対するluceneのスコアをexplain()で確認できます.
//  
Explanation explanation = indexSearcher.explain(query, docID);
System.out.println(explanation.toString());
バックグラウンドで印刷された情報は以下の通りです.
 2.4342022 = (MATCH) weight(name:books in 71491), product of:
        0.2964393 = queryWeight(name:books), product of:
          8.21147 = idf(docFreq=109, maxDocs=149037)
          0.036100637 = queryNorm

添付:luceneで構築されたApp検索システムのスクリーンショット