Encontre correspondências aproximadas com a pesquisa aproximada

Esta página descreve como usar uma pesquisa aproximada como parte de uma pesquisa de texto completo.

Além de realizar pesquisas exatas de tokens com as funções SEARCH e SEARCH_SUBSTRING, o Spanner também suporta pesquisas aproximadas (ou difusas). As pesquisas aproximadas encontram documentos correspondentes apesar das pequenas diferenças entre a consulta e o documento.

O Spanner suporta os seguintes tipos de pesquisa aproximada:

  • Pesquisa aproximada baseada em n-gramas
  • Pesquisa fonética com o algoritmo Soundex

A pesquisa aproximada baseada em n-gramas baseia-se na mesma tokenização de subcadeias que uma pesquisa de subcadeias requer. A configuração do tokenizador é importante, pois afeta a qualidade e o desempenho da pesquisa. O exemplo seguinte mostra como criar uma consulta com palavras com erros ortográficos ou escritas de forma diferente para encontrar correspondências aproximadas no índice de pesquisa.

Esquema

GoogleSQL

CREATE TABLE Albums (
  AlbumId STRING(MAX) NOT NULL,
  AlbumTitle STRING(MAX),
  AlbumTitle_Tokens TOKENLIST AS (
    TOKENIZE_SUBSTRING(AlbumTitle, ngram_size_min=>2, ngram_size_max=>3,
                      relative_search_types=>["word_prefix", "word_suffix"])) HIDDEN
) PRIMARY KEY(AlbumId);

CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens)
STORING (AlbumTitle);

PostgreSQL

Este exemplo usa spanner.tokenize_substring.

CREATE TABLE albums (
  albumid character varying NOT NULL,
  albumtitle character varying,
  albumtitle_tokens spanner.tokenlist GENERATED ALWAYS AS (
    spanner.tokenize_substring(albumtitle, ngram_size_min=>2, ngram_size_max=>3,
                      relative_search_types=>'{word_prefix, word_suffix}'::text[])) VIRTUAL HIDDEN,
PRIMARY KEY(albumid));

CREATE SEARCH INDEX albumsindex
ON albums(albumtitle_tokens)
INCLUDE (albumtitle);

Consulta

A seguinte consulta encontra os álbuns com títulos mais próximos de "Hatel Kaliphorn", como "Hotel California".

GoogleSQL

SELECT AlbumId
FROM Albums
WHERE SEARCH_NGRAMS(AlbumTitle_Tokens, "Hatel Kaliphorn")
ORDER BY SCORE_NGRAMS(AlbumTitle_Tokens, "Hatel Kaliphorn") DESC
LIMIT 10

PostgreSQL

Estes exemplos usam spanner.score_ngrams e spanner.search_ngrams.

SELECT albumid
FROM albums
WHERE spanner.search_ngrams(albumtitle_tokens, 'Hatel Kaliphorn')
ORDER BY spanner.score_ngrams(albumtitle_tokens, 'Hatel Kaliphorn') DESC
LIMIT 10

Otimize o desempenho e a capacidade de memorização para uma pesquisa aproximada baseada em n-gramas

A consulta de exemplo na secção anterior pesquisa em duas fases, usando duas funções diferentes:

  1. SEARCH_NGRAMS encontra todos os álbuns candidatos que têm n-gramas partilhados com a consulta de pesquisa. Por exemplo, os n-gramas de três carateres para "California" incluem [cal, ali, lif, ifo, for, orn, rni, nia] e para "Kaliphorn" incluem [kal, ali, lip, iph, pho, hor, orn]. Os n-gramas partilhados nestes conjuntos de dados são [ali, orn]. Por predefinição, SEARCH_NGRAMS corresponde a todos os documentos com, pelo menos, dois n-gramas partilhados. Por isso, "Kaliphorn" corresponde a "California".
  2. SCORE_NGRAMS classifica as correspondências por semelhança. A semelhança de duas strings é definida como uma relação de n-gramas partilhados distintos com n-gramas não partilhados distintos:
$$ \frac{shared\_ngrams}{total\_ngrams_{index} + total\_ngrams_{query} - shared\_ngrams} $$

Normalmente, a consulta de pesquisa é a mesma nas funções SEARCH_NGRAMS e SCORE_NGRAMS. A forma recomendada de o fazer é usar o argumento com parâmetros de consulta em vez de literais de strings e especificar o mesmo parâmetro de consulta nas funções SEARCH_NGRAMS e SCORE_NGRAMS.

O Spanner tem três argumentos de configuração que podem ser usados com SEARCH_NGRAMS:

  • Os tamanhos mínimo e máximo dos n-gramas são especificados com as funções TOKENIZE_SUBSTRING(/spanner/docs/reference/standard-sql/search_functions#tokenize_substring) ou TOKENIZE_NGRAMS. Não recomendamos n-gramas de um carater, porque podem corresponder a um número muito elevado de documentos. Por outro lado, os n-gramas longos fazem com que o SEARCH_NGRAMS não detete palavras curtas com erros ortográficos.
  • O número mínimo de n-gramas que SEARCH_NGRAMS tem de corresponder (definido com os argumentos min_ngrams e min_ngrams_percent em SEARCH_NGRAMS). Os números mais elevados normalmente tornam a consulta mais rápida, mas reduzem a evocação.

Para alcançar um bom equilíbrio entre o desempenho e a capacidade de memorização, pode configurar estes argumentos para se adequarem à consulta e à carga de trabalho específicas.

Também recomendamos a inclusão de um LIMIT interno para evitar a criação de consultas muito dispendiosas quando é encontrada uma combinação de n-gramas populares.

GoogleSQL

SELECT AlbumId
FROM (
  SELECT AlbumId,
        SCORE_NGRAMS(AlbumTitle_Tokens, @p) AS score
  FROM Albums
  WHERE SEARCH_NGRAMS(AlbumTitle_Tokens, @p)
  LIMIT 10000  # inner limit
)
ORDER BY score DESC
LIMIT 10  # outer limit

PostgreSQL

Este exemplo usa o parâmetro de consulta $1, que está associado a "Hatel Kaliphorn".

SELECT albumid
FROM
  (
    SELECT albumid, spanner.score_ngrams(albumtitle_tokens, $1) AS score
    FROM albums
    WHERE spanner.search_ngrams(albumtitle_tokens, $1)
    LIMIT 10000
  ) AS inner_query
ORDER BY inner_query.score DESC
LIMIT 10

Pesquisa aproximada baseada em n-gramas versus modo de consulta melhorado

Além da pesquisa aproximada baseada em n-gramas, o modo de consulta melhorado também processa algumas palavras com erros ortográficos. Assim, existe alguma sobreposição entre as duas funcionalidades. A tabela seguinte resume as diferenças:

Pesquisa aproximada baseada em n-gramas Modo de consulta melhorado
Custo Requer uma tokenização de subcadeias mais cara com base em n-gramas Requer uma tokenização de texto completo menos dispendiosa
Tipos de consultas de pesquisa Funciona bem com documentos curtos com algumas palavras, como o nome de uma pessoa, o nome de uma cidade ou o nome de um produto Funciona igualmente bem com documentos de qualquer tamanho e consultas de pesquisa de qualquer tamanho
Pesquisa de palavras parciais Executa uma pesquisa de subcadeias que permite erros ortográficos Só suporta uma pesquisa de palavras inteiras (SEARCH_SUBSTRING não suporta o argumento enhance_query)
Palavras com erros ortográficos Suporta palavras com erros ortográficos no índice ou na consulta Apenas suporta palavras com erros ortográficos na consulta
Correções Encontra quaisquer correspondências com erros ortográficos, mesmo que a correspondência não seja uma palavra real Corrige erros ortográficos de palavras comuns e conhecidas

Faça uma pesquisa fonética com o Soundex

O Spanner fornece a função SOUNDEX para encontrar palavras que são escritas de forma diferente, mas que têm o mesmo som. Por exemplo, SOUNDEX("steven"), SOUNDEX("stephen") e SOUNDEX("stefan") são todos "s315", enquanto SOUNDEX("stella") é "s340". SOUNDEX é sensível a maiúsculas e minúsculas e só funciona para alfabetos baseados no latim.

A pesquisa fonética com SOUNDEX pode ser implementada com uma coluna gerada e um índice de pesquisa, conforme mostrado no exemplo seguinte:

GoogleSQL

CREATE TABLE Singers (
  SingerId INT64,
  AlbumTitle STRING(MAX),
  AlbumTitle_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(AlbumTitle)) HIDDEN,
  Name STRING(MAX),
  NameSoundex STRING(MAX) AS (LOWER(SOUNDEX(Name))),
  NameSoundex_Tokens TOKENLIST AS (TOKEN(NameSoundex)) HIDDEN
) PRIMARY KEY(SingerId);

CREATE SEARCH INDEX SingersPhoneticIndex ON Singers(AlbumTitle_Tokens, NameSoundex_Tokens);

PostgreSQL

Este exemplo usa spanner.soundex.

CREATE TABLE singers (
  singerid bigint,
  albumtitle character varying,
  albumtitle_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(albumtitle)) VIRTUAL HIDDEN,
  name character varying,
  namesoundex character varying GENERATED ALWAYS AS (lower(spanner.soundex(name))) VIRTUAL,
  namesoundex_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.token(lower(spanner.soundex(name))) VIRTUAL HIDDEN,
PRIMARY KEY(singerid));

CREATE SEARCH INDEX singersphoneticindex ON singers(albumtitle_tokens, namesoundex_tokens);

A seguinte consulta faz corresponder "stefan" a "Steven" em SOUNDEX, juntamente com AlbumTitle que contenha "cat":

GoogleSQL

SELECT SingerId
FROM Singers
WHERE NameSoundex = LOWER(SOUNDEX("stefan")) AND SEARCH(AlbumTitle_Tokens, "cat")

PostgreSQL

SELECT singerid
FROM singers
WHERE namesoundex = lower(spanner.soundex('stefan')) AND spanner.search(albumtitle_tokens, 'cat')

O que se segue?