「検索精度が微妙だな…」と悩む君へ。PostgreSQLの `ts_rank` で検索体験を一段階引き上げよう
「検索機能を作ったけど、結果の並び順がイマイチしっくりこない」
「ただの `LIKE` 検索じゃ限界があるし、かといってElasticsearchを導入するほどの大規模構成でもないんだよな……」
現場でそんな相談を受けることがよくあります。わかるよ、その悩み。PostgreSQLを使っているなら、外部ツールを持ち出す前に、まずは標準の全文検索機能(Full Text Search)を使いこなすところから始めよう。
今回は、検索結果の「関連度」をコントロールするための要、`ts_rank` 関数について解説するよ。これを知っているだけで、ユーザーが目にする検索結果の質は劇的に変わるはずだ。
—
なぜ「検索順位」が重要なのか
そもそも、なぜ単なる一致検索じゃダメなのか。それは、ユーザーが「一番求めている情報」を先に見つけたいからだよね。
PostgreSQLの全文検索では、対象のテキストを `tsvector`(検索用データ)に変換し、検索語を `tsquery` に変換してマッチングさせる。でも、これだけだと「ヒットしたか、しないか」の0か1かしかわからない。
そこで登場するのが `ts_rank` だ。こいつは、「その単語がどれくらい頻繁に出現しているか」や「文書のどこに位置しているか」を計算して、スコア(浮動小数点数)を弾き出してくれる。
基本的な使い方:シンプルに並び替える
まずは一番シンプルな例を見てみよう。`articles` テーブルの `content` カラムを検索する場合だ。
SELECT 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;
これだけで、「PostgreSQL」と「パフォーマンス」の両方が含まれていて、なおかつ出現頻度が高い記事が上位に来るようになる。どう? 意外と簡単でしょ?
実務で差がつく!「重み付け」のテクニック
ただね、実務で `ts_rank` を使うときに気をつけたいのが「情報の優先度」だ。
例えば、ブログ記事なら「タイトルに含まれている単語」の方が「本文の最後の方に1回だけ出てくる単語」よりも、ユーザーにとって価値が高いはずだよね。こういうときは、`setweight` を使うのがプロのやり方だ。
SELECT title,
ts_rank(
setweight(to_tsvector(‘japanese’, title), ‘A’) ||
setweight(to_tsvector(‘japanese’, content), ‘B’),
to_tsquery(‘japanese’, ‘PostgreSQL’)
) AS rank
FROM articles
ORDER BY rank DESC;
ここで `A` や `B` というラベルを付けているのがポイント。
- A: 非常に重要(タイトルなど)
- B: 重要(本文など)
こうすると、PostgreSQLは自動的にAの重みを大きく計算してくれる。これで、「タイトルにキーワードが入っている記事」が優先的に表示されるようになる。この「ちょっとした気遣い」の積み重ねが、プロダクトのクオリティを左右するんだ。
注意点:パフォーマンスとのトレードオフ
ここで、少しだけ現場のリアルな忠告を。
`ts_rank` は実行時に計算コストがかかる関数だ。数万件程度のテーブルなら問題ないけれど、数百万件規模になってくると、`ORDER BY ts_rank` がクエリのボトルネックになりかねない。
もし検索が重いと感じたら、以下の対策を検討してほしい。
1. 生成カラム(Generated Columns)を使う:
`tsvector` を計算した結果をカラムとして物理的に持たせておく(PostgreSQL 12以降なら便利だ)。
2. インデックスの活用:
`GIN` インデックスを貼っておけば、`@@` による絞り込みは爆速になる。あとは `ts_rank` を計算する対象を絞り込めば、スキャンする行数を減らせる。
最後に:完璧を求めすぎないことも大切
全文検索は沼だ。こだわり始めるとキリがない。
「この単語のスコアが低すぎる」とか「この結果が1位に来るのはおかしい」なんて言い出すと、チューニング作業で日が暮れちゃうからね(笑)。
最初は `ts_rank` でベースラインを作り、ユーザーの反応を見ながら徐々に重み付けを調整していく。この「段階的な改善」こそが、良いエンジニアのやり方だと思う。
もしコードを書いていて「ここ、もっと効率よく書けないかな?」と迷ったら、いつでも聞いてくれ。一緒に最高に使いやすい検索機能を実装していこう!
コメント