TOKENLIST を結合する

このページでは、検索インデックスでスキーマを設定するときに、または Spanner で全文検索を実行するときに検索クエリで TOKENLIST を連結する方法について説明します。

検索インデックス内の TOKENLIST を結合する

アプリケーションで個別のフィールドを検索することが必要な場合もあります。その他の場合には、アプリケーションはすべてのフィールドを検索する必要があります。たとえば、2 つの文字列の列を含むテーブルで、アプリケーションで一致がどの列から取得されたかを区別せずに、両方の列を検索したい場合があります。

Spanner では、これを行う方法が 2 つあります。

  1. 単語を個別にトークン化し、生成された TOKENLIST を連結する(推奨)。
  2. 文字列を連結して結果をトークン化します。

2 つ目の方法には、次の 2 つの問題があります。

  1. Title または Studio を個別にインデックスに登録する場合は、TOKENLIST に結合してインデックスに登録するだけでなく、同じテキストが 2 回トークン化されます。これにより、トランザクションでより多くのリソースが使用されます。
  2. フレーズ検索は両方のフィールドにまたがって行われます。たとえば、@p"Blue Note" に設定されている場合は、Title="Big Blue Note" と Studio="Blue Note Studios" の両方を含む行が照合されます。

最初の方法では、フレーズが 1 つのフィールドにのみ一致し、個別および結合された TOKENLIST の両方がインデックスに登録されている場合に、各文字列フィールドが 1 回のみトークン化されるため、これらの問題は解決されます。各文字列フィールドは 1 回のみトークン化されますが、生成された TOKENLIST はインデックスに個別に保存されます。

単語を個別にトークン化し、TOKENLIST を連結する

次の例では、各単語をトークン化し、TOKENLIST_CONCAT を使用して TOKENLIST を連結しています。

GoogleSQL

CREATE TABLE Albums (
  AlbumId STRING(MAX) NOT NULL,
  Title STRING(MAX),
  Studio STRING(MAX),
  Title_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(Title)) HIDDEN,
  Studio_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(Studio)) HIDDEN,
  Combined_Tokens TOKENLIST AS (TOKENLIST_CONCAT([Title_Tokens, Studio_Tokens])) HIDDEN,
) PRIMARY KEY(AlbumId);

CREATE SEARCH INDEX AlbumsIndex ON Albums(Combined_Tokens);

SELECT AlbumId FROM Albums WHERE SEARCH(Combined_Tokens, @p);

PostgreSQL

PostgreSQL では、連結に spanner.tokenlist_concat が使用されます。クエリ パラメータ $1 は「Hatel Kaliphorn」にバインドされます。

CREATE TABLE albums (
  albumid character varying NOT NULL,
  title character varying,
  studio character varying,
  title_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(title)) VIRTUAL HIDDEN,
  studio_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(studio)) VIRTUAL HIDDEN,
  combined_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenlist_concat(ARRAY[spanner.tokenize_fulltext(title), spanner.tokenize_fulltext(studio)])) VIRTUAL HIDDEN,
PRIMARY KEY(albumid));

CREATE SEARCH INDEX albumsindex ON albums(combined_tokens);

SELECT albumid FROM albums WHERE spanner.search(combined_tokens, $1);

tokenlist_concattitle_tokensstudio_tokens ではなく、spanner.tokenize_fulltext(title)spanner.tokenize_fulltext(studio) を呼び出します。これは、PostgreSQL が、他の生成された列内の生成された列の参照をサポートしていないためです。spanner.tokenlist_concat は、tokenlist 列を直接参照せず、トークン化関数を呼び出す必要があります。

TOKENLIST の連結は、クエリサイドで実装全体を完結することもできます。詳細については、クエリサイドの TOKENLIST の連結をご覧ください。

TOKENLIST_CONCAT は、全文検索と部分文字列検索の両方でサポートされています。Spanner では、同じ TOKENLIST_CONCAT 呼び出しでトークン化タイプ(TOKENIZE_FULLTEXTTOKENIZE_SUBSTRING など)を混在させることはできません。

GoogleSQL では、テキスト TOKENLIST 列の定義は保存されていない列で変更でき、列を追加できます。これは、TOKENLIST_CONCAT に列を追加する場合に有効です。生成された列式を変更しても、インデックス内の既存の行はバックフィルされません。

文字列を連結して結果をトークン化する

次の例では、文字列を連結して結果をトークン化しています。

GoogleSQL

CREATE TABLE Albums (
  AlbumId STRING(MAX) NOT NULL,
  Title STRING(MAX),
  Studio STRING(MAX),
  Combined_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(Title || " " || Studio)) HIDDEN,
) PRIMARY KEY(AlbumId);

CREATE SEARCH INDEX AlbumsIndex ON Albums(Combined_Tokens);

SELECT AlbumId FROM Albums WHERE SEARCH(Combined_Tokens, @p);

PostgreSQL

CREATE TABLE albums (
  albumid character varying NOT NULL,
  title character varying,
  studio character varying,
  combined_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(title || ' ' || studio)) VIRTUAL HIDDEN,
PRIMARY KEY(albumid));

CREATE SEARCH INDEX albumsindex ON albums(combined_tokens);

SELECT albumid FROM albums WHERE spanner.search(combined_tokens, $1);

クエリサイドの TOKENLIST の連結

連結された TOKENLIST にインデックスを付加することのトレードオフとして、ストレージと書き込みのコストが増加します。各トークンは、ディスクに 2 回保存されます。すなわち、元の TOKENLIST の投稿リストに 1 回、結合された TOKENLIST の投稿リストに 1 回保存されます。TOKENLIST 列のクエリサイドの連結ではこの費用を回避できますが、クエリで使用するコンピューティング リソースが増加します。

複数の TOKENLIST を連結するには、SEARCH クエリで TOKENLIST_CONCAT 関数を使用します。このセクションでは、次のサンプル スキーマを使用します。

GoogleSQL

CREATE TABLE Albums (
  AlbumId STRING(MAX) NOT NULL,
  Title STRING(MAX),
  Studio STRING(MAX),
  Title_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(Title)) HIDDEN,
  Studio_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(Studio)) HIDDEN,
) PRIMARY KEY(AlbumId);

CREATE SEARCH INDEX AlbumsIndex ON Albums(Title_Tokens, Studio_Tokens);

PostgreSQL

CREATE TABLE albums (
  albumid character varying NOT NULL,
  title character varying,
  studio character varying,
  title_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(title)) VIRTUAL HIDDEN,
  studio_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(studio)) VIRTUAL HIDDEN,
 PRIMARY KEY(albumid));

CREATE SEARCH INDEX albumsindex ON albums(title_tokens, studio_tokens);

次のクエリは、Title 列と Studio 列の任意の場所にあるトークン「blue」と「note」を含む行を検索します。この検索結果では、Title 列に「blue」と「note」の両方、Studio 列に「blue」と「note」の両方、Title 列に「blue」、Studio 列に「note」、またはその逆が含まれます。

GoogleSQL

SELECT AlbumId
FROM Albums
WHERE SEARCH(TOKENLIST_CONCAT([AlbumTitle_Tokens, Studio_Tokens]), 'blue note')

PostgreSQL

この例では、spanner.tokenlist_concatspanner.search を一緒に使用しています。

SELECT albumid
FROM albums
WHERE spanner.search(spanner.tokenlist_concat(ARRAY[albumtitle_tokens, studio_tokens]), 'blue note')

書き込みサイドとクエリサイドの TOKENLIST の連結は同じ結果になります。2 つのうちどちらを選択するかは、ディスク費用とクエリ費用の間のトレードオフとなります。

または、アプリケーションで複数の TOKENLIST 列を検索し、SEARCH 関数とともに OR を使用することもできます。

GoogleSQL

SEARCH(AlbumTitle_Tokens, 'Blue Note') OR SEARCH(Studio_Tokens, 'Blue Note')

PostgreSQL

spanner.search(albumtitle_tokens, 'Blue Note') OR spanner.search(studio_tokens, 'Blue Note')

ただし、これには異なるセマンティクスが存在します。AlbumTitle_Tokens に「blue」が含まれていて「note」が含まれていない場合、さらに、Studio_Tokens に「note」が含まれていて「blue」が含まれていない場合は、アルバムは一致しません。

次のステップ