合并 TOKENLIST

本页面介绍了如何在设置架构时在搜索索引中或在 Spanner 中执行全文搜索时在搜索查询中串联 TOKENLIST

在搜索索引中合并 TOKENLIST

有时,您需要应用在个别字段中进行搜索。而在其他时候,应用需要在所有字段中进行搜索。例如,在包含两个字符串列的表中,您可能希望应用在两个列中进行搜索,而不区分匹配项来自哪个列。

在 Spanner 中,您可以通过以下两种方式实现此目的:

  1. 单独对字词进行词元化并串联生成的 TOKENLIST(推荐)。
  2. 串联字符串并对结果进行词元化。

第二种方法存在两个问题:

  1. 如果您想单独将 TitleStudio 编入索引,则除了在合并的 TOKENLIST 中将它们编入索引之外,相同的文本会被词元化两次。这会导致事务使用更多资源。
  2. 词组搜索会同时搜索这两个字段。例如,如果 @p 设置为 "Blue Note",则它会匹配同时包含 Title="Big Blue Note" 和 Studio="Blue Note Studios" 的行。

第一种方法可以解决这些问题,因为词组仅匹配一个字段,并且如果将单个合并的 TOKENLIST 编入索引,则每个字符串字段仅会被词元化一次。虽然每个字符串字段仅会被词元化一次,但生成的 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_concat 不会调用 title_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 编入索引的缺点是会增加存储和写入费用。每个词元现在都存储在磁盘上两次:一次存储在其原始 TOKENLIST 的发布列表中,另一次存储在合并的 TOKENLIST 的发布列表中。在查询端串联 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);

以下查询会在 TitleStudio 列中的任意位置搜索包含“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.searchspanner.tokenlist_concat 搭配使用。

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

写入端和查询端 TOKENLIST 串联会生成相同的结果。选择哪种方式需要在磁盘费用和查询费用之间进行权衡。

或者,应用可以搜索多个 TOKENLIST 列,并结合使用 ORSEARCH 函数:

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”。

后续步骤