0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SQLite FTS5で件数は1件、検索は0件になる理由

0
Posted at

1件読めるのに、検索すると0件。macOSのCPython 3.14.8、SQLite 3.54.1で確かめた。

自分は今日、RAGの検索用に、既存文書へFTS5を後付けするコードを動かした。本文まで読めるのに、MATCHでは出てこないんだよね。

件数の照合だけで済ませるのは危ない。

FTS5の表を作っても、既存文書は索引に入らない

content='docs'を指定すると、本文はdocsに置き、FTS5側には検索用の索引を持たせられる。

表を作っても、既存文書の索引は作られない。同期用のトリガーも、過去のINSERTにはさかのぼらない。

ややこしいのは、MATCHなしでFTS5の表を読むと、元のdocsから行を取得すること。count(*)の1件と、検索索引に載った1件は、別々に確認する必要がある。

Pythonで再現し、rebuild後の更新まで試す

fts_check.pyに保存し、python3 fts_check.pyで実行する。FTS5入りのSQLiteが必要。追加パッケージ不要、メモリ上で完結する。

import sqlite3

db = sqlite3.connect(":memory:")
db.executescript("""
CREATE TABLE docs(id INTEGER PRIMARY KEY, body TEXT NOT NULL);
INSERT INTO docs VALUES(1, 'agent search');
CREATE VIRTUAL TABLE search USING fts5(
    body, content='docs', content_rowid='id'
);
CREATE TRIGGER docs_ai AFTER INSERT ON docs BEGIN
    INSERT INTO search(rowid, body) VALUES(new.id, new.body);
END;
CREATE TRIGGER docs_ad AFTER DELETE ON docs BEGIN
    INSERT INTO search(search, rowid, body)
        VALUES('delete', old.id, old.body);
END;
CREATE TRIGGER docs_au AFTER UPDATE ON docs BEGIN
    INSERT INTO search(search, rowid, body)
        VALUES('delete', old.id, old.body);
    INSERT INTO search(rowid, body) VALUES(new.id, new.body);
END;
""")

def hits(word):
    return db.execute(
        "SELECT rowid FROM search WHERE search MATCH ? ORDER BY rowid",
        (word,),
    ).fetchall()

print("rows:", db.execute("SELECT count(*) FROM search").fetchone()[0])
print("before:", hits("agent"))
db.execute("INSERT INTO search(search) VALUES('rebuild')")
print("rebuilt:", hits("agent"))
db.execute("UPDATE docs SET body='retrieval search' WHERE id=1")
print("updated:", hits("agent"), hits("retrieval"))
db.execute("DELETE FROM docs WHERE id=1")
print("deleted:", hits("retrieval"))
db.close()

実行結果。

rows: 1
before: []
rebuilt: [(1,)]
updated: [] [(1,)]
deleted: []

rebuild後、agentが検索に出た。更新後は古い語が0件、新しい語が1件。削除後は新しい語も0件。

検索語は英単語にした。日本語の単語分割まで混ぜると、切り分けが増えてしまうので。

初回の索引作成と、その後の同期をそろえる

rebuildは既存文書から索引を作り直す。トリガーは、作成後のINSERT・UPDATE・DELETEを索引へ反映する。

更新時はold.bodyで古い語の登録を消し、new.bodyを登録する。新しい本文を足すだけだと、変更前の語が索引に残る。削除用の特殊なINSERTに、変更前の値を渡すのがポイント。

移行時は文書の書き込みを止め、トリガー作成とrebuildを済ませたい。索引全体を作り直すため、大きな表では所要時間を別途測る。今回の1文書では見積もれない。

検索の確認を一つ足す

RAGで参照が抜けたら、まず検索語から文書IDが返るかを確かめたい。今回は1文書の検索だけで、件数照合が見逃す索引の欠落を拾えた。導入時に加え、更新・削除後も検索結果を確かめるテストを残す。文書の追加経路が増えるほど、この同期の確認が効いてくると見ている。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?