【実務・中級編】 ランキング関数とハイライト関数 – PostgreSQL

「お疲れ様。検索機能のパフォーマンス改善、お疲れさん。全文検索の実装で `ts_rank` と `ts_headline` の使いどころに悩んでるみたいだね。

このあたり、ドキュメントを読めば載っているけれど、いざ実務で『どうやってスコアを調整するか』とか『スニペットをどう切り出すか』という話になると、意外とハマりどころが多いんだ。今日は、現場の知見を交えつつ、PostgreSQLの全文検索を一段階レベルアップさせる話をしようか。」

—

なぜ「ただ検索するだけ」ではダメなのか

全文検索を実装するとき、最初は `@@` 演算子だけで満足しがちだよね。でも、ユーザーにとって本当に価値があるのは「一致したレコード」そのものじゃなくて、「自分にとって関連性が高い順」に並んだ結果と、その「理由(どこにキーワードがあるか)」なんだ。

ここで登場するのが `ts_rank` と `ts_headline` だ。これらをうまく使うだけで、検索のUXは劇的に変わる。

—

1. ts_rank:ユーザーが「欲しい」結果を上位に持ってくる

`ts_rank` は、検索クエリとドキュメントの適合度を計算する関数だ。単純に「ヒットした数」だけじゃなく、「どこにキーワードがあるか」も加味してくれる。

実践的なコード例

SELECT
id,
title,
ts_rank(to_tsvector(‘japanese’, content), to_tsquery(‘japanese’, ‘PostgreSQL & パフォーマンス’)) AS rank
FROM
articles
WHERE
to_tsvector(‘japanese’, content) @@ to_tsquery(‘japanese’, ‘PostgreSQL & パフォーマンス’)
ORDER BY
rank DESC
LIMIT 10;

先輩からのアドバイス:重み付けを忘れるな

デフォルトのままでも動くけど、本番環境では「タイトルに含まれるキーワードは重要度が高い」といった調整が必要になるはず。その時は `setweight` を使うのが定石だ。

— タイトル(A)と本文(B)で重要度を変える例
SELECT
ts_rank(
setweight(to_tsvector(‘japanese’, title), ‘A’) ||
setweight(to_tsvector(‘japanese’, content), ‘B’),
to_tsquery(‘japanese’, ‘PostgreSQL’)
) AS rank
…

こうすると、タイトルにヒットした結果が自然と上位にくるようになる。この「少しの差」が、ユーザーの満足度に直結するんだよね。

—

2. ts_headline:検索結果に「納得感」を与える

検索結果一覧で、ただタイトルだけが並んでいてもユーザーは判断できない。「なぜこのページがヒットしたのか?」を可視化するのが `ts_headline` の役割だ。

基本的な使い方

SELECT
title,
ts_headline(‘japanese’, content, to_tsquery(‘japanese’, ‘PostgreSQL’), ‘StartSel = , StopSel = , MaxWords = 35, MinWords = 15′) AS snippet
FROM
articles
WHERE
to_tsvector(‘japanese’, content) @@ to_tsquery(‘japanese’, ‘PostgreSQL’);

ここが実務の勘所

`ts_headline` の設定オプションは意外と奥が深い。特に以下の2点は覚えておいて。

  • MaxWords / MinWords: これを指定しないと、デフォルトではかなり短いスニペットになりがちだ。UXを考慮して、スマホなら短め、PCなら長めといった調整をアプリ側で切り替えられるようにしておくと良いよ。
  • 計算コスト: `ts_headline` は実行時に文字列を走査するから、重い。検索結果全件に対して実行せず、LIMITで絞り込んだ結果に対してのみ適用するようにしよう。

—

注意点:パフォーマンスとの付き合い方

最後に一点だけ。ここが一番大事かもしれない。

`ts_rank` や `ts_headline` を使う際、`WHERE` 句で `to_tsvector` を毎回呼び出すのは避けてほしい。テーブルが大きくなると、検索のたびに全行の解析が走り、CPUが悲鳴を上げる。

必ず「検索用カラム(tsvector型)」を別に作り、GINインデックスを張ること。

— 事前にインデックスを作成しておく
CREATE INDEX idx_articles_content ON articles USING GIN (content_tsvector);

こうしておけば、`ts_rank` の計算対象もインデックス済みのカラムを指定できるから、爆速になる。インデックス設計を疎かにすると、どんなに優れた検索アルゴリズムを使っても台無しだからね。

—

まとめ:検索は「対話」である

全文検索は、データベースの中でも特に「ユーザーとの対話」に近い部分だ。ただ検索結果を返すのではなく、`ts_rank` で優先順位を整理し、`ts_headline` で文脈を提示する。

まずは自分の手元のデータで、`setweight` を変えてみて、スニペットの長さをいじってみる。その「微調整」の積み重ねが、最高の検索体験を作るんだ。

他にも詰まっているところがあればいつでも聞いてくれ。応援してるよ!

コメント

タイトルとURLをコピーしました