このページでは、Spanner テーブルに対する全文検索クエリの実行に使用される SEARCH
関数と拡張クエリモードについて説明します。
検索インデックスをクエリする
Spanner には、検索インデックス クエリに使用する SEARCH
関数が用意されています。ユースケースの例としては、ユーザーが検索ボックスにテキストを入力し、アプリケーションがユーザー入力を SEARCH
関数に直接送信するアプリケーションが該当します。SEARCH
関数は検索インデックスを使用して対象のテキストを検索します。
SEARCH
関数には次の 2 つの引数が必要です。
- 検索インデックス名
- 検索語句
SEARCH
関数は、検索インデックスが定義されている場合にのみ機能します。SEARCH
関数は、任意の SQL 構文(フィルタ、集計、結合など)と組み合わせることができます。
SEARCH
関数は、トランザクション クエリでは使用できません。
次のクエリは、SEARCH
関数を使用して、タイトルに friday
または monday
を含むすべてのアルバムを返します。
GoogleSQL
SELECT AlbumId
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, 'friday OR monday')
PostgreSQL
この例では spanner.search を使用しています。
SELECT albumid
FROM albums
WHERE spanner.search(albumtitle_tokens, 'friday OR monday')
検索クエリ
検索クエリでは、デフォルトで元の検索クエリの構文が使用されます。別の構文は、SEARCH
dialect
引数を使用して指定できます。
rquery 言語
デフォルトの言語は元の検索クエリです。Spanner は、rquery というドメイン固有の言語(DSL)を使用します。
rquery 言語は、入力検索クエリを個別の語句に分割するときに、プレーンテキスト トークナイザーと同じルールに従います。これには、アジアの言語のセグメント化も含まれます。
rquery の使用については、rquery の構文をご覧ください。
words 言語
words 言語は rquery に似ていますが、よりシンプルです。特殊演算子は使用しません。たとえば、OR
は除外演算子ではなく検索語句として扱われます。二重引用符はフレーズ検索ではなく句読点として扱われ、無視されます。
words 言語の場合、AND
はすべての用語に暗黙的に適用され、照合時に必要になります。入力検索クエリを語句に分割するときに、プレーンテキスト トークナイザーと同じルールに従います。
words 言語の使用方法については、words の構文をご覧ください。
words_phrase 言語
words_phrase 言語では特別な演算子は使用されず、すべての用語がフレーズとして扱われます。つまり、用語は隣接して指定された順序で指定する必要があります。
rquery と同様に、words_phrase 言語は、入力検索クエリを語句に分割するときに、プレーンテキスト トークナイザーと同じルールに従います。
words_phrase 言語の使用方法については、words フレーズ構文をご覧ください。
拡張クエリモード
Spanner には、基本的なトークンベースの検索と、enhance_query
というより高度なモードの 2 つの全文検索モードがあります。有効にすると、enhance_query
は関連する語句と類義語が含まれるように検索クエリを拡張するため、関連性の高い結果を見つけられる可能性が高まります。
このオプションを有効にするには、SEARCH
関数でオプションの引数 enhance_query=>true
を設定します。たとえば、検索クエリ hotl cal
はアルバム Hotel California
と一致します。
GoogleSQL
SELECT AlbumId
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, 'hotl cal', enhance_query=>true)
PostgreSQL
SELECT albumid
FROM albums
WHERE spanner.search(albumtitle_tokens, 'hotl cal', enhance_query=>true)
enhance_query
モードはクエリ時オプションです。トークン化には影響しません。enhance_query
の有無にかかわらず、同じ検索インデックスを使用できます。
Google は、クエリ拡張アルゴリズムの改善に継続的に取り組んでいます。そのため、enhance_query == true
を含むクエリでは、時間の経過とともに結果が若干異なる場合があります。
enhance_query
モードを有効にすると、SEARCH
関数が検索する語句の数が増加し、レイテンシが若干増加する可能性があります。
たとえば、次のクエリではタイムアウトが 3 秒発生します。enhance_query
が使用できない場合、クエリは失敗します。
GoogleSQL
@{require_enhance_query=true, enhance_query_timeout_ms=3000}
SELECT AlbumId
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, 'fast car', enhance_query=>true)
PostgreSQL
/*@require_enhance_query=true, enhance_query_timeout_ms=3000*/
SELECT albumid
FROM albums
WHERE spanner.search(albumtitle_tokens, 'fast car', 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
列は、同じ検索インデックスに登録されている必要があります。たとえば、次のテーブルとインデックスの定義について考えてみましょう。
GoogleSQL
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);
PostgreSQL
CREATE TABLE albums ( albumid character varying NOT NULL, albumtitle character varying, albumstudio character varying, albumtitle_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(albumtitle)) VIRTUAL HIDDEN, albumstudio_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(albumstudio)) VIRTUAL HIDDEN, PRIMARY KEY(albumid)); CREATE SEARCH INDEX albumstitleindex ON albums(albumtitle_tokens); CREATE SEARCH INDEX albumsstudioindex ON albums(albumstudio_tokens);
次のクエリは、
AlbumTitle_Tokens
とAlbumStudio_Tokens
の両方をインデックスに登録する単一の検索インデックスがないため、失敗します。GoogleSQL
SELECT AlbumId FROM Albums WHERE SEARCH(AlbumTitle_Tokens, @p1) AND SEARCH(AlbumStudio_Tokens, @p2)
PostgreSQL
この例では、クエリ パラメータ
$1
と$2
を使用し、それぞれ「fast car」と「blue note」にバインドしています。SELECT albumid FROM albums WHERE spanner.search(albumtitle_tokens, $1) AND spanner.search(albumstudio_tokens, $2)
並べ替え順列が NULL を許容している場合、スキーマとクエリの両方で、並べ替え順列が NULL である行を除外する必要があります。詳細については、検索インデックスの並べ替え順序をご覧ください。
検索インデックスが NULL でフィルタされている場合、クエリにはインデックスで使用されている NULL フィルタ式を含める必要があります。詳細については、NULL 値でフィルタされた検索インデックスをご覧ください。
検索インデックスと検索関数は通常、読み取り専用トランザクションで使用されます。アプリケーションの要件で古い結果が許容される場合は、データ未更新間隔が 10 秒以上の検索クエリを実行することでレイテンシを改善できる場合があります。詳細については、古いデータの読み取りをご覧ください。これは、多くのインデックス分割にファンアウトする検索クエリに特に便利です。
検索インデックスと検索関数は、読み取り / 書き込みトランザクションでは推奨されません。実行中に、検索クエリはインデックス パーティション全体をロックします。その結果、読み取り / 書き込みトランザクションで検索クエリの頻度が高くなると、ロック競合が発生し、レイテンシが急増する可能性があります。デフォルトでは、読み取り / 書き込みトランザクションで検索インデックスは自動的に選択されません。読み取り / 書き込みトランザクションでクエリが検索インデックスを使用するように強制された場合、トランザクションはデフォルトで失敗します。また、クエリに検索関数が含まれている場合も失敗します。この動作は、GoogleSQL @{ALLOW_SEARCH_INDEXES_IN_TRANSACTION=TRUE}
ステートメントレベルのヒントでオーバーライドできます(ただし、クエリでロック競合が発生する可能性が高くなります)。
インデックスの使用条件が満たされると、クエリ オプティマイザーはテキスト以外のクエリ条件(Rating > 4
など)を高速化しようとします。検索インデックスに適切な TOKENLIST
列が含まれていない場合、条件は高速化されず、残存条件のままになります。
クエリ パラメータ
検索クエリ引数は、リテラルまたはクエリ パラメータとして指定します。引数でクエリ パラメータ値が許可されている場合は、全文検索に文字列リテラルではなく、クエリ パラメータを使用することをおすすめします。
インデックスの選択
通常、Spanner は費用ベースのモデリングを使用して、クエリに最も効率的なインデックスを選択します。ただし、FORCE_INDEX
ヒントは、特定の検索インデックスを使用するように Spanner に明示的に指示します。たとえば、次のコードは、Spanner に AlbumsIndex
を使用するように強制します。
GoogleSQL
SELECT AlbumId
FROM Albums @{FORCE_INDEX=AlbumsIndex}
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")
PostgreSQL
SELECT albumid
FROM albums/*@force_index=albumsindex*/
WHERE spanner.search(albumtitle_tokens, 'fifth symphony')
指定された検索インデックスが対象外の場合、他の対象となる検索インデックスがあっても、クエリは失敗します。
検索結果内のスニペット
スニペットとは与えられた文字列から抽出されたテキストで、検索結果の内容と、クエリに関連性がある理由をユーザーに伝えます。
たとえば、Gmail ではスニペットを使用して、検索クエリに一致するメールの部分を示します。
データベースでスニペットを生成すると、次のようなメリットがあります。
- 利便性: 検索クエリからスニペットを生成するロジックを実装する必要はありません。
- 効率性: スニペットを使用すると、サーバーからの出力サイズを削減できます。
SNIPPET
関数はスニペットを作成します。元の文字列値の関連部分と、ハイライト表示する文字の位置が返されます。クライアントは、スニペットをエンドユーザーに表示する方法(ハイライト表示や太字のテキストなど)を選択できます。
たとえば、次の例では SNIPPET
を使用して AlbumTitle
からテキストを取得します。
GoogleSQL
SELECT AlbumId, SNIPPET(AlbumTitle, "Fast Car")
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, "Fast Car")
PostgreSQL
この例では spanner.snippet を使用しています。
SELECT albumid, spanner.snippet(albumtitle, 'Fast Car')
FROM albums
WHERE spanner.search(albumtitle_tokens, 'Fast Car')
次のステップ
- 検索結果をランク付けする方法を確認する。
- 部分文字列検索を行う方法を確認する。
- 検索結果をページ分けする方法を確認する。
- 全文クエリと非テキストクエリを組み合わせる方法を確認する。
- 複数の列を検索する方法を確認する。