SQL関数に似ている。だがFTSクエリ内でしか使えない。
成果物
情報源
組込の補助関数
補助関数 | 概要 |
---|---|
bm25() | 一致の精度を示す数値を返す 低いほど良い一致 |
snippet() | 一致した行からテキスト断片を抜き出す |
highlight() | 一致したフレーズの前後に任意のテキストを追加する |
bm25()
以下のように書く。
SELECT * FROM fts WHERE fts MATCH ? ORDER BY bm25(fts);
コード例。以前ビルドしたMeCab用トークナイザを使っている。
.load ./fts5_mecab create virtual table posts using fts5(title, content, tokenize='mecab'); insert into posts values('タイトルです', '本文です。'); insert into posts values('タイトなタイトルです', '本文章は本文です。');
bm25()
で一致率が高い順にする。
select * from posts where posts match 'タイト*' order by bm25(posts);
タイトなタイトルです|本文章は本文です。 タイトルです|本文です。
ソートしなければ以下のように挿入順になった。
select * from posts where posts match 'タイト*';
タイトルです|本文です。 タイトなタイトルです|本文章は本文です。
フレーズの一致数が多いほど優先順位が高くなると思われる。
列ごとに重み付けできる。デフォルトは10.0
。引数の位置は列の定義位置に対応する。
insert into posts values('表題です', 'タイトな本文です。');
select * from posts where posts match 'タイト*' order by bm25(posts, 2.0, 9.0);
表題です|タイトな本文です。 タイトなタイトルです|本文章は本文です。 タイトルです|本文です。
なんと、タイト*
に一致するのが1つしかない行が先頭になった。これは重み付けによるもの。
列 | 重み |
---|---|
title |
2.0 |
content |
9.0 |
title |
content |
重み |
---|---|---|
表題です |
タイトな本文です。 |
9 |
タイトなタイトルです |
本文章は本文です。 |
4 |
タイトルです |
本文です。 |
2 |
タイト
とタイトル
では重み付けが違う計算方法を使ってみるかもしれない。だが、それは誤差として概算する。詳しく知りたければ英語のwikipedia参照。
おなじ重み付けでソートすると以下。
select * from posts where posts match 'タイト*' order by bm25(posts);
タイトなタイトルです|本文章は本文です。 タイトルです|本文です。 表題です|タイトな本文です。
タイト*
に一致した数が多い行が最上位。
ちなみに、ソートしなければ挿入順。
select * from posts where posts match 'タイト*';
タイトルです|本文です。 タイトなタイトルです|本文章は本文です。 表題です|タイトな本文です。
highlight()
マッチ箇所の前後にテキストを挿入して返す。
以下のように書く。
SELECT highlight(fts, 0, '<b>', '</b>') FROM fts WHERE fts MATCH ?
4つの引数はそれぞれ以下。
- 対象テーブル名
- テキストを読み取るFTSテーブル列のインデックスを示す整数(
0
〜) - 各フレーズが一致する前に挿入するテキスト
- 各フレーズが一致した後に挿入するテキスト
CREATE VIRTUAL TABLE ft USING fts5(a); INSERT INTO ft VALUES('a b c x c d e'); INSERT INTO ft VALUES('a b c c d e'); INSERT INTO ft VALUES('a b c d e'); SELECT highlight(ft, 0, '[', ']') FROM ft WHERE ft MATCH 'a+b+c AND c+d+e';
[a b c] x [c d e] [a b c] [c d e] [a b c d e]
日本語で使ってみる。
.load ./fts5_mecab create virtual table posts using fts5(title, content, tokenize='mecab'); insert into posts values('タイトルです', '本文です。'); insert into posts values('タイトなタイトルです', '本文章は本文です。');
SELECT highlight(posts, 0, '<b>', '</b>') FROM posts WHERE posts MATCH 'タイトル'
<b>タイトル</b>です タイトな<b>タイトル</b>です
title
, content
の列を取得してみる。
SELECT highlight(posts, 0, '<b>', '</b>'), highlight(posts, 1, '<b>', '</b>') FROM posts('タイト*');
<b>タイトル</b>です|本文です。 <b>タイト</b>な<b>タイトル</b>です|本文章は本文です。 表題です|<b>タイト</b>な本文です。
ところで、検索結果の一部を目立たせたいときHTML5では<mark>
のほうが正しいと思われる。
SELECT highlight(posts, 0, '<mark>', '</mark>'), highlight(posts, 1, '<mark>', '</mark>') FROM posts('タイト*');
<mark>タイトル</mark>です|本文です。 <mark>タイト</mark>な<mark>タイトル</mark>です|本文章は本文です。 表題です|<mark>タイト</mark>な本文です。
snippet()
列の値を1行まるごとでなく一部を返す。
- テーブル名
- 返されたテキストを選択するFTSテーブル列のインデックスを示す整数。(
0
〜。負数: 列の自動選択) - 返されるテキスト内で各フレーズが一致する前に挿入するテキスト。
- 返されたテキスト内で各フレーズが一致した後に挿入するテキスト。
- 選択されたテキストの先頭または末尾に追加するテキスト。それぞれ、返されたテキストがその列の先頭または末尾にないことを示す。
- 返されるテキスト内のトークンの最大数。(
0<n && n<=64
)
.load ./fts5_mecab create virtual table posts using fts5(title, content, tokenize='mecab'); insert into posts values('タイトルです', '本文です。'); insert into posts values('タイトなタイトルです', '本文章は本文です。');
最後の引数が1
のとき。
SELECT snippet(posts, 0, '<mark>', '</mark>', '', 1) FROM posts WHERE posts MATCH 'タイト*';
<mark>タイトル</mark> <mark>タイト</mark>な<mark>タイトル</mark>
最後の引数が2
のとき。
SELECT snippet(posts, 0, '<mark>', '</mark>', '', 2) FROM posts WHERE posts MATCH 'タイト*';
<mark>タイトル</mark>です <mark>タイト</mark>な 表題です
ん? 2
のほうが1
より短くなってる行があるのだが。なにこれ? わからん。
補助関数の結果をソート
- すべてのFTS5テーブルには
rank
という名の特別な非表示列がある - 現在のクエリがフルテキストクエリでない(
MATCH
演算子がない)場合、rank
列の値は常にNULL
- それ以外の場合、フルテキストクエリでは
rank
列にデフォルトで後続の引数なしでbm25()
補助関数の結果が含まれる
- それ以外の場合、フルテキストクエリでは
-- 次のクエリは論理的に同等。2番目の方法は特に高速 -- 特に呼び出し元が以前にクエリを放棄した場合、すべての行が返された(またはクエリが変更された場合)LIMIT句を含めることができる。 SELECT * FROM fts WHERE fts MATCH ? ORDER BY bm25(fts); SELECT * FROM fts WHERE fts MATCH ? ORDER BY rank;
rank
列のデフォルトを任意に設定できる。
rank MATCH 'auxiliary-function-name(arg1, arg2, ...)' rank = 'auxiliary-function-name(arg1, arg2, ...)'
match
, =
の右側は補助関数であるべき。その引数はSQLリテラルでなければならない。
-- 次のクエリは論理的に同等。2番目の方法は高速 SELECT * FROM fts WHERE fts MATCH ? ORDER BY bm25(fts, 10.0, 5.0); SELECT * FROM fts WHERE fts MATCH ? AND rank MATCH 'bm25(10.0, 5.0)' ORDER BY rank;
以下3つは同等。
SELECT * FROM fts WHERE fts MATCH ? AND rank MATCH 'bm25(10.0, 5.0)' ORDER BY rank; SELECT * FROM fts WHERE fts = ? AND rank = 'bm25(10.0, 5.0)' ORDER BY rank; SELECT * FROM fts WHERE fts(?, 'bm25(10.0, 5.0)') ORDER BY rank;
以上、説明おわり。以下、試してみる。
.load ./fts5_mecab create virtual table posts using fts5(title, content, tokenize='mecab'); insert into posts values('タイトルです', '本文です。'); insert into posts values('タイトなタイトルです', '本文章は本文です。'); insert into posts values('表題です', 'タイトな本文です。');
ソートはorder by rank
のほうが早い。order by bm25(fts)
よりも。
select * from posts('タイト*') order by rank;
タイトなタイトルです|本文章は本文です。 タイトルです|本文です。 表題です|タイトな本文です。
重み付けを変えるときはrank
とbm25()
を以下のいずれかのように書くと高速。(rank
を使わずbm25()
単独だと低速)
SELECT * FROM posts WHERE posts MATCH 'タイト*' AND rank = 'bm25(1.0, 5.0)' ORDER BY rank; SELECT * FROM posts WHERE posts = 'タイト*' AND rank = 'bm25(1.0, 5.0)' ORDER BY rank; SELECT * FROM posts('タイト*', 'bm25(1.0, 5.0)') ORDER BY rank;
表題です|タイトな本文です。 タイトなタイトルです|本文章は本文です。 タイトルです|本文です。
列を指定したいなら以下。
SELECT * FROM posts WHERE posts MATCH 'content:タイト*' AND rank = 'bm25(1.0, 5.0)' ORDER BY rank; SELECT * FROM posts WHERE posts = 'content:タイト*' AND rank = 'bm25(1.0, 5.0)' ORDER BY rank; SELECT * FROM posts('content:タイト*', 'bm25(1.0, 5.0)') ORDER BY rank;
表題です|タイトな本文です。
所感
ソートするならrank
を使う。
対象環境
- Raspbierry pi 3 Model B+
- Raspbian stretch 9.0 2018-11-13
- bash 4.4.12(1)-release
- SQLite 3.29.0
$ uname -a Linux raspberrypi 4.19.42-v7+ #1218 SMP Tue May 14 00:48:17 BST 2019 armv7l GNU/Linux
前回まで
- SQLite3学習をはじめよう
- SQLite3学習 SQLiteについて
- SQLite3学習 SQLiteの適切な用途
- SQLite3学習 SQLiteの特徴
- SQLite3学習 SQLiteのクセ
- SQLite3学習 データ型とアフィニティ
- SQLite3学習 演算子の一覧
- SQLite3学習 よくある質問
- SQLite3学習 SQLiteダウンロード&コンパイル
- SQLite3学習 Tclで操作する
- SQLite3学習 ビルドオプション動作確認(SQLITE_ALLOW_URI_AUTHORITY)
- SQLite3学習 面白そうなコンパイルオプション
- SQLite3学習 SQLiteの拡張について
- SQLite3学習 JSON拡張
- SQLite3学習 JSON拡張(json_extract)
- SQLite3学習 JSON拡張(json_each)
- SQLite3学習 JSON拡張(json_tree オブジェクト→行)
- SQLite3学習 JSON拡張(json_tree オブジェクトツリー→行)
- SQLite3学習 JSON拡張(json_tree オブジェクト配列→行)
- SQLite3学習 JSON拡張(json_group_array 行→配列)
- SQLite3学習 JSON拡張(json_group_object 行→オブジェクト)
- SQLite3学習 JSON拡張(json_array_length)
- SQLite3学習 JSON拡張(json_type)
- SQLite3学習 JSON拡張(json_valid)
- SQLite3学習 JSON拡張(json_quote)
- SQLite3学習 JSON拡張(json_array)
- SQLite3学習 JSON拡張(json_object)
- SQLite3学習 JSON拡張(json_patch)
- SQLite3学習 JSON拡張(json_insert)
- SQLite3学習 JSON拡張(json_replace)
- SQLite3学習 JSON拡張(json_set)
- SQLite3学習 JSON拡張(json_remove)
- SQLite3学習 全文検索(FTS5)
- SQLite3学習 全文検索FTSを日本語で使う方法を調べてみた
- 形態素解析MeCabをインストールする
- SQLite3学習 全文検索FTS5のMeCab用トークナイザを実装する
- SQLite3学習 FTS5+MeCabでクエリ構文
- SQLite3学習 FTS5のテーブル作成と初期化