Node.jsとSQLiteで全文検索・一歩前

データベースに文字列を保存して、全文検索できるようにしたい。 でも、マネーレスでクラウドのデータベースサービスは使いたくない。 そんな悩みに答えるかもしれないSQLite。 今ではAWSのLambdaにdbファイルをデプロイして、使う人も出てきている。

AWSのLambda + sqlite でサーバレスRDSもなしでAPI実装
実サービスでAWS Lambda内でsqlite3を使った話

このようにお手軽なSQLiteだが、実は全文検索も行うことができる。 SQLite全文検索しようとすると、fts(Full Text Search)という拡張機能を有効にしてコンパイルする必要があるが、npmならこのコンパイルも簡単にできる。

fts機能を有効にしたSQLiteのインストール

export CFLAGS="-DSQLITE_ENABLE_FTS3_PARENTHESIS"
npm install sqlite3 --build-from-source

node-sqlite3の導入

npm install https://github.com/mapbox/node-sqlite3/tarball/master

テーブルの作成

このライブラリを使えば、とても簡単にデータベースを操作できる。 例えば、テーブルを作成するコードは以下のようになる。 a,b,cという3つのカラムを持つテーブルを作成する関数である。 クエリ文のVIRTUALというのは、仮想テーブルを作成するためのもの。 SQLite全文検索するためには、普通のテーブルではなく、専用の仮想テーブルを作成する必要がある。

import sqlite3 from "sqlite3"

const createTable = (tableName) => {
    const db = new sqlite3.Database('test.db')
    db.serialize(() => {
        let queryString = "CREATE VIRTUAL TABLE " + tableName +  " USING fts4('a', 'b', 'c')"
        db.run(queryString)
        db.close()
    })
}

createTable("test")

テーブルにデータを挿入

const insertData = (tableName) => {
    const db = new sqlite3.Database('test.db')
    db.serialize(() => {
        let prepare = db.prepare("INSERT INTO " + tableName + " VALUES (?, ?, ?)")
        const insertDataList = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
        insertDataList.forEach((value, index, array) => {
            prepare.run(value, (err) => {
                if(err){
                    throw err
                }
            })
        })
    })
    db.close();
}

insertData("test")

テーブルのデータを見る

const showTableData = (tableName) => {
    const db = new sqlite3.Database('test.db')
    db.serialize(() => {
        db.each("select * from " + tableName, (err, row) => {
            console.log(row)
        })
    })
    db.close();
}

showTableData("test")
/*
{ a: 1, b: 2, c: 3 }
{ a: 4, b: 5, c: 6 }
{ a: 7, b: 8, c: 9 }
*/

全文検索機能が有効になったことを確認

本題の全文検索であるが、MATCH句を使用して行う。 以下の検索は、検索対象が行に存在すれば、行全体を返してくれる。 今回はMATCH句が使えるようになり、全文検索機能が有効になるところまでを確認しておく。

const searchFullText = (tableName, searchWord) => {
    const db = new sqlite3.Database('test.db')
    db.serialize(() => {
        let queryString = "SELECT * FROM " + tableName + " WHERE " + tableName + " MATCH " + "'" + word + "'"
        db.each(queryString, (err, row) => {
            if (err) {
                throw err
            }
            console.log(row)
        })
    })
    db.close();
}

searchFullText("test", 1)
//{ a: 1, b: 2, c: 3 }
searchFullText("test", 4)
//{ a: 4, b: 5, c: 6 }
searchFullText("test", 7)
//{ a: 7, b: 8, c: 9 }

まとめ

ここから日本語の全文検索ができるようにするには、検索アルゴリズムをきちんと考えてやらねばならない。 関連キーワードは、部分一致、前方一致、N-Gram分かち書きMecabなど。 マネーで何事も解決できる人は、こんなことやらずともAWSのElasticSearchやAlgoliaを使えばよろし。