クエリの概要

このページでは、Spanner テーブルに対する全文検索クエリの実行に使用される SEARCH 関数と拡張クエリモードについて説明します。

検索インデックスをクエリする

Spanner には、検索インデックス クエリ用の SEARCH 関数があります。SEARCH 関数には次の 2 つの引数が必要です。

  • 検索インデックス名
  • rquery

SEARCH 関数は、検索インデックスが定義されている場合にのみ機能します。SEARCH 関数は、フィルタ、集計、結合など、任意の SQL 構成と組み合わせることができます。

次のクエリは、SEARCH 関数を使用して、タイトルに friday または monday を含むすべてのアルバムを返します。

SELECT AlbumId
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, 'friday OR monday')

rquery 言語

SEARCH 関数の 2 番目の引数は、rquery というドメイン固有の言語(DSL)です。rquery 言語は、インターネット ユーザーが google.com で慣れ親しんでいる言語に似ています。

  • 複数の用語は AND を意味します。たとえば、「Big Time」は big AND time と同等です。
  • OR オペレーションは、big OR time など、2 つの用語が分離していることを示します。ステートメント SEARCH(tl, 'big time OR fast car') は以下と同等です。

    SEARCH(tl, 'big')
    AND (SEARCH(tl, 'time')
         OR SEARCH(tl, 'fast'))
    AND SEARCH(tl, 'cat');
    

    OR は、隣接する 2 つの語句にのみ適用されます。たとえば、検索式 happy friday OR monday を使用すると、用語 happy と、friday または monday のいずれかを含んだすべてのドキュメントが検索されます。

  • 二重引用符はフレーズ検索を意味します。たとえば、"big cat" という rquery は、「I have a big cat」には一致しますが、「My cat is big」には一致しません。

  • AROUND 演算子は、互いに特定の距離内にある用語を照合します。たとえば、big AROUND cat という rquery は「big, white, and fluffy cat」と一致しますが、「big dog was sitting next to a small cat」とは一致しません。デフォルトでは、最大 5 つの位置で区切られた用語と一致します。距離を調整するには、AROUND 演算子に引数を渡します。Spanner は、AROUND の次の 2 つの構文をサポートしています。

    • big AROUND(10) cat
    • big AROUND 10 cat
  • AROUND 演算子は、フレーズ内の一部として使用すると、一定の距離内で同じ順序の語句を照合できます(デフォルトは 5 トークン)。たとえば、文字列 big AROUND cat は「bigblue and fluffy cat」には一致しますが、「cat was big」には一致しません。

  • 単一トークンの否定はダッシュ(-)で表します。たとえば、-dog は、語句 dog が含まれていないすべてのドキュメントに一致します。

  • 通常、文末の記号は無視されます。たとえば、「Big Time!」は「Big Time」と同じです。

  • 検索では大文字と小文字が区別されません。たとえば、「Big Time」は「big time」と一致します。

  • The OR 演算子と AROUND 演算子では大文字と小文字が区別されます。パイプ文字(|)は OR のショートカットです。

rquery 言語の仕組み

rquery 言語は、入力文字列を個別の語句に分割するときに、プレーンテキスト トークナイザーと同じルールに従います。これには、アジアの言語のセグメンテーションも含まれます。

多くのアプリでは、ユーザーが検索ボックスに検索語句を入力できるようにしています。このようなエンドユーザー クエリを統合する最も簡単な方法は、ユーザー入力を SEARCH 関数に直接送信することです。

次の表に、さまざまな rquery 文字列の意味を示します。

rquery 説明
Big Cat 「big」と「cat」の両方のキーワードを含むドキュメントを照合します。
cat OR dog 「cat」と「dog」のいずれかを含むドキュメントを照合します。
-cat 「cat」という用語を含まないすべてのドキュメントを照合します。
"big dog" -"big cat" 2 つの連続する語句「big」と「dog」を含むドキュメントに一致しますが、「big」と「cat」が連続して含まれているドキュメントには一致しません。たとえば、次のクエリは「I have a big dog and a small cat」には一致しますが、「I have a big dog and a big cat」には一致しません。
cat|dog これは「cat OR dog」と同じものです。
and OR or 「and」または「or」のいずれかのキーワードを含むドキュメントを照合します(OR 演算子は大文字にする必要があります)。

拡張クエリモード

Spanner は、トークンベースの全文検索に加えて、enhance_query というより高度なモードもサポートしています。このモードでは、より多くのトークン バリアントが含まれるように検索クエリが拡張されます。これらの書き換えにより、検索の再現性が向上します。

このオプションを有効にするには、SEARCH 関数でオプションの引数 enhance_query=>true を設定します。たとえば、検索クエリ hotl cal はアルバム Hotel California と一致します。

SELECT AlbumId
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, 'hotl cal', enhance_query=>true)

enhance_query モードはクエリ時のオプションです。トークン化には影響しません。enhance_query の有無にかかわらず、同じ検索インデックスを使用できます。

Google は、クエリ拡張アルゴリズムの改善に継続的に取り組んでいます。その結果、enhance_query == true を含むクエリでは、時間の経過とともに結果が若干異なる場合があります。

enhance_query モードを有効にすると、SEARCH 関数が検索する語句の数が増え、レイテンシがわずかに増加する可能性があります。

たとえば、次のクエリは 3 秒のタイムアウトを使用します。enhance_query が使用できない場合、クエリは失敗します。

@{require_enhance_query=true, enhance_query_timeout_ms=3000}
SELECT AlbumId
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, 'cat', enhance_query=>true)

SQL クエリの要件

検索インデックスを使用するには、SQL クエリがいくつかの条件を満たしている必要があります。これらの条件が満たされない場合、クエリは代替のクエリプランを使用するか、代替プランが存在しない場合は失敗します。

クエリは次の条件を満たしている必要があります。

  • SEARCH 関数と SEARCH_SUBSTRING 関数には検索インデックスが必要です。Spanner は、ベーステーブルまたはセカンダリ インデックスに対するクエリでこれらの関数をサポートしていません。クエリ対象
  • パーティション分割インデックスには、クエリの WHERE 句の等式条件でバインドされたすべてのパーティション列が必要です。

    たとえば、検索インデックスが PARTITION BY x, y として定義されている場合、クエリの x = <parameter or constant> AND y = <parameter or constant>WHERE 句で連結詞が必要です。このような条件がない場合、クエリ オプティマイザーは検索インデックスを考慮しません。

  • SEARCH 演算子と SEARCH_SUBSTRING 演算子によって参照されるすべての TOKENLIST 列は、同じ検索インデックスにインデックス付けされている必要があります。

    たとえば、次のテーブルとインデックスの定義について考えてみましょう。

    CREATE TABLE Albums (
        AlbumId STRING(MAX) NOT NULL,
        AlbumTitle STRING(MAX),
        AlbumStudio STRING(MAX),
        AlbumTitle_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(AlbumTitle)) HIDDEN,
        AlbumStudio_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(AlbumStudio)) HIDDEN
      ) PRIMARY KEY(AlbumId);
    
      CREATE SEARCH INDEX AlbumsTitleIndex ON Albums(AlbumTitle_Tokens);
      CREATE SEARCH INDEX AlbumsStudioIndex ON Albums(AlbumStudio_Tokens);
    

    次のクエリは、AlbumTitle_TokensAlbumStudio_Tokens の両方をインデックスに登録する単一の検索インデックスがないため、失敗します。

    SELECT AlbumId
    FROM Albums
    WHERE SEARCH(AlbumTitle_Tokens, @p1) AND SEARCH(AlbumStudio_Tokens, @p2)
    
  • 並べ替え順列が NULL 可能である場合、スキーマとクエリの両方で、並べ替え順列が NULL である行を除外する必要があります。詳細については、検索インデックスの並べ替え順序をご覧ください。

  • 検索インデックスが NULL でフィルタされている場合、クエリにはインデックスで使用されている NULL フィルタリング式を含める必要があります。詳細については、NULL でフィルタされた検索インデックスをご覧ください。

  • 検索インデックスは、DML、パーティション化 DML、パーティション分割クエリではサポートされていません。

  • 検索インデックスは通常、読み取り専用トランザクションで使用されます。アプリケーションの要件で古い結果が許容される場合は、古いデータの保持期間が 10 秒以上の検索クエリを実行することをおすすめします。詳細については、古いデータを読み取るをご覧ください。これは、複数の Paxos グループにファンアウトする大規模な検索クエリに特に便利です。

    読み取り / 書き込みトランザクションでは、検索インデックスはおすすめしません。実行中、検索クエリはインデックス パーティション全体をロックします。その結果、読み取り / 書き込みトランザクションで検索クエリが多いとロックの競合が発生し、レイテンシの急増が発生する可能性があります。デフォルトでは、読み取り / 書き込みトランザクションで検索インデックスは自動的に選択されません。読み取り / 書き込みトランザクションでクエリが検索インデックスを使用するように強制された場合、デフォルトでは失敗します。この動作は、@{ALLOW_SEARCH_INDEXES_IN_TRANSACTION=TRUE} ステートメント レベルのヒントでオーバーライドできます(ただし、クエリではロックの競合が発生しやすくなります)。

インデックスの資格条件が満たされると、クエリ オプティマイザーはテキスト以外のクエリ条件(Rating > 4 など)を高速化しようとします。検索インデックスに適切な TOKENLIST 列が含まれていない場合、条件は加速されず、残余条件のままです。

クエリ パラメータ

rquery や OFFSET などの他のパラメータは、リテラルまたはクエリ パラメータのいずれかとして指定されます。文字列リテラルではなくクエリ パラメータを使用することをおすすめします。パラメータ化されたクエリは、クエリ キャッシュのヒット率が向上するため、クエリのレイテンシが低くなり、全体的な CPU 使用率が低くなります。

たとえば、次のようなクエリを使用する代わりに、

SELECT AlbumId FROM Albums WHERE SEARCH(AlbumTitle_Tokens, 'cat')

次の構文を使用します。

SELECT AlbumId FROM Albums WHERE SEARCH(AlbumTitle_Tokens, @p)

Spanner は、個別の SQL テキストに対してクエリ オプティマイザーを実行します。アプリケーションで使用する個別の SQL テキストが少ないほど、クエリの最適化が呼び出される回数が少なくなります。

検索インデックスの並べ替え順

検索インデックスの並べ替え順序の動作は、セカンダリ インデックスとは異なります。

たとえば、次の表を検討します。

CREATE TABLE Albums (
  AlbumId STRING(MAX) NOT NULL,
  ReleaseTimestamp INT64 NOT NULL,
  AlbumName STRING(MAX),
  AlbumName_Token TOKENLIST AS (TOKEN(AlbumName)) HIDDEN
) PRIMARY KEY(AlbumId);

アプリケーションは、ReleaseTimestamp で並べ替えられた AlbumName を使用して情報を検索するセカンダリ インデックスを定義できます。

CREATE INDEX AlbumsSecondaryIndex ON Albums(AlbumName, ReleaseTimestamp DESC);

同等の検索インデックスは次のようになります(セカンダリ インデックスは全文検索をサポートしていないため、完全一致トークン化を使用します)。

CREATE SEARCH INDEX AlbumsSearchIndex
ON Albums(AlbumName_Token)
ORDER BY ReleaseTimestamp DESC;

検索インデックスの並べ替え順序は、次のルールに従う必要があります。

  1. 検索インデックスの並べ替え順序には、INT64 列のみを使用します。Spanner は各トークンの横に Docid を保存する必要があるため、列のサイズが任意に低い場合、検索インデックスでリソースの使用量が多すぎることになります。具体的には、TIMESTAMP では 64 ビット整数には収まらないナノ秒の精度が使用されるため、並べ替え順序の列で TIMESTAMP 型を使用することはできません。
  2. 並べ替え順序の列は NULL にできません。この要件を満たすには、次の 2 つの方法があります。

    1. 並べ替え順序の列を NOT NULL として宣言します。
    2. NULL 値を除外するようにインデックスを構成します。

実際には、タイムスタンプを使用して並べ替え順序を決定することがよくあります。一般的な方法は、このようなタイムスタンプに Unix エポック時刻からのマイクロ秒数を使用することです。

アプリケーションは通常、降順で並べ替えられた検索インデックスを使用して、まず最新のデータを取得します。

インデックスの選択

通常、Spanner は費用ベースのモデリングを使用して、クエリに対して最も効率的なインデックスを選択します。ただし、FORCE_INDEX ヒントは、特定の検索インデックスを使用するように Spanner に明示的に指示します。たとえば、以下は Spanner に AlbumsIndex を強制的に使用する方法を示しています。

SELECT AlbumId
FROM Albums @{FORCE_INDEX=AlbumsIndex}
WHERE SEARCH(AlbumTitle_Tokens, @p1)

指定した検索インデックスが対象外の場合、他の対象となる検索インデックスがあっても、クエリは失敗します。

検索結果内のスニペット

スニペットとは与えられた文字列から抽出されたテキストで、検索結果の内容と、クエリに関連性がある理由をユーザーに伝えます。

たとえば、Gmail ではスニペットを使用して、検索クエリに一致するメールの部分を示します。

スニペットのリスト

データベースでスニペットを生成することには、いくつかのメリットがあります。

  1. 利便性: 検索クエリからスニペットを生成するロジックを実装する必要はありません。
  2. 効率性: スニペットを使用すると、サーバーからの出力サイズを削減できます。

SNIPPET 関数によりスニペットが作成されます。元の文字列値の関連部分と、ハイライト表示する文字の位置が返されます。クライアントは、エンドユーザーにスニペットを表示する方法(ハイライト表示されたテキストや太字のテキストなど)を選択できます。SNIPPET 関数は、元の文字列からすべての HTML タグを削除します。

たとえば、以下では SNIPPET を使用して AlbumTitle からテキストを取得します。

SELECT AlbumId, SNIPPET(AlbumTitle, "Fast Car")
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, "Fast Car")

次のステップ