对搜索结果进行排名

本页面介绍了如何在 Spanner 中对全文搜索的搜索结果进行排名。

Spanner 支持计算主题相关性得分,这为创建复杂的排名函数提供了基础。这些得分会根据查询字词频率和其他可自定义的选项来计算结果与查询的相关性。

以下示例展示了如何使用 SCORE 函数执行排名搜索:

GoogleSQL

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

PostgreSQL

此示例将 spanner.searchspanner.score 搭配使用。

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

使用 SCORE 函数为查询字词打分

SCORE 函数会计算每个查询字词的得分,然后将这些得分进行组合。每字词得分大致基于词频 - 逆文档频率 (TF/IDF)。得分是记录的最终排序的一个组成部分。查询会将其与其他信号(例如用于调整主题相关性得分的新鲜度)相结合。

在当前实现中,仅当使用 enhance_query=>true 时,TF/IDF 的 IDF 部分才可用。它会根据 Google 搜索使用的完整网络语料库(而非特定搜索索引)来计算字词的相对频率。如果未启用 rquery 增强功能,打分仅使用词频 (TF) 组成部分(即,IDF 项设置为 1)。

SCORE 函数返回的值用作相关性得分,供 Spanner 用于确定排列顺序。它们没有独立含义。得分越高,与查询的匹配度就越高。

通常,queryenhance_query 等参数在 SEARCHSCORE 函数中是相同的,以确保检索和排名的一致性。

建议将这些参数与查询参数(而非字符串字面量)一起使用,并在 SEARCHSCORE 函数中指定相同的查询参数。

对多个列进行打分

Spanner 使用 SCORE 函数单独为每个字段打分。查询随后会将这些单独的得分组合在一起。一种常见的方法是先将各个得分相加,然后根据用户提供的字段权重(使用 SQL 查询参数提供)来提升得分。

例如,以下查询将两个 SCORE 函数的输出组合在一起:

GoogleSQL

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

PostgreSQL

此示例使用查询参数 $1$2,它们分别绑定到“fifth symphony”和“blue note”。

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

以下示例添加了两个提升参数:

  • 新鲜度 (FreshnessBoost) 会按照 (1 + @freshnessweight * GREATEST(0, 30 - DaysOld) / 30) 增加得分
  • 热门程度 (PopularityBoost) 通过将得分乘以系数 (1 + IF(HasGrammy, @grammyweight, 0) 来增加得分。

为了提高可读性,查询使用 WITH 运算符。

GoogleSQL

SELECT AlbumId
FROM Albums
WHERE SEARCH(Title_Tokens, @p1) AND 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

此示例使用查询参数 $1$2$3$4$5$6,它们分别绑定到为 titlequerystudioquerytitleweightstudioweightgrammyweightfreshnessweight 指定的值。

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) AND 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 还可以在适当的时候用于搜索和打分以简化查询。

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

此示例使用 spanner.tokenlist_concat。 查询参数 $1 绑定到“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

提升查询顺序匹配

对于包含查询字词的值,如果这些字词的顺序与在查询中出现的顺序相同,Spanner 会对 SCORE 函数的输出应用乘法提升。此提升有两个版本:部分匹配和完全匹配。在以下情况下会应用部分匹配提升:

  1. TOKENLIST 包含查询中的所有原始字词。
  2. token 彼此相邻,并且顺序与它们在查询中出现的顺序相同。

对于合取、求反和短语,有一些特殊规则:

  • 包含求反的查询无法获得部分匹配提升。
  • 如果合取的一部分出现在适当的位置,则包含相应合取的查询会获得提升。
  • 如果短语出现在 TOKENLIST 中,并且查询中短语左侧的字词在 TOKENLIST 中出现在短语的左侧,且短语右侧的字词也同样如此,则包含相应短语的查询会获得提升。

如果满足上述所有规则,并且查询中的第一个和最后一个 token 是文档中的第一个和最后一个 token,则 Spanner 会应用完全匹配提升。

示例文档:Bridge Over Troubled Water

查询 应用的提升
Bridge Troubled 无提升
Bridge Over - other water 无提升
Bridge (Over OR Troubled) Water 无提升
Bridge Over 部分匹配提升
Bridge Over (Troubled OR Water) 部分匹配提升
Bridge Over Troubled Water 完全匹配提升
Bridge "Over Troubled" Water 完全匹配提升
Bridge ("Over Troubled" OR missingterm) Water 完全匹配提升

限制检索深度

搜索索引通常包含数百万个文档。对于谓词选择性较低的查询,对所有结果进行排名是不切实际的。对查询进行打分通常有两个限制:

  1. 检索深度限制:要打分的最大行数。
  2. 结果集大小限制:查询应返回的最大行数(通常为页面大小)。

查询可以使用 SQL 子查询限制检索深度:

GoogleSQL

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

PostgreSQL

此示例使用查询参数 $1$2$3,它们分别绑定到为 title_queryretrieval_limitpage_size 指定的值。

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

如果 Spanner 使用最重要的排名衡量因素对索引进行排序,这种方法的效果会特别好。

后续步骤