*

Search APIの「Faceted Search」を使ってみた

公開日: : 最終更新日:2015/05/27 投稿者: GAE ,

Search API詳細解説シリーズは完結しましたが、2015年2月19日のAppEngine SDK 1.9.18のリリースでSearch APIの機能が増えましたので、その新機能を少し使ってみたいと思います。
追加された新機能は、「Faceted Search」という機能です。
※「Faceted Search」機能は2015年5月22日時点でBeta版です。
公式ドキュメントは下記ページにあります。
https://cloud.google.com/appengine/docs/java/search/faceted_search

「Faceted Search」って何?

「Faceted Search」は、サイトによっては、「ファセットナビゲーション」や「ファセットブラウジング」、「ドリルダウン検索」と呼ばれたりします。
例えば、下記の画像は某通販サイトのカテゴリー「本」に関する「ファセットナビゲーション」です。

ファセットナビゲーション とは 意味 解説 説明 (ファセットナビゲーション) 【Faceted Navigation】

カテゴリー「本」に関して、ジャンルやフォーマットの種類と件数がひと目で分かり、とても使いやすいかと思います。

今までのSearch APIではできなかったの?

今までのSearch APIでは取得できる情報は大まかに、

  • 実行したクエリで取得できるDocumentの全件数
  • 実際に取得できたDocumentの件数
  • 取得できたDocumentそのもの
  • カーソル情報

でした。
なので、Documentの件数は取得できるものの、複数のフィールドに何の値が何件あるのかまで分かりません。各値毎に検索をして件数を出す必要があり、上記Amazonの例だと、カテゴリやフォーマット等の数分だけクエリを流す必要がありました。
それを簡単にカテゴリ別の件数がわかるようになると言うものです。

登録してみる

今回の「使ってみた」では次のようなDocumentを合計50件登録してみます。

フィールド名 フィールドタイプ Faceted Search対象
name TEXT 対象外
bloodType ATOM 対象。検索時には、A、B、O、ABの4つの血液型のそれぞれの件数を出したいと思います。
age NUMBER 対象。検索時には、0〜9歳、10〜19歳、20〜29歳、30〜39歳、40〜49歳、50歳以上の6つに分けた時のそれぞれの件数を出したいと思います。

 

では早速使ってみましょう。
Documentの登録方法は過去に説明した実装とほとんど同じです。

// import com.google.appengine.api.search.Facet;

IndexSpec indexSpec = IndexSpec.newBuilder().setName("FacetedSearch").build();
Index index = SearchServiceFactory.getSearchService().getIndex(indexSpec);

Builder builder = Document.newBuilder().setId("1").setLocale(Locale.ENGLISH);
Field.Builder textFieldBuilder = Field.newBuilder();
textFieldBuilder.setName("name").setText("テスト太郎1");
builder.addField(textFieldBuilder);
Field.Builder atomFieldBuilder = Field.newBuilder();
atomFieldBuilder.setName("bloodType").setAtom("A");
builder.addField(atomFieldBuilder);
Field.Builder numFieldBuilder = Field.newBuilder();
numFieldBuilder.setName("age").setNumber(1);
builder.addField(numFieldBuilder);

// ↓↓↓↓↓↓ここからFaceted Searchの登録処理↓↓↓↓↓↓
// Faceted Searchする項目と値を登録する。Faceted Searchとして登録できる型はATOM型とNUMBER型の2種類だけです。
builder.addFacet(Facet.withAtom("bloodType", "A")); // ATOM型として、項目名「bloodType」と値「A」を設定
builder.addFacet(Facet.withNumber("age", Double.valueOf(1))); // NUMBER型として、項目名「age」と値「1」を設定
// ↑↑↑↑↑↑ここまでFaceted Searchの登録処理↑↑↑↑↑↑

index.put(builder);

※Documentの以前説明した登録処理の部分の説明は割愛します。過去の記事を読んでください。

AppEngine SDK 1.9.18以降では、「com.google.appengine.api.search.Document.Builder」に「addFacet」というメソッドが追加されており、このメソッドにFaceted Search対象のフィールド名とその値を指定すればOKです。
設定できるFacetの種類は、ATOMとNumberの2つだけとなっております。
Search APIの管理画面からの見た目は変わっていませんが、これでFaceted Searchができる準備が整いました。

今回登録した血液型、年齢の各条件の件数はそれぞれ以下のようになっています。

項目 設定項目名 条件 件数
血液型 bloodType A 14件
B 10件
O 11件
AB 15件
年齢 age 10歳未満 9件
10歳以上20歳未満 10件
20歳以上30歳未満 10件
30歳以上40歳未満 10件
40歳以上50歳未満 10件
50歳以上 1件

次は、実際に検索してみましょう。

検索してみる

// import com.google.appengine.api.search.FacetRange;
// import com.google.appengine.api.search.FacetRequest;
// import com.google.appengine.api.search.Query.Builder;

Builder queryBuilder = Query.newBuilder();

// ATOM型の値をFaceted Searchする場合は、以下のように指定します。
// 下記では「bloodType」というFacetの名前に対して、A,B,O,ABがそれぞれ何件あるのか集計を実行します。
queryBuilder.addReturnFacet(FacetRequest.newBuilder()
    .setName("bloodType").addValueConstraint("A")
    .addValueConstraint("B").addValueConstraint("O")
    .addValueConstraint("AB"));

// 数値型の値をFaceted Searchする場合は、以下のように指定します。
// FacetRange.withEndは指定した引数の値未満が対象。
// FacetRange.withStartは指定した引数の値以上が対象。
// FacetRange.withStartEndは第一引数の値以上、第二引数の値未満が対象。
// 下記では「age」というFacetの名前に対して、集計を実行します。
queryBuilder.addReturnFacet(FacetRequest.newBuilder().setName("age")
    .addRange(FacetRange.withEnd(10.0))// 10歳未満の件数をカウント
    .addRange(FacetRange.withStartEnd(10.0, 20.0))// 10歳以上20歳未満の件数をカウント
    .addRange(FacetRange.withStartEnd(20.0, 30.0))// 20歳以上30歳未満の件数をカウント
    .addRange(FacetRange.withStartEnd(40.0, 50.0))// 40歳以上50歳未満の件数をカウント
    .addRange(FacetRange.withStart(50.0)));// 50歳以上の件数をカウント

// 検索実行
IndexSpec indexSpec = IndexSpec.newBuilder().setName("FacetedSearch").build();
Index index = SearchServiceFactory.getSearchService().getIndex(indexSpec);
Results<ScoredDocument> results = index.search(queryBuilder.build(""));

com.google.appengine.api.search.Query.Builderに「addReturnFacet」というメソッドがありますので、そのメソッドでFaceted Searchの集計処理の設定を行ないます。

 

結果を取得する

// 検索の実行結果から、getFacetsでFaceted Searchの結果をFacet単位で取得することができます。
Collection<FacetResult> facets = results.getFacets();
System.out.println("total count=" + results.getNumberFound()); // 検索結果の合計件数を出力
// Faceted Searchの結果を出力
for (FacetResult facet : facets) {
    // Facetの名前を出力
    // FacetRequest.newBuilder().setNameに設定した値が出力されます。
    System.out.println("FacetResult name=" + facet.getName());
    List<FacetResultValue> values = facet.getValues();
    for (FacetResultValue value : values) {
        // FacetResultValue.getLabelでは、
        // FacetRequest.newBuilder().addValueConstraint、
        // もしくは、FacetRequest.newBuilder().addRangeで設定した集計条件が出力されます。
        // FacetResultValue.getCountでは、集計件数が出力されます。
        System.out.println("FacetResultValue label=" + value.getLabel() + "/ count=" + value.getCount());
    }
}

実際の出力結果は以下のようになりました。

total count=50
FacetResult name=age
FacetResultValue label=[-Infinity,10.0)/ count=9
FacetResultValue label=[10.0,20.0)/ count=10
FacetResultValue label=[20.0,30.0)/ count=10
FacetResultValue label=[30.0,40.0)/ count=10
FacetResultValue label=[40.0,50.0)/ count=10
FacetResultValue label=[50.0,Infinity)/ count=1
FacetResult name=bloodType
FacetResultValue label=AB/ count=15
FacetResultValue label=A/ count=14
FacetResultValue label=O/ count=11
FacetResultValue label=B/ count=10

それぞれの件数は登録したものと同じですね。
ですが、FacetRequest.newBuilder().addRangeのgetLabelの値ですが、開始カッコは「[」ですが、閉じカッコが「)」になってますね……これは表示の不具合なのでしょうか?
上限、下限を設定していないという意味なのでしょうが、「Infinity」という表現に一瞬「ん?」となってしまいました。
2015年5月22日時点で最新のAppEngine SDK 1.9.21でも同じように出力されましたが、「Faceted Search」はまだBeta版ですので、これから修正はいろいろと入ると思います。
既存のDocumentを「Faceted Search」する場合は、「Faceted Search」の設定をしたDocumentとして再登録をしなければならないので少し手間ですね。
できれば検索条件だけで「Faceted Search」ができればいいんですが……

 

RDBMSに馴染み深い方にとっては「GROUP BY」が使用できるようになったと考えたほうがわかりやすいかもしれません。
検索に特化したデータベースであるSearch APIに「Faceted Search」のような機能が追加されるのは嬉しいですね。正式リリースが待ち遠しい限りです。
簡単にでしたが、「Faceted Search」の実装について説明はここまでです。
読んで下さり、ありがとうございました。

タイトル
Part1 Search API 概要説明
Part2 Search APIの使い方 登録・削除編
Part3 Search APIの使い方 検索編
Part4 Search API 詳細 検索性能編
Part5 Search API 詳細 反映速度編
Part6 Search API 詳細 限界値編

吉積情報は、Google App Engine やSearch APIを活用したクラウドシステム開発の専門集団です。
お仕事の依頼、開発者の募集を常に行っておりますので、興味のある方はお問い合わせください。

関連記事

GAEでWordPressを動かす

これは2014年時点での記事になります。 2015年版の記事がありますのでそちらをご参照下さい。

記事を読む

PaaS最前線!たったの15分でできるGAE/GO入門!

はじめに 2015年7月ついにGoogle App EngineのGO言語正式サポートが決定しまし

記事を読む

GAEでよくあるエラーの発生原因と対策1

GAE上で動くWebアプリケーションに特有の例外について、弊社での運用の事例からいくつか特徴的なもの

記事を読む

Search API詳細解説 Part3「Search APIの使い方 検索編」

Search API詳細解説シリーズ タイトル Part1Search API 概要説明

記事を読む

AppEngineでTwilioを試してみた(基本編)

AppEngineでTwilioを試してみた(基本編) AppEngineでTwilioを試し

記事を読む

AppEngineでTwilioを試してみた(応用編)

AppEngineでTwilioを試してみた(基本編) AppEngineでTwilioを試し

記事を読む

Search API詳細解説 Part5「Search API 詳細 反映速度編」

Search API詳細解説シリーズ タイトル Part1Search API 概要説明

記事を読む

たったの15分でできるGAE/GO入門 標準APIその1

知っておけば必ず開発が楽になる! GAE/Go入門の本連載ですが、前回は第一弾として「PaaS最前

記事を読む

Prediction API入門(後編)

今回はPrediction API on GAE/J みなさん、こんにちは。Prediction

記事を読む

1つのエンティティにプロパティをいくつまで作れるか(パート2)

1つのエンティティにプロパティをいくつまで作れるか 1つのエンティティにプロパティをいくつまで

記事を読む

PAGE TOP ↑