kakiro-webカキローウェブ

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

【Lucene】Queryの基本動作

インデックスからDocumentを検索するときの検索条件の指定にはQueryが使用されます。

ここではQueryの基本動作を理解するために、TermQueryとBooleanQueryを使用したサンプルプログラムを元に解説を行っていきます。

検索対象として以下のような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", "ab cd ef", Field.Store.YES));
            doc.add(new StringField("str1", "gh ij kl", Field.Store.YES));

            writer.addDocument(doc);

            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", "abcd efgh ijkl", Field.Store.YES));
            doc.add(new StringField("str1", "mnop qrst uvwx", Field.Store.YES));

            writer.addDocument(doc);

            doc = new Document();
            doc.add(new TextField("txt1", "fghi de abc", Field.Store.YES));
            doc.add(new StringField("str1", "opqr mn jkl", Field.Store.YES));

            writer.addDocument(doc);

            writer.commit();

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

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

}

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

----- fieldName:str1 -----
fieldText:gh ij kl
fieldText:jkl mno pqr
fieldText:mnop qrst uvwx
fieldText:opqr mn jkl
----- fieldName:txt1 -----
fieldText:ab
fieldText:abc
fieldText:abcd
fieldText:cd
fieldText:de
fieldText:def
fieldText:ef
fieldText:efgh
fieldText:fghi
fieldText:ghi
fieldText:ijkl

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

Document ID フィールド「txt1」のキーワード フィールド「str1」のキーワード
0 ab gh ij kl
cd
ef
1 abc jkl mno pqr
def
ghi
2 abcd mnop qrst uvwx
efgh
ijkl
3 fghi opqr mn jkl
de
abc

上記のようにインデックスに登録したDocumentに対し、まずは1つのキーワードを指定して検索を行う例を以下に示します。

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

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

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.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;

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;

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

            reader = DirectoryReader.open(dir);

            IndexSearcher searcher = new IndexSearcher(reader);

            TermQuery termQuery = new TermQuery(new Term("txt1", "abc"));//---(1)

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

            TopDocs topDocs = searcher.search(termQuery, 5);//---(2)

            ScoreDoc[] scoreDocs = topDocs.scoreDocs;

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

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

}

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

(1)

キーワードに一致するDocumentを検索するときの条件の指定にはTermQueryを使用します。

TermQueryはコンストラクタにキーワードを示すTermを指定して生成を行っています。

そのTermはコンストラクタで検索対象とするフィールド名と、キーワードを指定して生成を行っています。

ここではフィールド名「txt1」に対し、キーワード「abc」で検索を行うよう条件の指定を行っています。

(2)

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

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

termQuery:txt1:abc
---------- docId:1 ----------
----- fieldName:txt1 -----
fieldText:abc def ghi
----- fieldName:str1 -----
fieldText:jkl mno pqr
---------- docId:3 ----------
----- fieldName:txt1 -----
fieldText:fghi de abc
----- fieldName:str1 -----
fieldText:opqr mn jkl

インデックスの検索を行うときに条件を指定するQueryは、「txt1:abc」のようにフィールド名とキーワードを「:」で区切った形式の文字列で表現されています。

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

続いてキーワードを2つ指定して検索を行う例を以下に示します。

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

//(略)
            BooleanQuery booleanQuery =  new BooleanQuery();//---(1)

            booleanQuery.add(new TermQuery(new Term("txt1", "abc")), Occur.MUST);//---(2)
            booleanQuery.add(new TermQuery(new Term("txt1", "def")), Occur.MUST);

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

            TopDocs topDocs = searcher.search(booleanQuery, 5);//---(3)
//(略)

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

(1)

複数の検索キーワードにAND、ORを組み合わせて条件指定したい場合には、BooleanQueryを使用します。

(2)

BooleanQueryのaddメソッドでQueryを追加していくことで、複数の条件を組み合わせていきます。

第2引数では第1引数に指定したQueryの条件の適用のされ方を、列挙型BooleanClause.Occurで指定します。適用のされ方には以下のようなものがあります。

MUST 指定した条件を必ず満たすDocumentが検索対象となります。
SHOULD SHOULDで指定した条件が複数ある場合に、その内の1つでも条件を満たすDocumentがあれば検索対象となります。
MUST_NOT 指定した条件を満たすDocumentは検索対象外となります。

ここではMUSTの条件を2つ指定して、フィールド名「txt1」にキーワード「abc」と「def」の両方を含むDocumentを検索するようにしています。

(3)

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

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

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

BooleanQueryでMUSTを指定した条件は頭に「+」が付いています。また、2つの条件は半角スペースで区切られています。

検索条件に指定した通り、フィールド名「txt1」にキーワード「abc」と「def」の両方を含むDocumentが検索結果として取得されています。

上記ではMUSTを使用しましたので、続いてSHOULDを使用する例を以下に示します。

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

//(略)
            BooleanQuery booleanQuery =  new BooleanQuery();

            booleanQuery.add(new TermQuery(new Term("txt1", "abc")), Occur.SHOULD);//---(1)
            booleanQuery.add(new TermQuery(new Term("txt1", "ijkl")), Occur.SHOULD);

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

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

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

(1)

ここではSHOULDの条件を2つ指定して、フィールド名「txt1」にキーワード「abc」または「ijkl」を含むDocumentを検索するようにしています。

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

booleanQuery:txt1:abc txt1:ijkl
---------- docId:2 ----------
----- fieldName:txt1 -----
fieldText:abcd efgh ijkl
----- fieldName:str1 -----
fieldText:mnop qrst uvwx
---------- docId:1 ----------
----- fieldName:txt1 -----
fieldText:abc def ghi
----- fieldName:str1 -----
fieldText:jkl mno pqr
---------- docId:3 ----------
----- fieldName:txt1 -----
fieldText:fghi de abc
----- fieldName:str1 -----
fieldText:opqr mn jkl

BooleanQueryでSHOULDを指定した条件は頭に何も付いていません。

検索条件に指定した通り、フィールド名「txt1」にキーワード「abc」または「ijkl」を含むDocumentが検索結果として取得されています。

最後にMUST_NOTを使用する例を以下に示します。

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

//(略)
            BooleanQuery booleanQuery =  new BooleanQuery();

            booleanQuery.add(new TermQuery(new Term("txt1", "abc")), Occur.MUST);//---(1)
            booleanQuery.add(new TermQuery(new Term("txt1", "fghi")), Occur.MUST_NOT);

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

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

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

(1)

ここではMUSTの条件とMUST_NOTの条件を指定して、フィールド名「txt1」にキーワード「abc」を含み、「fghi」を含まないDocumentを検索するようにしています。

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

booleanQuery:+txt1:abc -txt1:fghi
---------- docId:1 ----------
----- fieldName:txt1 -----
fieldText:abc def ghi
----- fieldName:str1 -----
fieldText:jkl mno pqr

BooleanQueryでMUST_NOTを指定した条件は頭に「-」が付いています。

検索条件に指定した通り、フィールド名「txt1」にキーワード「abc」を含み、「fghi」を含まないDocumentが検索結果として取得されています。

Queryの基本動作としましては、BooleanQueryを使用して複数のQueryを組み合わせていくものとなります。組み合わせに使用できるQueryにはここで使用したTermQuery以外にも種類がありますので、それらは別途解説を行います。