すべてのプロダクト
Search
ドキュメントセンター

AnalyticDB:Nova BM25 クイックスタート

最終更新日:Aug 13, 2026

BM25 は、情報検索における古典的な関連性ランキングアルゴリズムです。単語頻度、ドキュメントの長さ、逆文書頻度に基づいてキーワードとドキュメントの一致度を測定し、Elasticsearch などの検索エンジンのデフォルトのランキングの基盤として機能します。Nova BM25 は、この機能をネイティブインデックスとして AnalyticDB for PostgreSQL に統合します。テキスト列に nova_bm25 インデックスを作成するだけで、標準 SQL で中国語の形態素解析、キーワードリコール、フレーズマッチング、関連性ランキングを実行できます。また、BM25 検索を、カテゴリ、数値、タイムスタンプなどの構造化条件やベクトル検索と組み合わせることもでき、外部の検索システムは不要です。このトピックでは、AnalyticDB for PostgreSQL に Nova BM25 拡張をインストールし、最初の中国語全文検索の例を実行する方法について説明します。このトピックの手順を実行すると、関連性によって結果をランク付けする全文検索 SQL クエリを作成できるようになります。

Nova BM25 の SQL 関数は bm25 スキーマ配下にあり、インデックスアクセスメソッド名は nova_bm25 です。

前提条件

  • AnalyticDB for PostgreSQL インスタンスのバージョンが 7.5.1.0 以降で、ベクトル検索エンジン最適化が有効になっている必要があります。

  • nova_bm25 プラグインは、コンソールからセルフサービスでインストールすることはまだできません。このプラグインを使用するには、チケットを送信し、テクニカルサポートに連絡してインスタンスのアップグレードとインストールを依頼する必要があります。

操作手順

ステップ 1:サンプルデータの準備

サンプルテーブルを作成し、後続の全文検索操作用のテストデータを挿入します。

DROP TABLE IF EXISTS docs;

CREATE TABLE docs (
    id         BIGINT PRIMARY KEY,
    title      TEXT,
    body       TEXT,
    category   TEXT,
    rating     INT,
    publish_at TIMESTAMP
) DISTRIBUTED BY (id);

INSERT INTO docs(id, title, body, category, rating, publish_at) VALUES
    (1, 'Database System Design', 'PostgreSQL supports full-text search and transaction processing', 'tech', 5, '2024-01-01 10:00:00'),
    (2, 'Search Engine Development', 'BM25 ranking is used for full-text search relevance computation', 'tech', 5, '2024-02-01 10:00:00'),
    (3, 'Vector Search Practice', 'Hybrid search combines vector recall and keyword recall', 'tech', 4, '2024-03-01 10:00:00'),
    (4, 'User Profile Analysis', 'Real-time profile processing for recommendation systems', 'biz', 3, '2024-04-01 10:00:00'),
    (5, 'Punctuation Test', 'Database, full-text search; query optimization! BM25 ranking.', 'tech', 4, '2024-05-01 10:00:00');

実際のテーブルでこの例に示されている列名を使用する必要はありません。全文検索が必要なテキスト列と、フィルタリングに使用したいカテゴリ、数値、またはタイムスタンプ列を BM25 インデックスに含めるだけです。

ステップ 2:中国語の BM25 インデックスの作成

サンプルテーブルに BM25 インデックスを作成し、各フィールドの目的に基づいて、異なるトークナイザーとフィールドタイプを設定します。

CREATE INDEX docs_bm25_idx ON docs
USING nova_bm25 (body, title, category, rating, publish_at)
WITH (
    text_fields = '{
      "body": {"tokenizer": {"type": "jieba"}},
      "title": {"tokenizer": {"type": "jieba"}},
      "category": {"tokenizer": {"type": "keyword"}}
    }',
    numeric_fields = '{"rating": {}}',
    datetime_fields = '{"publish_at": {}}'
);

上記の WITH 句では、各フィールドをその目的に基づいて設定します。text_fieldstokenizer.type は、各テキストフィールドに使用されるトークナイザーを指定します。

  • 全文検索が必要な中国語のテキスト列 (body や title など) は、 text_fields で設定し、 jieba トークナイザーを使用します。

  • カテゴリ、ステータス、テナント、enum 値など、完全一致が必要なテキスト列も text_fields で設定しますが、 keyword トークナイザーを使用して、形態素解析を行わずに値全体を照合します。

  • 評価や価格などの数値列は、 numeric_fields で設定します。

  • 公開時刻や更新時刻などのタイムスタンプ列は、 datetime_fields で設定します。

ステップ 3:最初のクエリの実行

次の SQL 文を使用して、 body フィールドに対して BM25 全文検索を実行します。クエリテキストは「database full-text search」です。 bm25.match 関数は、フィールドのトークナイザーに基づいて入力を自動的に形態素解析します。デフォルトでは、いずれかのトークンに一致するドキュメントが返され、結果は BM25 スコアによってランク付けされます。

SELECT
    id,
    title,
    bm25.score(docs) AS score
FROM docs
WHERE body @@@ bm25.match('database full-text search')
ORDER BY bm25.score(docs) DESC
LIMIT 10;

上記の SQL 文の主な構成要素は次のとおりです。

  • body @@@ bm25.match(...)body フィールドで BM25 全文検索を実行します。

  • bm25.score(docs):BM25 の関連性スコアを返します。スコアが高いほど、関連性が高いことを示します。

  • ORDER BY bm25.score(docs) DESC:結果を関連性スコアの降順で並べ替え、最も関連性の高い結果を最初に表示します。

クエリ結果の例を次の表に示します。 score の正確な値は、バージョンやトークナイザーの設定によって若干異なる場合があります。

id

title

score

2

Search Engine Development

...

5

Punctuation Test

...

1

Database System Design

...

3

Vector Search Practice

...

実際には、検索テキストを bm25.match に渡すだけで全文検索を実行できます。

一般的なクエリパターン

AND マッチング (すべてのトークンが一致)

デフォルトでは、 bm25.match は、いずれかのトークンに一致する場合にドキュメントを返します。すべてのトークンの一致を必須にするには、 operator => 'and' を設定します。

SELECT id, title, bm25.score(docs) AS score
FROM docs
WHERE body @@@ bm25.match('database full-text search', operator => 'and')
ORDER BY bm25.score(docs) DESC
LIMIT 10;

また、短縮演算子 &&& を使用しても、同じ結果が得られます。

SELECT id, title, bm25.score(docs) AS score
FROM docs
WHERE body &&& 'database full-text search'
ORDER BY bm25.score(docs) DESC
LIMIT 10;

フレーズマッチング

full-text search のような完全なフレーズを検索する場合、つまりトークンが元の順序で隣接している必要がある場合は、 bm25.phrase を使用します。

SELECT id, title, bm25.score(docs) AS score
FROM docs
WHERE body @@@ bm25.phrase('full-text search')
ORDER BY bm25.score(docs) DESC
LIMIT 10;

構造化フィルタリング

BM25 全文検索は、標準 SQL 条件と組み合わせて、カテゴリ、評価、時刻、その他の構造化フィールドでフィルタリングできます。

SELECT id, title, bm25.score(docs) AS score
FROM docs
WHERE body @@@ bm25.match('database search')
  AND category = 'tech'
  AND rating >= 4
  AND publish_at >= '2024-01-01'
ORDER BY bm25.score(docs) DESC
LIMIT 10;

複数フィールドの重み付けクエリ

body フィールドでの一致よりも title フィールドでの一致が重要な場合は、title フィールドにより高い重みを割り当てることができます。

SELECT id, title, bm25.score(docs) AS score
FROM docs
WHERE bm25.multi_match(
    ARRAY['title^3', 'body'],
    query => 'database full-text search'
)
ORDER BY bm25.score(docs) DESC
LIMIT 10;

上記の例では、 title^3 は、title フィールドの重みを body フィールドの 3 倍に設定します。 body に重みが指定されていない場合、デフォルトで 1 になります。

説明

複数フィールドの重み付けクエリに含まれるすべてのフィールドは、同じ BM25 インデックスに属している必要があり、重みの値は正の数でなければなりません。

書き込み後のクエリの可視性

この例では、テーブルの作成、データの挿入、インデックスの作成、そしてクエリの実行という順序に従います。インデックス作成前にコミットされたデータは、 CREATE INDEX 操作中にインデックスが作成されます。したがって、この例のすべてのデータは、インデックス作成後すぐにクエリ可能です。

インデックス作成後の INSERTUPDATE 操作は増分書き込みとなります。Nova BM25 はデフォルトでニアリアルタイムのクエリモードを使用します。増分書き込みによる最新データは、デフォルトのクエリ結果に表示されるまでに、短いバックグラウンド処理期間を必要とする場合があります。

現在のセッションで最新の増分データをすぐにクエリする必要がある場合は、次のステートメントを実行します。

SET nova_bm25.query_include_mutable = on;

不要になったら、次のステートメントでデフォルト設定に戻します。

RESET nova_bm25.query_include_mutable;
説明

この設定は現在のデータベース接続にのみ適用されます。コネクションプールを使用する場合、接続が再利用されることがあります。ビジネス要件に基づいてこの設定を行い、速やかに元に戻してください。

インデックスが常に最新の増分データを含む必要がある場合は、次のステートメントを実行して設定できます。

ALTER INDEX docs_bm25_idx
SET (query_skip_mutable = false);
重要

最新の増分データをクエリするには、まだバックグラウンドで完全に整理されていないインデックスデータの追加処理が必要となり、クエリレイテンシーとリソース消費が増加する可能性があります。

アプリケーション側のパラメーターバインド

SQL インジェクションを防ぐため、ユーザー入力を直接 SQL 文字列に連結しないでください。JDBC では、パラメーターバインドに PreparedStatement を使用することを推奨します。

PreparedStatement ps = conn.prepareStatement("""
    SELECT id, title, bm25.score(docs) AS score
    FROM docs
    WHERE body @@@ bm25.match(?, operator => 'or')
    ORDER BY bm25.score(docs) DESC
    LIMIT ?
""");
ps.setString(1, userQuery);
ps.setInt(2, 10);

次のステップ