[[["わかりやすい","easyToUnderstand","thumb-up"],["問題の解決に役立った","solvedMyProblem","thumb-up"],["その他","otherUp","thumb-up"]],[["わかりにくい","hardToUnderstand","thumb-down"],["情報またはサンプルコードが不正確","incorrectInformationOrSampleCode","thumb-down"],["必要な情報 / サンプルがない","missingTheInformationSamplesINeed","thumb-down"],["翻訳に関する問題","translationIssue","thumb-down"],["その他","otherDown","thumb-down"]],["最終更新日 2025-09-03 UTC。"],[],[],null,["# Combine TOKENLISTs\n\n| **Note:** This feature is available with the Spanner Enterprise edition and Enterprise Plus edition. For more information, see the [Spanner editions overview](/spanner/docs/editions-overview).\n\n\u003cbr /\u003e\n\nThis page describes how to concatenate `TOKENLIST`s in either a\n[search index](/spanner/docs/full-text-search) when you set up your schema\nor in a search query when performing a full-text search in Spanner.\n\nCombine TOKENLISTs in a search index\n------------------------------------\n\nSometimes, you need your application to search across individual fields. At\nother times, the application needs to search across all fields. For example, in\na table with two string columns, you might want your application to search\nacross both columns without differentiating which column the matches come from.\n\nIn Spanner, there are two ways to achieve this:\n\n1. [Tokenize words separately and concatenate the resulting `TOKENLIST`s\n (recommended).](#tokenize-separately)\n2. [Concatenate strings and tokenize the result.](#concatenate-strings)\n\nWith the second approach, there are two problems:\n\n1. If you want to index `Title` or `Studio` individually, in addition to indexing them in a combined `TOKENLIST`, the same text is tokenized twice. This causes transactions to use more resources.\n2. A phrase search spans both fields. For example, if `@p` is set to `\"Blue Note\"`, it matches a row that contains both `Title`=\"Big Blue Note\" and `Studio`=\"Blue Note Studios\".\n\nThe first approach solves these problems because a phrase only matches one field\nand each string field is only tokenized once if both the individual and combined\n`TOKENLIST`s are indexed. Even though each string field is only tokenized once,\nthe resulting `TOKENLIST`s are stored separately in the index.\n\n### Tokenize words separately and concatenate `TOKENLIST`s\n\nThe following example tokenizes each word and uses\n[`TOKENLIST_CONCAT`](/spanner/docs/reference/standard-sql/search_functions#tokenlist_concat)\nto concatenate the `TOKENLIST`s: \n\n### GoogleSQL\n\n CREATE TABLE Albums (\n AlbumId STRING(MAX) NOT NULL,\n Title STRING(MAX),\n Studio STRING(MAX),\n Title_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(Title)) HIDDEN,\n Studio_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(Studio)) HIDDEN,\n Combined_Tokens TOKENLIST AS (TOKENLIST_CONCAT([Title_Tokens, Studio_Tokens])) HIDDEN,\n ) PRIMARY KEY(AlbumId);\n\n CREATE SEARCH INDEX AlbumsIndex ON Albums(Combined_Tokens);\n\n SELECT AlbumId FROM Albums WHERE SEARCH(Combined_Tokens, @p);\n\n### PostgreSQL\n\nPostgreSQL uses\n[`spanner.tokenlist_concat`](/spanner/docs/reference/postgresql/functions-and-operators#search_functions)\nfor concatenation. The query parameter `$1` is bound to 'Hatel Kaliphorn'. \n\n CREATE TABLE albums (\n albumid character varying NOT NULL,\n title character varying,\n studio character varying,\n title_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(title)) VIRTUAL HIDDEN,\n studio_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(studio)) VIRTUAL HIDDEN,\n combined_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenlist_concat(ARRAY[spanner.tokenize_fulltext(title), spanner.tokenize_fulltext(studio)])) VIRTUAL HIDDEN,\n PRIMARY KEY(albumid));\n\n CREATE SEARCH INDEX albumsindex ON albums(combined_tokens);\n\n SELECT albumid FROM albums WHERE spanner.search(combined_tokens, $1);\n\nNote that `tokenlist_concat` doesn't call `title_tokens` or `studio_tokens`,\nbut instead calls `spanner.tokenize_fulltext(title)` and\n`spanner.tokenize_fulltext(studio)`. This is because PostgreSQL\ndoesn't support referencing generated columns that are within other generated\ncolumns. `spanner.tokenlist_concat` needs to call tokenize functions and not\nreference tokenlist columns directly.\n\n`TOKENLIST` concatenation can also be implemented entirely on the query side.\nFor more information, see [Query-side `TOKENLIST`\nconcatenation](#concatenate-tokenlists-query).\n\n`TOKENLIST_CONCAT` is supported for both full-text and\n[substring](/spanner/docs/full-text-search/substring-search) searches.\nSpanner doesn't let you mix tokenization types, such as\n`TOKENIZE_FULLTEXT` and `TOKENIZE_SUBSTRING` in the same `TOKENLIST_CONCAT`\ncall.\n\nIn GoogleSQL, the definition of text `TOKENLIST` columns can be\nchanged in non-stored columns to add additional columns. This is useful when you\nwant to add an additional column to `TOKENLIST_CONCAT`. Changing the generated\ncolumn expression doesn't backfill existing rows in the index.\n\n### Concatenate strings and tokenize the result\n\nThe following example concatenates strings and tokenizes the result: \n\n### GoogleSQL\n\n CREATE TABLE Albums (\n AlbumId STRING(MAX) NOT NULL,\n Title STRING(MAX),\n Studio STRING(MAX),\n Combined_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(Title || \" \" || Studio)) HIDDEN,\n ) PRIMARY KEY(AlbumId);\n\n CREATE SEARCH INDEX AlbumsIndex ON Albums(Combined_Tokens);\n\n SELECT AlbumId FROM Albums WHERE SEARCH(Combined_Tokens, @p);\n\n### PostgreSQL\n\n CREATE TABLE albums (\n albumid character varying NOT NULL,\n title character varying,\n studio character varying,\n combined_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(title || ' ' || studio)) VIRTUAL HIDDEN,\n PRIMARY KEY(albumid));\n\n CREATE SEARCH INDEX albumsindex ON albums(combined_tokens);\n\n SELECT albumid FROM albums WHERE spanner.search(combined_tokens, $1);\n\nQuery-side `TOKENLIST` concatenation\n------------------------------------\n\nThe tradeoff with indexing the concatenated `TOKENLIST` is that it increases\nstorage and write cost. Each token is now stored on the disk twice:\nonce in a posting list of its original `TOKENLIST`, and once in a posting list\nof the combined `TOKENLIST`. Query-side concatenation of `TOKENLIST` columns\navoids this cost but the query uses more compute resources.\n\nTo concatenate multiple `TOKENLIST`s, use the\n[`TOKENLIST_CONCAT`](/spanner/docs/reference/standard-sql/search_functions#tokenlist_concat)\nfunction in the\n[`SEARCH`](/spanner/docs/reference/standard-sql/search_functions#search_fulltext)\nquery. For this section, we're using the following sample schema: \n\n### GoogleSQL\n\n CREATE TABLE Albums (\n AlbumId STRING(MAX) NOT NULL,\n Title STRING(MAX),\n Studio STRING(MAX),\n Title_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(Title)) HIDDEN,\n Studio_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(Studio)) HIDDEN,\n ) PRIMARY KEY(AlbumId);\n\n CREATE SEARCH INDEX AlbumsIndex ON Albums(Title_Tokens, Studio_Tokens);\n\n### PostgreSQL\n\n CREATE TABLE albums (\n albumid character varying NOT NULL,\n title character varying,\n studio character varying,\n title_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(title)) VIRTUAL HIDDEN,\n studio_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(studio)) VIRTUAL HIDDEN,\n PRIMARY KEY(albumid));\n\n CREATE SEARCH INDEX albumsindex ON albums(title_tokens, studio_tokens);\n\nThe following query searches for rows that have the tokens \"blue\"\nand \"note\" anywhere in the `Title` and `Studio` columns. This includes\nrows with both \"blue\" and \"note\" in the `Title` column, \"blue\" and \"note\" in the\n`Studio` column, and \"blue\" in the `Title` column and \"note\" in the `Studio`\ncolumn, or the opposite. \n\n### GoogleSQL\n\n SELECT AlbumId\n FROM Albums\n WHERE SEARCH(TOKENLIST_CONCAT([AlbumTitle_Tokens, Studio_Tokens]), 'blue note')\n\n### PostgreSQL\n\nThis example uses\n[`spanner.search`](/spanner/docs/reference/postgresql/functions-and-operators#search_functions)\nwith\n[`spanner.tokenlist_concat`](/spanner/docs/reference/postgresql/functions-and-operators#search_functions). \n\n SELECT albumid\n FROM albums\n WHERE spanner.search(spanner.tokenlist_concat(ARRAY[albumtitle_tokens, studio_tokens]), 'blue note')\n\nWrite-side and query-side `TOKENLIST` concatenation produce identical results.\nThe choice between the two is a trade-off between disk cost and query cost.\n\nAlternatively, an application could search multiple `TOKENLIST` columns and use\n`OR` along with the `SEARCH` function: \n\n### GoogleSQL\n\n SEARCH(AlbumTitle_Tokens, 'Blue Note') OR SEARCH(Studio_Tokens, 'Blue Note')\n\n### PostgreSQL\n\n spanner.search(albumtitle_tokens, 'Blue Note') OR spanner.search(studio_tokens, 'Blue Note')\n\nThis, however, has different semantics. It doesn't match albums where\n`AlbumTitle_Tokens` has \"blue\", but not \"note\" and `Studio_Tokens` has\n\"note\", but not \"blue\".\n\nWhat's next\n-----------\n\n- Learn about [full-text search queries](/spanner/docs/full-text-search/query-overview).\n- Learn about [search indexes](/spanner/docs/full-text-search/search-indexes)."]]