このページでは、Spanner で全文検索の検索結果をランク付けする方法について説明します。
Spanner はトピカリティ スコアの計算をサポートしており、これは高度なランキング関数を作るための基盤となります。これらのスコアは、クエリに対する結果の関連性を、クエリ内の語句の出現頻度や他のカスタマイズ可能なオプションに基づいて計算します。
次の例は、SCORE
関数を使用してランク付けされた検索を行う方法を示したものです。
GoogleSQL
SELECT AlbumId
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")
ORDER BY SCORE(AlbumTitle_Tokens, "fifth symphony") DESC
PostgreSQL
この例では、spanner.score
と spanner.search
を一緒に使用しています。
SELECT albumid
FROM albums
WHERE spanner.search(albumtitle_tokens, 'fifth symphony')
ORDER BY spanner.score(albumtitle_tokens, 'fifth symphony') DESC
SCORE
関数を使用してクエリ語句をスコアリングする
SCORE
関数は、クエリ内の各語句のスコアを計算し、そのスコアを結合します。語句ごとのスコアは、おおまかに用語頻度 - 逆文書頻度(TF/IDF)に基づいています。スコアは、レコードの最終的な並び順を決める要素の一つです。クエリは、スコアを他のシグナルと組み合わせて使用します。たとえば、鮮度がトピカリティ スコアに影響を与えるといった具合です。
現在の実装では、TF/IDF の IDF 部分は enhance_query=>true
が使用されている場合にのみ利用可能です。特定の検索インデックスではなく、Google 検索で使用されるウェブコーパス全体に基づいて、単語の相対頻度が計算されます。rquery 拡張機能が有効になっていない場合、スコアリングでは語句頻度(TF)コンポーネントのみが使用されます(つまり、IDF 語句は 1 に設定されます)。
SCORE
関数は、Spanner が並び順を決めるために使用する関連性スコアとなる値を返します。これらの値は単独では機能しません。スコアが高いほど、クエリとの一致度が高くなります。
通常、query
や enhance_query
などの引数は、SEARCH
関数と SCORE
関数の両方で同じ値にすることで、情報の取得とランキングの整合性を保ちます。
これを行うために、文字列リテラルではなくクエリ パラメータを指定した引数を使用し、SEARCH
関数と SCORE
関数で同じクエリ パラメータを指定することをおすすめします。
複数の列をスコアリングする
Spanner は SCORE
関数を使用して、各フィールドを個別にスコアリングします。それから、クエリによってこれらの個々のスコアが結合されます。これを行う一般的な方法は、個別のスコアを合計し、その後、ユーザーが指定したフィールドの重み付けに応じてスコアを調整することです(重みは SQL クエリのパラメータとして指定されます)。
たとえば、次のクエリは 2 つの 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
次の例では、2 つのブースト パラメータを追加します。
- 鮮度(
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
関数の出力に乗算によるブーストを適用します。このブーストには、部分一致と完全一致の 2 つの種類があります。部分一致ブーストは、次の場合に適用されます。
TOKENLIST
に、クエリの元の語句がすべて含まれている。- トークン同士が隣接しており、クエリでの並び順どおりである。
連結詞、否定、フレーズに対しては、特別なルールがあります。
- 否定を含むクエリは、部分一致ブーストを受けられません。
- 結合詞を含むクエリは、その結合詞の一部が適切な場所に現れるとブーストの対象となります。
- フレーズを含むクエリは、そのフレーズが
TOKENLIST
に現れ、かつクエリ内のフレーズの左側の語句がTOKENLIST
内でもフレーズの左側に現れる場合にブーストを受けます。右側の語句に対しても同様です。
上記のすべてのルールが満たされ、かつクエリの最初と最後のトークンがドキュメントの最初と最後のトークンと一致するときに、完全一致のブーストを適用します。
ドキュメントの例: Bridge Over Troubled Water
クエリ | 適用されるブースト |
---|---|
Bridge Troubled | ブーストなし |
Bridge Over - other water | ブーストなし |
Bridge (Over OR Troubled) Water | ブーストなし |
Bridge Over | 部分的なブースト |
Bridge Over(Troubled または Water) | 部分的なブースト |
Bridge Over Troubled Water | 完全一致ブースト |
Bridge「Over Troubled」Water | 完全一致ブースト |
Bridge ("Over Troubled" OR missingterm) Water | 完全一致ブースト |
取得深度を制限する
多くの場合、検索インデックスには何百万ものドキュメントが含まれています。述語の選択性が低いクエリの場合、すべての結果をランク付けするのは現実的ではありません。クエリのスコアリングには通常、次の 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
この例では、title_query
、retrieval_limit
、page_size
に指定された値にそれぞれバインドされるクエリ パラメータ $1
、$2
、$3
を使用しています。
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 が最も重要なランキング シグナルを使ってインデックスを並べ替えている場合に特に効果的です。
次のステップ
- 全文検索クエリについて確認する。
- 部分文字列検索を行う方法を確認する。
- 検索結果をページ分けする方法を確認する。
- 全文クエリと非テキストクエリを組み合わせる方法を確認する。
- 複数の列を検索する方法を確認する。