*

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のスケーリング 前編 <仕組みについて>

今回は、こちらの公式ドキュメントをもとに、GAEのスケーリングの仕組みと最適化のやり方について紹介し

記事を読む

Search API詳細解説 Part1「Search API 概要説明」

Google App Engine(以降GAE)に全文検索サービスであるSearch APIが正式リ

記事を読む

Search API詳細解説 Part6「Search API 詳細 限界値編」

みなさん、こんにちは。 前回はSearch APIの反映速度について調べてみましたが、いかがだった

記事を読む

Datastoreの仕組み 〜Consistencyについて〜

はじめに スケーラビリティと可用性が高いと言われているGoogle Cloud Datastore

記事を読む

GAEでWordPressを動かす

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

記事を読む

GAE/GOでVideo Intelligence APIを操作してみた!!〜事前準備編〜

先週のGCP Next 17’でGoogleから新しい機械学習APIが発表されました!その名

記事を読む

GCEにPuttyから簡単接続する

年末ではありますが、先日ちょっとGCE(Google Compute Engine)を触る機会がまた

記事を読む

専用Memcacheは共有Memcacheよりも本当に性能が高いのか検証する Part2

前回のGAEの「専用Memcacheは共有Memcacheよりも本当に性能が高いのか検証する」が投稿

記事を読む

GAEのスケーリング 後編 <最適化の実践>

この記事では、こちらの公式ドキュメントをもとに、GAEのスケーリングの仕組みと最適化のやり方について

記事を読む

AppEngine Security Scan Tool

AppEngine Security Scan Toolが利用出来るようになりました。ついては、GA

記事を読む

PAGE TOP ↑