ファセット検索

ファセット検索により、ドキュメントにカテゴリ情報を追加できます。ファセットは属性と値のペアです。たとえば、「size」というファセットが「small」、「medium」、「large」などの値を持つとします。

検索でファセットを使用すると、一連のステップでクエリを絞り込み、結果に「ドリルダウン」するために役立つ概要情報を取得できます。

これはショッピング サイトのような用途で有用な機能であり、購入者が見たい商品を絞り込めるように一連のフィルタを提供できます。

ファセットの集計データは、ファセットの値の分布状態を示します。たとえば、ファセット「size」が結果セットで多くのドキュメントに出現しているとします。そのファセットの集計データには、「small」の値が 100 回出現し、「medium」が 300 回、「large」が 250 回出現したことが示されます。各ファセットと値のペアは、クエリ結果内のドキュメントのサブセットを表します。各ペアには refinement というキーが関連付けられています。クエリに絞り込みを含めると、クエリ文字列に一致し、1 つ以上の絞り込みに対応するファセット値のあるドキュメントを取得できます。

検索を実行する場合、収集し、結果で表示するファセットを選択し、ファセット検出を有効にして、ドキュメントで最も頻繁に出現するファセットを自動的に選択できます。

ドキュメントにファセットを追加する

インデックスにドキュメントを追加する前に、ドキュメントにファセットを追加します。これはドキュメントのフィールドを指定するときに同時に行います。

package com.google.test.facet;

import java.io.IOException;
import javax.servlet.http.*;
import com.google.appengine.api.search.*;

public class FacetsearchjavaServlet extends HttpServlet {
  public void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    Document doc1 = Document.newBuilder()
      .setId("doc1")
      .addField(Field.newBuilder().setName("name").setAtom("x86"))
      .addFacet(Facet.withAtom("type", "computer"))
      .addFacet(Facet.withNumber("ram_size_gb", 8.0))
      .build();
    IndexSpec indexSpec = IndexSpec.newBuilder().setName("products").build();
      Index index = SearchServiceFactory.getSearchService().getIndex(indexSpec);
    index.put(doc1);
  }
}

ファセットはドキュメント フィールドに似ており、名前と 1 つの値を持ちます。

ファセット名はドキュメント フィールドと同じルールに従います。名前は大文字と小文字が区別され、ASCII 文字のみ含めることができます。それらは文字で始まり、文字、数字、またはアンダースコアを含めることができます。名前は 500 文字を超えることはできません。

ファセットの値は、アトミック文字列(500 文字以内)または数値(-2147483647 から 2147483647 までの倍精度浮動小数点値)のいずれかです。

1 つのドキュメントのファセットに複数の値を割り当てるには、毎回異なる値を使用して、同じ名前と型のファセットを何回も追加します。

ファセットに指定できる値の数に上限はありません。ドキュメントに追加できるファセット数やインデックス内の一意の名前が付けられたファセット数にも上限はありません。

ファセットを使用するときは毎回、アトミック値または数値のどちらでも指定できます。「size」の名前を持つファセットは、文字列値「small」で 1 つのドキュメントに追加し、数値 8 で別のドキュメントに追加できます。実際に、同じファセットが両方の種類の値で、同じドキュメントに複数回出現できます。可能であっても同じファセットにアトミック値と数値の両方を使用することはおすすめできません。

ファセットはドキュメントに追加する際に特定の型を持ちますが、検索結果は、その値をすべてまとめて収集します。たとえば、ファセット「size」に、値「small」の 100 インスタンス、「medium」の 150 インスタンス、および範囲 [4, 8) の数値の 135 インスタンスがあったことが示されます。正確な数値とそれらの頻度分布は表示されません。

クエリを使用してドキュメントを取得する場合、そのファセットや値に直接アクセスすることはできません。次のセクションで説明するように、クエリでファセット情報が返されるようにリクエストする必要があります。

ファセット検索を使用してファセット情報を取得する

検索バックエンドに、最も頻繁に使用されているファセットの検出を要求できます。これは自動ファセット検出と呼ばれます。名前または名前と値でファセットを選択して、ファセット情報を明示的に取得することもできます。1 つのクエリで 3 種類すべてのファセット取得を混在させ、照合させることができます。

ファセット情報の要求は、クエリで返されるドキュメントに影響しません。それはパフォーマンスに影響する可能性があります。デフォルトの深さ 1,000 のファセット検索を実行することは、並べ替えオプション スコア上限を 1,000 に設定するのと同じ効果があります。

自動ファセット検出

自動ファセット検出はドキュメントの集計で最も頻繁に出現するファセットを探します。たとえば、クエリに一致するドキュメントに、値「red」で 5 回、値「white」で 5 回、値「blue」で 5 回出現する「color」ファセットが含まれているとします。このファセットの合計カウントは 15 になります。検出の目的で、これは同じ一致するドキュメントに、値「dark」で 6 回および値「light」で 7 回出現する別のファセット「shade」より上の順位になります。

クエリにファセット検出を設定して有効にする必要があります。

package com.google.test.facet;

import java.io.IOException;
import javax.servlet.http.*;
import com.google.appengine.api.search.*;

public class FacetsearchjavaServlet extends HttpServlet {
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    IndexSpec indexSpec = IndexSpec.newBuilder().setName("products").build();
      Index index = SearchServiceFactory.getSearchService().getIndex(indexSpec);
      Results<ScoredDocument> result = index.search(
          Query.newBuilder().setEnableFacetDiscovery(true) // enable discovery
          .build("name:x86"));
      for(FacetResult facetResult : result.getFacets()) {
        resp.getWriter().printf("Facet %s:\n", facetResult.getName());
        for (FacetResultValue facetValue : facetResult.getValues()) {
          resp.getWriter().printf("   %s: Count=%s, RefinementKey=%s\n",
              facetValue.getLabel(),
              facetValue.getCount(),
              facetValue.getRefinementToken());
        }
      }
  }
}

検出によってファセットを取得する場合、デフォルトでは、ファセットの最も頻繁に出現する 10 個の値のみが返されます。FacetOptions.Builder.setDiscoveryValueLimit() を使用すると、この上限を 100 まで増やすことができます。

自動ファセット検出では、可能性のあるすべてのファセットとその値が返されるわけではない点に注意してください。検出で返されるファセットは、実行ごとに異なる可能性があります。固定された一連のファセットが必要な場合は、クエリで return_facets パラメータを使用します。

文字列値は個別に返されます。検出されたファセットの数値は 1 つの範囲 [min max) で返されます。この範囲を調査して、後のクエリ用に小さな部分範囲を作成できます。

名前でファセットを選択する

名前のみでファセットに関する情報を取得するには、ReturnFacet オブジェクトをクエリに追加し、ファセット名を指定します。

package com.google.test.facet;

import java.io.IOException;
import javax.servlet.http.*;
import com.google.appengine.api.search.*;

public class FacetsearchjavaServlet extends HttpServlet {
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    IndexSpec indexSpec = IndexSpec.newBuilder().setName("products").build();
      Index index = SearchServiceFactory.getSearchService().getIndex(indexSpec);
      Results<ScoredDocument> result = index.search(Query.newBuilder()
        .addReturnFacet("type")
        .addReturnFacet("ram_size_gb")
        .build("name:x86"));
      for(FacetResult facetResult : result.getFacets()) {
        resp.getWriter().printf("Facet %s:\n", facetResult.getName());
        for (FacetResultValue facetValue : facetResult.getValues()) {
          resp.getWriter().printf("   %s: Count=%s, RefinementKey=%s\n",
              facetValue.getLabel(),
              facetValue.getCount(),
              facetValue.getRefinementToken());
        }
      }
  }
}

名前でファセットを取得する場合、デフォルトでファセットの最も頻繁に出現する 10 個の値のみが返されます。FacetOptions.Builder.setDiscoveryValueLimit() を使用すると、この上限を 20 まで増やすことができます。

名前と値でファセットを選択する

特定の値でファセットを取得するには、FacetRequest を含む ReturnFacet オブジェクトを追加します。

package com.google.test.facet;

import java.io.IOException;
import javax.servlet.http.*;
import com.google.appengine.api.search.*;

public class FacetsearchjavaServlet extends HttpServlet {
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        IndexSpec indexSpec = IndexSpec.newBuilder().setName("products").build();
        Index index = SearchServiceFactory.getSearchService().getIndex(indexSpec);
        // Fetch the "type" facet with values "computer and "printer"
        // along with the "ram_size_gb" facet with values in the ranges [0,4), [4, 8), and [8, max]
        Results<ScoredDocument> result = index.search(Query.newBuilder()
            .addReturnFacet(FacetRequest.newBuilder()
                .setName("type")
                .addValueConstraint("computer")
                .addValueConstraint("printer"))
            .addReturnFacet(FacetRequest.newBuilder()
                .setName("ram_size_gb")
                .addRange(FacetRange.withEnd(4.0))
                .addRange(FacetRange.withStartEnd(4.0, 8.0))
                .addRange(FacetRange.withStart(8.0)))
            .build("name:x86"));
        for(FacetResult facetResult : result.getFacets()) {
            resp.getWriter().printf("Facet %s:\n", facetResult.getName());
            for (FacetResultValue facetValue : facetResult.getValues()) {
                resp.getWriter().printf("   %s: Count=%s, RefinementKey=%s\n",
                        facetValue.getLabel(),
                        facetValue.getCount(),
                        facetValue.getRefinementToken());
            }
        }
    }
}

1 つの FacetRequest 内の値はすべて、文字列値のリストまたは FacetRanges のリスト(数値の場合)のいずれか同じ型でなければなりません。数値の場合は、左側(開始)はその値を含み、右側(終了)はその値を含まない区間で指定します。ファセットに文字列値と数値が混在している場合は、それぞれに個別の FacetRequests を追加します。

オプション

ファセット検索を制御するには、FacetOptions パラメータをクエリ呼び出しに追加します。このパラメータは FacetOptions の 1 つのインスタンスを取得します。このパラメータを使用して、ファセット検索のデフォルトの動作をオーバーライドします。

Results<ScoredDocument> results = index.search(Query.newBuilder()
  .addReturnFacet(FacetRequest.newBuilder()
    .setName("type")
    .addValueConstraint("computer")
    .addValueConstraint("printer"))
  .addReturnFacet(FacetRequest.newBuilder()
    .setName("ram_size_gb")
    .addRange(FacetRange.withEnd(4.0))
    .addRange(FacetRange.withStartEnd(4.0, 8.0))
    .addRange(FacetRange.withStart(8.0)))
  .setFacetOptions(FacetOptions.newBuilder()
    .setDiscoveryLimit(5)
    .setDiscoveryValueLimit(10)
    .setDepth(6000).build());
  .build("some_query");
パラメータ 説明 デフォルト
DiscoveryLimit ファセット検出が有効にされている場合に、検出するファセット数。0 の場合、ファセットの検出が無効にされます。 10
DiscoveryValueLimit 検出された上位の各ファセットについて返される値の数。 10
Depth ファセット情報を収集するために評価するクエリ結果内のドキュメントの最小数。 1,000

Depth オプションは、名前、名前と値、自動検出による 3 つすべての種類のファセット集計に適用されます。他のオプションは自動検出専用です。

ファセットの深さは通常クエリ上限よりはるかに大きいことに注意してください。ファセット結果は、ドキュメントの深さ数以上に計算されます。並べ替えオプションを深さより大きいスコア制限に設定している場合、スコア上限が代わりに使われます。

ファセット結果の取得

クエリでファセット検索パラメータを使用すると、集計されたファセット情報がクエリ結果自体に付属します。

クエリには FacetResult のリストが含まれます。クエリに一致したドキュメントに出現するファセットごとに、1 つの結果がリストにあります。各結果で、次を取得します。

  • ファセット名
  • ファセットの最も頻繁な値のリスト。値ごとに、その出現回数のおおよそのカウント、このクエリとファセット値に一致するドキュメントを取得するために使用できる絞り込みキーがあります。

値のリストには、ファセットの文字列数値が含まれることに注意してください。ファセットが自動検出された場合、その数値が 1 つの間隔 [min max) として返されます。クエリで 1 つ以上の範囲を持つ数値ファセットを明示的に要求した場合、リストには範囲ごとに 1 つの closed-open 間隔 [start end) が含まれます。

クエリ オプションによって、調査するドキュメント数や返される値の数が指定されているため、ファセット値のリストに、ドキュメントで見つかったすべての値が含まれないことがあります。

各ファセットの集計情報を検索結果から読み取ることができます。

Results<ScoredDocument> results = index.search(...);
for (FacetResult facetInfo : results.getFacets()) {
  ...
}

たとえば、クエリによって文字列値と数値の「size」ファセットを含むドキュメントが見つかったとします。このファセットの FacetResult は次のように構築されます。

FacetResult.newBuilder()
        .setName("size")
        .addValue(FacetResultValue.create("[8, 10)", 22, refinement_key)
        .addValue(FacetResultValue.create("small", 100, refinement_key)
        .addValue(FacetResultValue.create("medium", 300, refinement_key)
        .addValue(FacetResultValue.create("large", 250, refinement_key).build());

FacetResultValue.label はファセット値から構築されます。数値は範囲の「stringified」表現として返されます。

refinement_key は、後でクエリで使用して、その結果のファセット名と値に一致するドキュメントを取得できるウェブ / URL セーフ文字列です。

ファセットを使用してクエリを絞り込み / フィルタする

Query query = Query.newBuilder()
  .addFacetRefinementFromToken(refinement_key1)
  .addFacetRefinementFromToken(refinement_key2)
  .addFacetRefinementFromToken(refinement_key3)
  .build("some_query");

同じリクエストで、1 つ以上の異なるファセットの絞り込みを組み合わせることができます。同じファセットに属するすべての絞り込みは OR で結合します。異なるファセットの絞り込みは AND で組み合わせます。

手動でカスタム FacetRefinement キーを作成することもできます。詳細については、クラスのドキュメントをご覧ください。