Encontrar 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 de token exatas usando as funções SEARCH e SEARCH_SUBSTRING, o Spanner também oferece suporte a pesquisas aproximadas (ou imprecisas). As pesquisas vagas encontram documentos correspondentes, apesar de pequenas diferenças entre a consulta e o documento.

O Spanner oferece suporte aos seguintes tipos de pesquisa aproximada:

  • Pesquisa aproximada com base em n-gramas
  • Pesquisa fonética usando Soundex

A pesquisa fuzzy baseada em n-gramas depende da mesma tokenização de substring que uma pesquisa de substring precisa. A configuração do tokenizer é importante porque afeta a qualidade e o desempenho da pesquisa. O exemplo a seguir mostra como criar uma consulta com palavras com erros de ortografia ou grafia 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 consulta a seguir 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

Este exemplo usa 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

Otimizar a performance e a recuperação de uma pesquisa aproximada baseada em n-gramas

A consulta de exemplo na seção anterior faz pesquisas em duas fases, usando duas funções diferentes:

  1. SEARCH_NGRAMS encontra todos os álbuns candidatos que têm n-gramas compartilhados com a consulta de pesquisa. Por exemplo, os n-gramas de três caracteres 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 compartilhados nesses conjuntos de dados são [ali, orn]. Por padrão, SEARCH_NGRAMS corresponde a todos os documentos com pelo menos dois n-gramas compartilhados. Portanto, "Kaliphorn" corresponde a "California".
  2. SCORE_NGRAMS classifica as correspondências por semelhança. A semelhança de duas strings é definida como uma proporção de n-gramas compartilhados distintos para n-gramas não compartilhados 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 maneira recomendada de fazer isso é usar o argumento com parâmetros de consulta em vez de literais de string 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 para 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 caractere porque eles podem corresponder a um número muito grande de documentos. Por outro lado, n-gramas longos fazem com que SEARCH_NGRAMS perca palavras curtas com erros ortográficos.
  • O número mínimo de n-gramas que SEARCH_NGRAMS precisa corresponder (definido com os argumentos min_ngrams e min_ngrams_percent em SEARCH_NGRAMS). Números maiores geralmente tornam a consulta mais rápida, mas reduzem a recuperação.

Para alcançar um bom equilíbrio entre desempenho e recuperação, é possível configurar esses argumentos para se ajustar à consulta e à carga de trabalho específica.

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

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á vinculado 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 com base em N-gramas versus modo de consulta aprimorado

Além da pesquisa aproximada baseada em n-gramas, o modo de consulta aprimorado também processa algumas palavras com erros ortográficos. Portanto, há uma coincidência entre os dois recursos. A tabela a seguir resume as diferenças:

Pesquisa imprecisa com n-gramas Modo de consulta aprimorado
Custo Requer uma tokenização de substring mais cara com base em n-grams Requer uma tokenização de texto completo mais barata
Tipos de consulta de pesquisa Funciona bem com documentos curtos com poucas palavras, como o nome de uma pessoa, de uma cidade ou de um produto Funciona igualmente bem com documentos e consultas de pesquisa de qualquer tamanho
Pesquisa de palavras parciais Realiza uma pesquisa de substring que permite erros ortográficos Suporte apenas a uma pesquisa de palavras inteiras (SEARCH_SUBSTRING não oferece suporte ao argumento enhance_query)
Palavras com erros de ortografia Suporta palavras com erros de ortografia no índice ou na consulta Suporte apenas para palavras com erros ortográficos na consulta
Correções Encontra correspondências com erros ortográficos, mesmo que não sejam palavras reais Corrige erros ortográficos de palavras comuns e conhecidas

Realizar uma pesquisa fonética com o Soundex

O Spanner fornece a função SOUNDEX para encontrar palavras com grafias diferentes, mas que soam da mesma forma. Por exemplo, SOUNDEX("steven"), SOUNDEX("stephen") e SOUNDEX("stefan") são todos "s315", enquanto SOUNDEX("stella") é "s340". SOUNDEX diferencia maiúsculas de minúsculas e só funciona para alfabetos baseados em latim.

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

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 consulta a seguir corresponde a "stefan" com "Steven" em SOUNDEX, com AlbumTitle contendo "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')

A seguir