フリーウエア利用2 SQLite その4 全文検索・FTS5
SQLiteを使って全文検索(FTS、Full Text Search)を行うプログラムです。
SQLiteには、FTS5という全文検索機能が標準的に実装されています、主な情報は、SQLiteのこちらのFTS5のページです。
英語のように分かち書きされる文章を対象にしていますが、日本語はそのような単語に分けて書かないので、そのままでは使用できません。
ここでは、文章をMeCab(形態要素分析エンジン)を使って、あらかじめ分かち書きすることで利用します。MeCabについてはこちらを参考にしてください。
※この方法以外にもSQLiteのトークナイザーを日本語の分かち書きに分解しするものに変更することでも可能です。
全文検索の対象例として、全文検索・LIKE版と同じく日本国憲法を使います。前文、条文ごとに、章・条・連番をつなぎ合わせたインデックスを付加しています。文章(前文、条文)部分は、こちらで分かち書きに置き換えています。
<作成後>

データベース仕様、テーブル名、ファイル名(デフォルト)などのデータベースの定義や共通関数を「FTS_SQLITE.h」にまとめています。
「FTS_5.c」に、メニューからデータベースの作成(create_db)、インデックス・条文での検索( search_data)が選べます。
SQLiteでは、「TEXT」項目のコードがUTF8ですので、S-JIS<->UTF8変換のため、NKF32を使用しています。 (コンパイル時にヘッダ、ライブラリが必要、実行時にはdllが必要)
全体を表示すると長くなるので「FTS_SQLITE.h」のDB定義部分と「FTS_5.c」の検索部分を示します。ここで使用したソースコード、実行プログラム、DB、DBに読み込ませたテキストデータはこちらです。
@「FTS_SQLITE.h」、定義部分
/ ===================================================================
// fts_sqlite.h SQLite・全文検索プログラム 共通関数・設定
// K.Nakamura 2023/09/03 LIKE
// 2023/10/28 FTS5
// 1)FTS_LIKE 全文検索(LIKE版)
// @構造体定義 ASQLite 諸定義
// B関数(オープン・作成:open 、構造体にセット:set)
// 2)FTS5 全文検索(FTS5版)
// @構造体定義 ASQLite 諸定義
// B関数(オープン・作成:open 、構造体にセット:set)
// ===================================================================
// ---------------- データ構造体定義 ------------------------------
// FTS5 データ インデックスキー、分かち書きデータ
typedef struct
{
char *index_key; // 1 インデックス TEXT
char *fts_data; // 2 分かち書きデータ TEXT
} FTS_5_REC;
// ------------ SQLite 全文検索 データベース定義 fts5 用 --------
char *FTS_5_tname = "FTS_5";
char *FTS_5_table = "index_key , fts_data";
char FTS_5_fname[500] = "fts_5.db";
char FTS_5_ORG_fname[500] = "input2.txt";
sqlite3 *fts_5_db;
sqlite3_stmt *fts_5_stmt_get ;
sqlite3_stmt *fts_5_stmt_insert ;
char *fts_5_sql_insert = "INSERT INTO FTS_5 values( ?1 , ?2 )";
// =========================================================================
// Name : int fts_5_open(void)
// Usage : QA用のデータベースをオープン、なければ作成(+テーブル作成)する。
// Parameter: なし
// Return : 0 :OK
// ========================================================================
int fts_5_open(void)
{
int rc;
// データベースファイルを新規生成 or オープン
rc = sqlite3_open_v2( FTS_5_fname, &fts_5_db , SQLITE_OPEN_READWRITE , NULL);
if(rc == SQLITE_OK ) fprintf(stdout, "Open:[%s]\n", FTS_5_fname );
else{
rc = sqlite3_open_v2( FTS_5_fname, &fts_5_db,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
if(rc == SQLITE_OK){
fprintf(stdout, "Create:[%s]\n", FTS_5_fname );
sprintf(sql_statement, "create virtual table %s using fts5(%s)", FTS_5_tname
, FTS_5_table);
rc = sqlite3_exec( fts_5_db , sql_statement, NULL, NULL, &error_message);
if(rc == SQLITE_OK) fprintf(stdout, "Table[%s] \n", FTS_5_tname);
else{
fprintf(stdout, "Table NG[%s] \n", error_message);
return -1;
}
}
else{
fprintf(stdout, "Ceate Error[%s][%s]\n", FTS_5_fname , sqlite3_errmsg( fts_5_db ));
return -1;
}
}
return 0;
}
A検索部分です。
検索のSQL文は、「SELECT * FROM FTS_5 WHERE fts_data match 'XX XX';」のようになります。
// =========================================================================
// Name : int search_data( int)
// Usage :SQLite・データベースを検索して、結果を表示する。
// Parameter: 1:インデックス,2:データ文
// Return : なし
// =========================================================================
int search_data( int id )
{
int rc ;
char temp_s[300];
char temp_utf[900];
FTS_5_REC fts5;
int count;
char temp_statement[100];
// fts_5のオープンまたは作成
rc = fts_5_open();
if(rc != 0) {
fprintf(stdout,"SQLite DB error%s\n", FTS_5_fname);
return -1;
}
fprintf(stdout , "-------------------------------------------------\n");
if( id == 1) fprintf(stdout ,"検索する語(インデックス)を入力してください。:");
else fprintf(stdout ,"検索1 MATCH '□□□□'; □□□□部分を入力してください:");
strcpy(temp_s ,"");
fgets(temp_s, 256 , stdin);
temp_s[strlen(temp_s)-1] ='\0';
fprintf(stdout, "\n");
sprintf(sql_statement, "SELECT * FROM FTS_5 WHERE ");
SetNkfOption("-Swx");
NkfConvert( temp_utf, temp_s );
if( id == 1) sprintf(temp_statement, "index_key like \'%%%s%%\' ",temp_utf);
else sprintf(temp_statement, "fts_data match \'%s\' ",temp_utf);
strcat(sql_statement, temp_statement);
strcat(sql_statement, ";");
fprintf(stdout, "\n%s\n", sql_statement);
sqlite3_prepare_v2(fts_5_db, sql_statement , -1, &fts_5_stmt_get, NULL);
count=0;
fprintf(stdout , "-------------------------------------------------\n");
while(1){
// 1レコードごと読み込み
if(kbhit()!=0)break;
rc = sqlite3_step(fts_5_stmt_get);
if(rc != SQLITE_ROW){
fprintf(stdout, "--------------------\n");
fprintf(stdout, "検索終了(%d件)しました。\n",count);
break;
}
else{
count++;
fts_5_set(&fts5);
fprintf(stdout, "--------------------\n");
fprintf(stdout, "%d 件目 [%s]\n",count ,fts5.index_key );
fprintf(stdout, "【内容】:%s\n",fts5.fts_data);
}
}
rc =sqlite3_reset(fts_5_stmt_get);
rc =sqlite3_finalize(fts_5_stmt_get);
fprintf(stdout , "-------------------------------------------------\n");
rc = sqlite3_close(fts_5_db);
return 0;
}
・コンパイルには、sqlite、NKFのヘッダ(.h)とライブラリ(.lib)が必要です。取得方法は、
記載していますので、各自で取得をお願いしいます。
SQLiteはこちら、NKFはこちらのページを見てください。

・全文検索のSQLiteデータベースを元のデータを読み込んで作成します。
・各レコードはインデックス項目、データ項目(条文・分かち書き)からなります。テーブルは、FTS5用で、「create virtual table FTS_5 using fts5( INDEX_KEY , FTS_5);」のように指定します。ここで、「FTS_5」はテーブル名です、「using fts5( INDEX_KEY , FTS_5)」は、テーブル定義を意味します。テーブルの各項目はテキストとなります。また、プライマリーキーは指定できません。

検索結果1(1語句、複数の語句指定、ワイルドカード指定) |
・Bがデータ項目(条文)を検索して一致する語句を含むレコードが表示されます。
・Cが項目を複数指定ができ、すべてが含まれている行が表示され、AND条件と同じです。
・Dがワイルドカード(*)を指定したものです。
B検索(1語句)

C複数語(AND)

Dワイルドカード

検索結果2 ブーリアン演算(AND、OR、NOT 大文字で記述する) |
・AND、OR、NOTが指定できます、語句でもクエリ間の演算もできます。ANDは両方が含まれるものでつけなくてもANDで検索します。
EOR条件

検索結果3(FTS データ内容) ブーリアン演算(NEAR 大文字で記述する) |
・指定する語句の距離・・・近さを指定できます。
・書式は「NEAR( 語A 語B , N):ここでNは数字で、語句と語句の間の形態素の数を意味していて、この場合、語Aと語Bの間にN個以下の場合が抽出されます。数字を省略するとデフォルトで10が使われます。
・NEAR(語A 語B , 0)とすると、連続する場合のみが選ばれます。順序は指定できないので、「語A 語B」も「語A 語B」も対象になります。
・いくつか行った結果ですが、「、」はカウントされないようです。
FNEAR

検索結果4(FTS データ内容)
ここであげたプログラムを使わないでも検証ができます、「sqlite3.exe」とこちらに含まれる「FTS_5.db」があれば試すことができますので、SQLが使える方はそちらのほうが早くできます。
検証したものがGとなります。
・表示順序は、指定しない場合には登録順に出てきますが、重要性を加味した表示順も指定できます。
・「order by BM25(fts_5)」のように付加すれば、より重要なものから並びます。bm25()については、説明できません、Wikipediaなどを調べてください。
・「order by rank」も使えるとあります、BM25()を使っても同じとSQLiteのサイトに書かれています、rankを使うほうが処理が速いともあります。
GSQLite3.exe


|