Classificar resultados da pesquisa

Nesta página, descrevemos como classificar os resultados de pesquisas de texto completo no Spanner.

O Spanner oferece suporte ao cálculo de uma pontuação de topicalidade, que fornece um elemento básico para a criação de funções de classificação sofisticadas. Essas pontuações calculam a relevância de um resultado para uma consulta com base na frequência do termo de consulta e em outras opções personalizáveis.

O exemplo a seguir mostra como realizar uma pesquisa classificada usando a SCORE função:

GoogleSQL

SELECT AlbumId
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")
ORDER BY SCORE(AlbumTitle_Tokens, "fifth symphony") DESC

PostgreSQL

Este exemplo usa spanner.search com spanner.score.

SELECT albumid
FROM albums
WHERE spanner.search(albumtitle_tokens, 'fifth symphony')
ORDER BY spanner.score(albumtitle_tokens, 'fifth symphony') DESC

Pontuar termos de consulta com a função SCORE

A SCORE função calcula uma pontuação para cada termo de consulta e combina as pontuações. A pontuação por termo é baseada na frequência do termo – frequência inversa do documento (TF/IDF, na sigla em inglês). A pontuação é um componente da ordenação final de um registro. A consulta a combina com outros indicadores, como a atualização que modula a pontuação de topicalidade.

Na implementação atual, a parte IDF do TF/IDF só está disponível quando enhance_query=>true é usada. Ela calcula a frequência relativa das palavras com base no corpus da Web completo usado pela Pesquisa Google, em vez de um índice de pesquisa específico. Se a melhoria da consulta não estiver ativada, a pontuação só usará o componente de frequência do termo (TF), ou seja, o termo IDF será definido como 1.

A função SCORE retorna valores que servem como pontuações de relevância que o Spanner usa para estabelecer uma ordem de classificação. Eles não têm significado independente. Quanto maior a pontuação, melhor a correspondência com a consulta.

Geralmente, argumentos como query e enhance_query são os mesmos nas funções SEARCH e SCORE para garantir a consistência na recuperação e na classificação.

A maneira recomendada de fazer isso é usar esses argumentos com parâmetros de consulta em vez de literais de string e especificar os mesmos parâmetros de consulta nas funções SEARCH e SCORE.

Pontuar várias colunas

O Spanner usa a função SCORE para pontuar cada campo individualmente. A consulta combina essas pontuações individuais. Uma maneira comum de fazer isso é somar as pontuações individuais e, em seguida, aumentá-las de acordo com as ponderações de campo fornecidas pelo usuário (que são fornecidas usando parâmetros de consulta SQL).

Por exemplo, a consulta a seguir combina a saída de duas funções SCORE:

GoogleSQL

SELECT AlbumId
FROM Albums
WHERE SEARCH(Title_Tokens, @p1) OR SEARCH(Studio_Tokens, @p2)
ORDER BY SCORE(Title_Tokens, @p1) * @titleweight + SCORE(Studio_Tokens, @p2) * @studioweight
LIMIT 25

PostgreSQL

Este exemplo usa os parâmetros de consulta $1 e $2, que estão vinculados a "quinta sinfonia" e "blue note", respectivamente.

SELECT albumid
FROM albums
WHERE spanner.search(title_tokens, $1) OR spanner.search(studio_tokens, $2)
ORDER BY spanner.score(title_tokens, $1) * $titleweight
        + spanner.score(studio_tokens, $2) * $studioweight
LIMIT 25

O exemplo a seguir adiciona dois parâmetros de otimização:

  • A atualização (FreshnessBoost) aumenta a pontuação com (1 + @freshnessweight * GREATEST(0, 30 - DaysOld) / 30)
  • A popularidade(PopularityBoost) aumenta a pontuação multiplicando-a pelo fator (1 + IF(HasGrammy, @grammyweight, 0).

Para facilitar a leitura, a consulta usa o operador WITH.

GoogleSQL

SELECT AlbumId
FROM Albums
WHERE SEARCH(Title_Tokens, @p1) OR SEARCH(Studio_Tokens, @p2)
ORDER BY WITH(
  TitleScore AS SCORE(Title_Tokens, @p1) * @titleweight,
  StudioScore AS SCORE(Studio_Tokens, @p2) * @studioweight,
  DaysOld AS (UNIX_MICROS(CURRENT_TIMESTAMP()) - ReleaseTimestamp) / 8.64e+10,
  FreshnessBoost AS (1 + @freshnessweight * GREATEST(0, 30 - DaysOld) / 30),
  PopularityBoost AS (1 + IF(HasGrammy, @grammyweight, 0)),
  (TitleScore + StudioScore) * FreshnessBoost * PopularityBoost)
LIMIT 25

PostgreSQL

Este exemplo usa os parâmetros de consulta $1, $2, $3, $4, $5 e $6, que estão vinculados aos valores especificados para titlequery, studioquery, titleweight, studioweight, grammyweight e freshnessweight, respectivamente.

SELECT albumid
FROM
  (
    SELECT
      albumid,
      spanner.score(title_tokens, $1) * $3 AS titlescore,
      spanner.score(studio_tokens, $2) * $4 AS studioscore,
      (extract(epoch FROM current_timestamp) * 10e+6 - releasetimestamp) / 8.64e+10 AS daysold,
      (1 + CASE WHEN hasgrammy THEN $5 ELSE 0 END) AS popularityboost
    FROM albums
    WHERE spanner.search(title_tokens, $1) OR spanner.search(studio_tokens, $2)
  ) AS subquery
ORDER BY (subquery.TitleScore + subquery.studioscore)
  * (1 + $6 * greatest(0, 30 - subquery.daysold) / 30) * subquery.popularityboost
LIMIT 25

TOKENLIST_CONCAT também pode ser usado na pesquisa e na pontuação para simplificar as consultas quando apropriado.

GoogleSQL

SELECT AlbumId
FROM Albums
WHERE SEARCH(TOKENLIST_CONCAT([Title_Tokens, Studio_Tokens]), @p)
ORDER BY SCORE(TOKENLIST_CONCAT([Title_Tokens, Studio_Tokens]), @p)
LIMIT 25

PostgreSQL

Este exemplo usa spanner.tokenlist_concat. O parâmetro de consulta $1 está vinculado a "blue note".

SELECT albumid
FROM albums
WHERE spanner.search(spanner.tokenlist_concat(ARRAY[title_tokens, studio_tokens]), $1)
ORDER BY spanner.score(spanner.tokenlist_concat(ARRAY[title_tokens, studio_tokens]), $1)
LIMIT 25

Otimizar correspondências de ordem de consulta

O Spanner aplica uma otimização multiplicativa à saída da função SCORE para valores que contêm os termos de consulta na mesma ordem em que aparecem na consulta. Há duas versões dessa otimização: correspondência parcial e correspondência exata. Uma otimização de correspondência parcial é aplicada quando:

  1. O TOKENLIST contém todos os termos originais na consulta.
  2. Os tokens são adjacentes uns aos outros e na mesma ordem em que aparecem na consulta.

Há algumas regras especiais para conjunções, negações e frases:

  • Uma consulta com uma negação não pode receber uma otimização de correspondência parcial.
  • Uma consulta com uma conjunção recebe uma otimização se parte da conjunção aparecer nos locais apropriados.
  • Uma consulta com uma frase recebe uma otimização se a frase aparecer no TOKENLIST, e o termo à esquerda da frase na consulta aparecer à esquerda da frase no TOKENLIST, e o mesmo se aplica ao termo à direita da frase.

O Spanner aplica uma otimização de correspondência exata quando todas as regras anteriores são verdadeiras e os primeiros e últimos tokens na consulta são os primeiros e últimos tokens no documento.

Exemplo de documento: Bridge Over Troubled Water

Consulta Otimização aplicada
Bridge Troubled sem otimização
Bridge Over - other water sem otimização
Bridge (Over OR Troubled) Water sem otimização
Bridge Over otimização parcial
Bridge Over (Troubled OR Water) otimização parcial
Bridge Over Troubled Water otimização exata
Bridge "Over Troubled" Water otimização exata
Bridge ("Over Troubled" OR missingterm) Water otimização exata

Versões do marcador

O algoritmo do marcador é atualizado periodicamente. Cada versão agrupa um conjunto de melhorias no algoritmo de pontuação. Para uma lista detalhada das diferenças entre as versões, consulte Versões do marcador.

É possível definir a versão padrão do marcador para seu banco de dados ou especificar uma versão para uma consulta específica.

Definir a versão padrão do marcador do banco de dados

É possível definir a versão padrão do algoritmo do marcador para seu banco de dados. Essa opção determina qual versão do algoritmo de pontuação o Spanner usa quando a função SCORE é chamada sem uma opção de pontuação version explícita.

GoogleSQL

Defina a opção de banco de dados score_version:

ALTER DATABASE database_name SET OPTIONS (score_version = 2)

PostgreSQL

Defina a opção de banco de dados spanner.score_version:

ALTER DATABASE database_name SET "spanner.score_version" = 2

Os valores válidos para a opção de banco de dados são 1 ou 2. Se o parâmetro version estiver presente em uma solicitação, ele vai substituir a versão padrão do banco de dados.

Substituir a versão do marcador por consulta

É possível substituir a versão padrão do marcador para uma consulta específica usando o parâmetro version no argumento options da função SCORE. Se uma consulta definir uma versão do marcador, ela vai substituir a versão padrão do banco de dados.

O exemplo a seguir substitui a versão padrão do marcador especificando version no parâmetro options:

GoogleSQL

SELECT AlbumId
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, @query)
ORDER BY SCORE(
  AlbumTitle_Tokens,
  @query,
  options=>JSON '{"version": 2}'
) DESC

PostgreSQL

Este exemplo usa o parâmetro de consulta $1, que está vinculado à string de consulta.

SELECT albumid
FROM albums
WHERE spanner.search(albumtitle_tokens, $1)
ORDER BY spanner.score(
  albumtitle_tokens,
  $1,
  options => '{"version": 2}'::jsonb
) DESC

Limitar a profundidade de recuperação

Os índices de pesquisa geralmente contêm milhões de documentos. Para consultas em que os predicados têm baixa seletividade, não é prático classificar todos os resultados. As consultas de pontuação geralmente têm dois limites:

  1. Limite de profundidade de recuperação: o número máximo de linhas a serem pontuadas.
  2. Limite de tamanho do conjunto de resultados: o número máximo de linhas que a consulta deve retornar (normalmente o tamanho da página).

As consultas podem limitar a profundidade de recuperação com subconsultas SQL:

GoogleSQL

SELECT AlbumId
FROM
  (
    SELECT AlbumId, SCORE(Title_Tokens, @p1) AS score
    FROM Albums
    WHERE SEARCH(Title_Tokens, @p1)
    ORDER BY ReleaseTimestamp DESC
    LIMIT @retrieval_limit
  )
ORDER BY score DESC
LIMIT @page_size

PostgreSQL

Este exemplo usa os parâmetros de consulta $1, $2 e $3, que estão vinculados aos valores especificados para title_query, retrieval_limit e page_size, respectivamente.

SELECT albumid
FROM
  (
    SELECT albumid, spanner.score(title_tokens, $1) AS score
    FROM albums
    WHERE spanner.search(title_tokens, $1)
    ORDER BY releasetimestamp DESC
    LIMIT $2
  ) AS subquery
ORDER BY score DESC
LIMIT $3

Isso funciona muito bem se o Spanner usar o indicador de classificação mais importante para classificar o índice.

A seguir