設定が変更できないDBで日本語でfull text searchを実現する

執筆日: 更新日:

nnahitoのサーバをなんとなくレンタルサーバに移動させました。
そしたらDBの設定がいじれなくて、full text searchでn-gramが使えず、記事が一切マッチングしなくなりました。
それを直したお話。

そもそもngramってなに?

文字をn個の塊で考えることです。

例:今日は渋谷に行く

今日
日は
は渋
谷に
に行
行く

こんな感じです。
これはngramのうちの2gram(bi-gram)です。
N=2ですね。

tri-gram(N=3)だと、

今日は
日は渋
は渋谷
渋谷に
谷に行
に行く

ですね。

これを使うと、日本語でもある程度マッチングしますが、それが使えなかったというお話。

解決策:検索用のテーブル/カラムを作る

じゃあどうするか。
検索用のカラムを作ってやればいい。

MySQLのデフォルトのfull text searchは半角スペース区切りで文字列を区切り、Indexをリスト化します。
英語だとわかりやすいですよね?

I have an apple

I have an apple

と、このようにスペースでわけます。

しかし、日本語はスペース区切りの文法じゃありません。

私はりんごを持っています

りんご 持っ ます

はい、むずいですよね。
これを解決するのが形態素解析です。

MeCabや、GiNZAなどが有名ですよね?

ただ今回はレンタルサーバ。
Pythonをぶっこむのも一苦労です。

じゃあどうするか。

精度は落ちますが、TinySegmenter PHPを使います。
文字を重み付けしていき、単語に区切るという、すげー発想です。天才じゃねーか?
なので、辞書がなくても動きます。やべーよ。

ということでこれを利用し、文章を形態素解析し、スペース区切りにしてあげます。
そしてそれをfull text searchのindexが貼られたカラムに突っ込み、検索の際にそちらを参照します。

そして検索の際に入力されたテキストも半角スペース区切りにしてSQLにぶっこみます。

テーブルのマイグレーションはこんな感じです。

Schema::create('article_search_index', function (Blueprint $table) {
		$table->unsignedBigInteger('article_id')->comment('articleテーブルのPK');
		$table->longText('search_content')->comment('検索用のデータに整形したテキスト');

		$table->dateTime('created_at')->useCurrent()->comment('レコード作成日時');

		$table->unique('article_id');
		$table->fullText('search_content');

		$table->comment('記事の検索用に、スペース区切りに文字列を区切ってfulltext searchに対応する情報を保存するテーブル');
});

検索SQLはこんな感じになります

SELECT *
FROM article_search_index
WHERE MATCH(search_content) AGAINST ('私 りんご' IN NATURAL LANGUAGE MODE);

これでそこそこの精度の日本語検索ができるようになりました。
ソースはこのブログの検索機能です。

以上


[PR] おすすめの本

Nな人

この記事を書いた人

Nな人(えぬなひと)

Laravelが大好きなPHPerで、WEBを作るときはLaravelを技術スタックに絶対推すマン。 PHP、Pythonと、昔はperlを書いていたP言語エンジニア。 最近はNimを書いたりしています。