kakiro-webカキローウェブ

システム開発情報とコンピューター書籍の紹介サイト

【Lucene】QueryParserについて

Documentを検索する検索条件にユーザーが自由に入力した文字列等、いくつかのキーワードが組み合わされた文字列を使用し、そこからQueryを生成するのはQueryParserを使用することで可能となります。

ここではQueryParserについてサンプルプログラムを元に解説を行っていきます。

検索対象として以下のようなDocumentを使用します。基本処理 インデックスの登録のページで紹介したサンプルプログラムを元に、インデックスに登録するDocumentを変更した箇所を以下に示します。

AnalyzerにはStandardAnalyzerを使用しています。

import java.io.File;
import java.util.Iterator;
import java.util.List;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.AtomicReader;
import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.Fields;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.index.Terms;
import org.apache.lucene.index.TermsEnum;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.Version;

public class WriteIndex {

    public static void main(String[] args) {
        try {
            WriteIndex wi = new WriteIndex();
            wi.main();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void main() throws Exception {
        //(略)
        try {
            //(略)
            analyzer = new StandardAnalyzer(Version.LUCENE_46);

            IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_46, analyzer);
            config.setOpenMode(OpenMode.CREATE);

            writer = new IndexWriter(dir, config);

            Document doc = null;

            doc = new Document();
            doc.add(new TextField("txt1", "abc def ghi", Field.Store.YES));
            doc.add(new StringField("str1", "jkl mno pqr", Field.Store.YES));

            writer.addDocument(doc);

            doc = new Document();
            doc.add(new TextField("txt1", "abc ghi def", Field.Store.YES));
            doc.add(new StringField("str1", "jkl pqr mno", Field.Store.YES));

            writer.addDocument(doc);

            doc = new Document();
            doc.add(new TextField("txt1", "abc mno ghi", Field.Store.YES));
            doc.add(new StringField("str1", "jkl def pqr", Field.Store.YES));

            writer.addDocument(doc);

            doc = new Document();
            doc.add(new TextField("txt1", "jkl def ghi", Field.Store.YES));
            doc.add(new StringField("str1", "abc mno pqr", Field.Store.YES));

            writer.addDocument(doc);

            doc = new Document();
            doc.add(new TextField("txt1", "jkl mno ghi", Field.Store.YES));
            doc.add(new StringField("str1", "abc def pqr", Field.Store.YES));

            writer.addDocument(doc);

            writer.commit();

            //登録したインデックスの確認
            checkIndex(writer);
        } finally {
            //(略)
        }
    }

    private void checkIndex(IndexWriter writer) throws Exception {
        //(略)
    }

}

これにより登録されるインデックスは以下のようになります。

----- fieldName:str1 -----
fieldText:abc def pqr
fieldText:abc mno pqr
fieldText:jkl def pqr
fieldText:jkl mno pqr
fieldText:jkl pqr mno
----- fieldName:txt1 -----
fieldText:abc
fieldText:def
fieldText:ghi
fieldText:jkl
fieldText:mno

Documentは以下のように登録されています。

Document ID フィールド「txt1」のキーワード フィールド「str1」のキーワード
0 abc jkl mno pqr
def
ghi
1 abc jkl pqr mno
ghi
def
2 abc jkl def pqr
mno
ghi
3 jkl abc mno pqr
def
ghi
4 jkl abc def pqr
mno
ghi

上記のようにインデックスに登録したDocumentに対し、QueryParserからQueryを生成し検索を行う例を以下に示します。

検索を行うサンプルプログラムは、基本処理 インデックスの検索のページで紹介したものを元に、Queryを指定する部分を変更したものとなっています。

import java.io.File;
import java.util.List;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;

public class SearchIndex {

    public static void main(String[] args) {
        try {
            SearchIndex si = new SearchIndex();
            si.main();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void main() throws Exception {
        //検索を行うインデックスが登録されているディレクトリのパス
        String indexPath = "index";

        Directory dir = null;
        IndexReader reader = null;
        Analyzer analyzer = null;

        try {
            dir = FSDirectory.open(new File(indexPath));

            reader = DirectoryReader.open(dir);

            IndexSearcher searcher = new IndexSearcher(reader);

            analyzer = new StandardAnalyzer(Version.LUCENE_46);//---(1)

            QueryParser queryParser = new QueryParser(Version.LUCENE_46, "txt1", analyzer);//---(2)
            queryParser.setDefaultOperator(QueryParser.Operator.AND);

            Query query = queryParser.parse("abc def");//---(3)

            System.out.println("query:" + query);

            TopDocs topDocs = searcher.search(query, 5);//---(4)

            ScoreDoc[] scoreDocs = topDocs.scoreDocs;

            for (ScoreDoc scoreDoc : scoreDocs) {
                //(略)
            }
        } finally {
            if (analyzer != null) {
                analyzer.close();
            }

            if (reader != null) {
                reader.close();
            }

            if (dir != null) {
                dir.close();
            }
        }
    }

}

上記プログラムの解説を行います。

(1)

QueryParserからQueryを生成する際には、検索条件として使用する文字列の解析が行われるため、Analyzerが使用されます。

AnalyzerにはDucumentをインデックスに登録するときに使用したものと同じものを使用し、文字列からキーワードへの分割がインデックスの登録時と検索時で同じように行われるようにしておいてほうがよいです。

ここではインデックスの登録時と同じAnalyzerとなるStandardAnalyzerを使用するようにしています。

(2)

QueryParserの生成を行っています。

コンストラクタでは使用するLuceneのバージョン、検索対象とするフィールドとして「txt1」、(1)で生成したAnalyzerを指定しています。

またsetDefaultOperatorメソッドでQueryParserが複数のキーワードからQueryを生成するときに使用するAND、OR条件を指定しています。

ここではAND条件を指定しています。

(3)

(2)で生成したQueryParserのparseメソッドに検索条件となる文字列を指定して、Queryの生成を行っています。

(4)

IndexSearcherのsearchメソッドに(3)で生成したQueryを指定して、インデックスの検索を行っています。

このサンプルプログラムの実行結果は以下のようになります。

query:+txt1:abc +txt1:def
---------- docId:0 ----------
----- fieldName:txt1 -----
fieldText:abc def ghi
----- fieldName:str1 -----
fieldText:jkl mno pqr
---------- docId:1 ----------
----- fieldName:txt1 -----
fieldText:abc ghi def
----- fieldName:str1 -----
fieldText:jkl pqr mno

Queryの文字列表現は、Queryの基本動作のページでも使用例を示したBooleanQueryにMUSTの条件を2つ指定したときと同様になっています。

ここではフィールド名「txt1」にキーワード「abc」と「def」の両方を含むDocumentが検索結果として取得されています。

続いて上記のQueryParserで、setDefaultOperatorメソッドにOR条件を指定した例を以下に示します。

サンプルプログラムはQueryを指定する部分のみを抜粋して示します。

//(略)
            analyzer = new StandardAnalyzer(Version.LUCENE_46);

            QueryParser queryParser = new QueryParser(Version.LUCENE_46, "txt1", analyzer);
            queryParser.setDefaultOperator(QueryParser.Operator.OR);//---(1)

            Query query = queryParser.parse("abc def");

            System.out.println("query:" + query);

            TopDocs topDocs = searcher.search(query, 5);
//(略)

上記プログラムの解説を行います。

(1)

QueryParserのsetDefaultOperatorメソッドでOR条件を指定しています。

その他については上記のAND条件を指定した場合と同じです。

このサンプルプログラムの実行結果は以下のようになります。

query:txt1:abc txt1:def
---------- docId:0 ----------
----- fieldName:txt1 -----
fieldText:abc def ghi
----- fieldName:str1 -----
fieldText:jkl mno pqr
---------- docId:1 ----------
----- fieldName:txt1 -----
fieldText:abc ghi def
----- fieldName:str1 -----
fieldText:jkl pqr mno
---------- docId:2 ----------
----- fieldName:txt1 -----
fieldText:abc mno ghi
----- fieldName:str1 -----
fieldText:jkl def pqr
---------- docId:3 ----------
----- fieldName:txt1 -----
fieldText:jkl def ghi
----- fieldName:str1 -----
fieldText:abc mno pqr

Queryの文字列表現は、Queryの基本動作のページでも使用例を示したBooleanQueryにSHOULDの条件を2つ指定したときと同様になっています。

ここではフィールド名「txt1」にキーワード「abc」または「def」を含むDocumentが検索結果として取得されています。

上記2つの例で示したようにQueryParserではデフォルトでBooleanQueryが生成されるようになっています。

検索条件として「abc def」と指定された場合にキーワード「abc」、「def」をこの順で含むものを検索したい場合、つまりPhraseQueryについてのページでも解説したPhraseQueryを生成したい場合は、以下のように行うことで可能になります。

サンプルプログラムはQueryを指定する部分のみを抜粋して示します。

//(略)
            analyzer = new StandardAnalyzer(Version.LUCENE_46);

            QueryParser queryParser = new QueryParser(Version.LUCENE_46, "txt1", analyzer);//---(1)

            Query query = queryParser.parse("\"abc def\"");//---(2)

            System.out.println("query:" + query);

            TopDocs topDocs = searcher.search(query, 5);
//(略)

上記プログラムの解説を行います。

(1)

今回QueryParserからQueryを生成する際にAND、OR条件は使用しませんので、QueryParserのsetDefaultOperatorメソッドは実行していません。

(2)

QueryParserのparseメソッドに検索条件となる文字列を指定するときに「"」で囲むようにしています。

このサンプルプログラムの実行結果は以下のようになります。

---------- docId:0 ----------
----- fieldName:txt1 -----
fieldText:abc def ghi
----- fieldName:str1 -----
fieldText:jkl mno pqr

Queryの文字列表現は、PhraseQueryについてのページでも使用例を示したPhraseQueryに条件を2つ指定したときと同様になっています。

ここではフィールド名「txt1」にキーワード「abc」、「dfe」をこの順で含むDucumentが検索結果として取得されています。

尚、Luceneの過去のバージョンではPhraseQueryのsetAutoGeneratePhraseQueriesメソッドでtrueを指定することで、検索条件を「abc def」と「"」で囲んでいない場合でもPhraseQueryからPhraseQueryを生成することができましたが、バージョン4.4.0でこの動作は行わなくなったようです。

QueryParserの使用例は以上です。