Cómo combinar TOKENLISTs

En esta página, se describe cómo concatenar TOKENLISTs en un índice de búsqueda cuando configuras tu esquema o en una consulta de búsqueda cuando realizas una búsqueda de texto completo en Spanner.

Combina TOKENLISTs en un índice de búsqueda

A veces, necesitas que tu aplicación busque en campos individuales. En otras ocasiones, la aplicación necesita buscar en todos los campos. Por ejemplo, en una tabla con dos columnas de cadena, es posible que desees que tu aplicación busque en ambas columnas sin diferenciar de qué columna provienen las coincidencias.

En Spanner, hay dos formas de lograr esto:

  1. Tokeniza palabras por separado y concatena las TOKENLISTs resultantes (recomendado).
  2. Concatena cadenas y tokeniza el resultado.

Con el segundo enfoque, hay dos problemas:

  1. Si deseas indexar Title o Studio de forma individual, además de indexarlos en una TOKENLIST combinada, el mismo texto se tokeniza dos veces. Esto hace que las transacciones usen más recursos.
  2. Una búsqueda de frases abarca ambos campos. Por ejemplo, si @p se establece en "Blue Note", coincide con una fila que contiene Title="Big Blue Note" y Studio="Blue Note Studios".

El primer enfoque resuelve estos problemas porque una frase solo coincide con un campo y cada campo de cadena solo se tokeniza una vez si se indexan las TOKENLISTs individuales y combinadas. Aunque cada campo de cadena solo se tokeniza una vez, las TOKENLISTs resultantes se almacenan por separado en el índice.

Tokeniza palabras por separado y concatena TOKENLISTs

En el siguiente ejemplo, se tokeniza cada palabra y se usa TOKENLIST_CONCAT para concatenar las TOKENLISTs:

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 uses spanner.tokenlist_concat para la concatenación. El parámetro de consulta $1 está vinculado a "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);

Ten en cuenta que tokenlist_concat no llama a title_tokens ni a studio_tokens, sino que llama a spanner.tokenize_fulltext(title) y spanner.tokenize_fulltext(studio). Esto se debe a que PostgreSQL no admite hacer referencia a columnas generadas que se encuentran dentro de otras columnas generadas. spanner.tokenlist_concat necesita llamar a las funciones de tokenización y no hacer referencia a las columnas de la lista de tokens directamente.

La concatenación de TOKENLIST también se puede implementar por completo en el lado de la consulta. Para obtener más información, consulta Concatenación TOKENLIST del lado de la consulta.

TOKENLIST_CONCAT es compatible con las búsquedas de texto completo y de subcadenas searches. Spanner no te permite combinar tipos de tokenización, como TOKENIZE_FULLTEXT y TOKENIZE_SUBSTRING en la misma llamada TOKENLIST_CONCAT.

En GoogleSQL, la definición de las columnas de texto TOKENLIST se puede cambiar en columnas no almacenadas para agregar columnas adicionales. Esto es útil cuando deseas agregar una columna adicional a TOKENLIST_CONCAT. Si cambias la expresión de la columna generada, no se propagan las filas existentes en el índice.

Concatena cadenas y tokeniza el resultado

En el siguiente ejemplo, se concatenan cadenas y se tokeniza el resultado:

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

Concatenación de TOKENLIST del lado de la consulta

La desventaja de indexar la TOKENLIST concatenada es que aumenta el costo de almacenamiento y escritura. Cada token ahora se almacena en el disco dos veces: una vez en una lista de publicación de su TOKENLIST original y otra vez en una lista de publicación de la TOKENLIST combinada. La concatenación del lado de la consulta de las columnas TOKENLIST evita este costo, pero la consulta usa más recursos de procesamiento.

Para concatenar varias TOKENLISTs, usa la TOKENLIST_CONCAT función en la SEARCH consulta. Para esta sección, usaremos el siguiente esquema de muestra:

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

La siguiente consulta busca filas que tengan los tokens "blue" y "note" en cualquier lugar de las columnas Title y Studio. Esto incluye filas con "blue" y "note" en la columna Title, "blue" y "note" en la columna Studio, y "blue" en la columna Title y "note" en la columna Studio, o lo contrario.

GoogleSQL

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

PostgreSQL

En este ejemplo, se usa spanner.search con spanner.tokenlist_concat.

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

La concatenación de TOKENLIST del lado de la escritura y de la consulta produce resultados idénticos. La elección entre los dos es una compensación entre el costo del disco y el costo de la consulta.

Como alternativa, una aplicación podría buscar varias columnas TOKENLIST y usar OR junto con la función SEARCH:

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

Sin embargo, esto tiene una semántica diferente. No coincide con los álbumes en los que AlbumTitle_Tokens tiene "blue", pero no "note", y Studio_Tokens tiene "note", pero no "blue".

¿Qué sigue?