kakiro-webカキローウェブ

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

【Lucene】PhraseQueryについて

検索条件に指定された文字列からQueryを生成する際にはQueryParserを使用することが多くなるかと思いますが、ここではそのQueryParserの動作を理解するために必要なPhraseQueryについてサンプルプログラムを元に解説を行っていきます。

検索対象として以下のような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", "abcdef ghijkl", Field.Store.YES));
            doc.add(new StringField("str1", "mnopqr stuvwx", Field.Store.YES));

            writer.addDocument(doc);

            writer.commit();

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

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

}

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

----- fieldName:str1 -----
fieldText:jkl mno pqr
fieldText:jkl pqr mno
fieldText:mnopqr stuvwx
----- fieldName:txt1 -----
fieldText:abc
fieldText:abcdef
fieldText:def
fieldText:ghi
fieldText:ghijkl

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

Document ID フィールド「txt1」のキーワード フィールド「str1」のキーワード
0 abc jkl mno pqr
def
ghi
1 abc jkl pqr mno
ghi
def
2 abcdef mnopqr stuvwx
ghijkl

上記のようにインデックスに登録したDocumentに対し、PhraseQueryを使用して検索を行う例を以下に示します。

検索を行うサンプルプログラムは、基本処理 インデックスの検索のページで紹介したものを元に、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.PhraseQuery;
import org.apache.lucene.search.ScoreDoc;
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);

            PhraseQuery phraseQuery = new PhraseQuery();//---(1)
            phraseQuery.add(new Term("txt1", "abc"));
            phraseQuery.add(new Term("txt1", "def"));

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

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

            ScoreDoc[] scoreDocs = topDocs.scoreDocs;

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

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

}

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

(1)

PhraseQueryは引数なしのコンストラクタで空の状態で生成を行い、addメソッドでTermを追加していくことでフレーズを組み立てていきます。

ここではフィールド名「txt1」に対し、キーワード「abc」と「def」をこの順で追加しています。

尚、1つのPhraseQueryに対し、異なるフィールド名を持つTermを追加しようとするとエラーになり、追加できないようになっています。

(2)

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

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

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

PhraseQueryを使用したQueryは、「txt1:"abc def"」のようにフィールド名とキーワードを「:」で区切った形式の文字列で表現されています。

TermQuery等とは違い、キーワードの部分は「"」で囲まれるようになっています。

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

PhraseQueryはこのようにキーワードの出現順を考慮した検索を行うものとなっています。

よって、フィールド「txt1」の値が「abc ghi def」となっているDucumentは、キーワードの出現順が異なるため検索対象外となっており、値が「abcdef ghijkl」となっているDucumentは、「abcdef」の部分は1つのキーワードになっているため「abc」、「dfe」の2つのキーワードが連続したものとはみなされず、検索対象外となっています。

PhraseQueryをもう少し理解し易いように続いてCJKAnalyzerを使用して日本語を扱った場合の例を以下に示します。

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

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

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.cjk.CJKAnalyzer;
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 CJKAnalyzer(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", "あいうえお", Field.Store.YES));
            doc.add(new StringField("str1", "かきくけこ", Field.Store.YES));

            writer.addDocument(doc);

            doc = new Document();
            doc.add(new TextField("txt1", "あいうおうえ", Field.Store.YES));
            doc.add(new StringField("str1", "かきくこくけ", Field.Store.YES));

            writer.addDocument(doc);

            doc = new Document();
            doc.add(new TextField("txt1", "あい いう うえ えお", Field.Store.YES));
            doc.add(new StringField("str1", "かき きく くけ けこ", Field.Store.YES));

            writer.addDocument(doc);

            doc = new Document();
            doc.add(new TextField("txt1", "あい いう えお うえ", Field.Store.YES));
            doc.add(new StringField("str1", "かき きく けこ くけ", Field.Store.YES));

            writer.addDocument(doc);

            writer.commit();

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

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

}

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

----- fieldName:str1 -----
fieldText:かき きく くけ けこ
fieldText:かき きく けこ くけ
fieldText:かきくけこ
fieldText:かきくこくけ
----- fieldName:txt1 -----
fieldText:あい
fieldText:いう
fieldText:うえ
fieldText:うお
fieldText:えお
fieldText:おう

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

Document ID フィールド「txt1」のキーワード フィールド「str1」のキーワード
0 あい かきくけこ
いう
うえ
えお
1 あい かきくこくけ
いう
うお
おう
うえ
2 あい かき きく くけ けこ
いう
うえ
えお
3 あい かき きく けこ くけ
いう
えお
うえ

上記のようにインデックスに登録したDocumentに対し、PhraseQueryを使用して検索を行う例を以下に示します。

検索を行うサンプルプログラムは、基本処理 インデックスの検索のページで紹介したものを元に、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.PhraseQuery;
import org.apache.lucene.search.ScoreDoc;
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);

            PhraseQuery phraseQuery = new PhraseQuery();//---(1)
            phraseQuery.add(new Term("txt1", "いう"));
            phraseQuery.add(new Term("txt1", "うえ"));

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

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

            ScoreDoc[] scoreDocs = topDocs.scoreDocs;

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

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

}

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

(1)

PhraseQueryを使用してフィールド名「txt1」に対し、キーワード「いう」と「うえ」をこの順で追加しています。

(2)

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

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

phraseQuery:txt1:"いう うえ"
---------- docId:0 ----------
----- fieldName:txt1 -----
fieldText:あいうえお
----- fieldName:str1 -----
fieldText:かきくけこ
---------- docId:2 ----------
----- fieldName:txt1 -----
fieldText:あい いう うえ えお
----- fieldName:str1 -----
fieldText:かき きく くけ けこ

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

フィールド「txt1」の値が「あいうえお」となっているDucumentは、キーワードが「あい」、「いう」、「うえ」、「えお」と分割されますので、「いう」、「うえ」をこの順で含むものとなり、検索対象になっています。

フィールド「txt1」の値が「あい いう うえ えお」となっているDucumentもキーワードは上記と同じように分割されますので、検索対象になっています。

フィールド「txt1」の値が「あいうおうえ」となっているDucumentは、キーワードが「あい」、「いう」、「うお」、「おう」、「うえ」と分割され、「いう」、「うえ」をこの順では含みませんので、検索対象外になっています。

フィールド「txt1」の値が「あい いう えお うえ」となっているDucumentは、キーワードが「あい」、「いう」、「えお」、「うえ」と分割され、「いう」、「うえ」をこの順では含みませんので、検索対象外になっています。

PhraseQueryの使用例は以上です。