【実務・中級編】 tsvector型と全文検索 – PostgreSQL

やあ、今日もデータベースと格闘してる?

PostgreSQLで「全文検索」って聞くと、真っ先に「ElasticsearchやMeilisearchを入れるべきか?」って悩むよね。もちろん、要件が複雑ならそれらを使うのが正解なんだけど、実はPostgreSQL標準の機能だけでも、相当なことができるんだ。

特に「`tsvector`」を使いこなせると、中規模程度のアプリケーションなら外部エンジンなしで爆速の検索機能を実装できる。今日は、現場でエンジニアが現場でハマりやすいポイントも交えながら、`tsvector`の勘所を解説していくよ。

—

1. tsvectorって結局何者なの?

一言で言うと、「検索に特化した、ドキュメントの要約辞書」だ。

通常のテキストデータ(`text`型)をそのまま検索すると、SQLは逐次スキャン(シーケンシャルスキャン)を走らせるから、データ量が増えると死ぬほど重くなる。一方、`tsvector`に変換しておけば、単語が正規化され、どの位置にその単語があるかのインデックス情報がセットで保持される。

つまり、検索するときには「テキストの中身を探す」んじゃなくて、「整理された単語リストの中から目的のIDを引く」という処理に切り替わるんだ。これが速さの秘密。

—

2. 実践的な使い方:まずは変換してみよう

まずは、どうやってテキストを`tsvector`に変換するか。これが基本の呪文だよ。

— to_tsvectorを使って変換
SELECT to_tsvector(‘english’, ‘PostgreSQL is a powerful open-source relational database.’);

これの結果を見ると、`’databas’ ‘open-sourc’ ‘postgresql’ ‘power’ ‘relat’` みたいに、単語が抽出されて、語幹(ステミング)が処理された状態になっているはずだ。

現場の知恵:インデックスを貼るのが大前提

`tsvector`を計算するたびに毎回`to_tsvector`を呼んでいたら意味がない。テーブルに「検索用カラム」として保存するか、「関数インデックス」を貼るのが鉄板だ。

— インデックスを作成(GINインデックスがおすすめ)
CREATE INDEX idx_fts_content ON my_table USING GIN (to_tsvector(‘english’, content));

これで、`@@`演算子を使った検索がインデックス経由で爆速になる。

SELECT FROM my_table
WHERE to_tsvector(‘english’, content) @@ to_tsquery(‘english’, ‘PostgreSQL & database’);

—

3. 現場で「あ、これ必須だな」って思ったテクニック

教科書には載ってないけど、実務でやらないと後悔するポイントを3つだけ教えておくよ。

① 「カラムの自動更新」をトリガーでやる

毎回SQL側で`tsvector`を計算させるのはバグの元だ。テーブルに専用の`tsvector`型カラムを作っておいて、`BEFORE INSERT OR UPDATE`トリガーで自動的に値を更新させるのが一番クリーンだよ。

— こんな感じでトリガーを設定する
CREATE FUNCTION update_tsvector_column() RETURNS trigger AS $$
BEGIN
NEW.tsv := to_tsvector(‘english’, NEW.content);
RETURN NEW;
END
$$ LANGUAGE plpgsql;

② 重み付け(Weighting)で検索精度を上げる

タイトルと本文、どちらにキーワードが含まれているか重要だよね?`setweight`を使うと、「タイトルのヒットを優先する」みたいなチューニングが可能になる。

— タイトル(A)と本文(B)で重みを変える
UPDATE my_table SET tsv =
setweight(to_tsvector(‘english’, title), ‘A’) ||
setweight(to_tsvector(‘english’, content), ‘B’);

こうしておくと、`ts_rank`関数で検索結果のスコアを計算するとき、タイトルのヒットがより上位に来るようになるんだ。

③ 日本語は「pg_bigm」か「textsearch_ja」

ここが日本でPostgreSQLを使うときの最大の壁なんだけど、英語と違って日本語にはスペースがない。標準の`english`設定では単語分割がうまくいかないんだ。
現場の結論としては、`pg_bigm`拡張機能を導入するのが最も手軽で安定する。2文字ずつのバイグラムでインデックスを貼るから、どんな文章でも漏れなく検索できるよ。

—

最後に:完璧を求めすぎない勇気も大事

`tsvector`は素晴らしい機能だけど、限界もある。「曖昧検索(あいまいけんさく)」の柔軟性や、高度なサジェスト機能、あるいは超大規模な分散検索が必要になったら、素直にElasticsearchのような専門のツールへ移行するタイミングだ。

でも、まずはPostgreSQLの中で完結させてみる。この「シンプルに保つ」という感覚が、運用コストを下げる一番の近道だと僕は思うよ。

何か詰まったら、またいつでも聞きに来て。DBの設計は、結局のところ「どうやってデータを使いやすくするか」というパズルだからね。楽しんでいこう!

コメント

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