クエリの概要

このページでは、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_TokensAlbumStudio_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 値でフィルタされた検索インデックスをご覧ください。

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

  • 検索インデックス検索関数は通常、読み取り専用トランザクションで使用されます。アプリケーションの要件で古い結果が許容される場合は、データ未更新間隔が 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 ではスニペットを使用して、検索クエリに一致するメールの部分を示します。

スニペットのリスト

データベースでスニペットを生成すると、次のようなメリットがあります。

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

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')

次のステップ