本页面介绍了如何在 Spanner 中对全文搜索的搜索结果进行排名。
Spanner 支持计算主题相关性得分,这为创建复杂的排名函数提供了基础。这些得分会根据查询字词频率和其他可自定义的选项来计算结果与查询的相关性。
以下示例展示了如何使用 SCORE
函数执行排名搜索:
GoogleSQL
SELECT AlbumId
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")
ORDER BY SCORE(AlbumTitle_Tokens, "fifth symphony") DESC
PostgreSQL
此示例将 spanner.search
与 spanner.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 用于确定排列顺序。它们没有独立含义。得分越高,与查询的匹配度就越高。
通常,query
和 enhance_query
等参数在 SEARCH
和 SCORE
函数中是相同的,以确保检索和排名的一致性。
建议将这些参数与查询参数(而非字符串字面量)一起使用,并在 SEARCH
和 SCORE
函数中指定相同的查询参数。
对多个列进行打分
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
,它们分别绑定到为 titlequery
、studioquery
、titleweight
、studioweight
、grammyweight
和 freshnessweight
指定的值。
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
函数的输出应用乘法提升。此提升有两个版本:部分匹配和完全匹配。在以下情况下会应用部分匹配提升:
TOKENLIST
包含查询中的所有原始字词。- 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 | 完全匹配提升 |
限制检索深度
搜索索引通常包含数百万个文档。对于谓词选择性较低的查询,对所有结果进行排名是不切实际的。对查询进行打分通常有两个限制:
- 检索深度限制:要打分的最大行数。
- 结果集大小限制:查询应返回的最大行数(通常为页面大小)。
查询可以使用 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_query
、retrieval_limit
和 page_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 使用最重要的排名衡量因素对索引进行排序,这种方法的效果会特别好。
后续步骤
- 了解全文搜索查询。
- 了解如何执行子字符串搜索。
- 了解如何对搜索结果进行分页。
- 了解如何混合使用全文查询和非文本查询。
- 了解如何搜索多个列。