FuelPHPでInnoDBの全文検索を利用してみる

LINEで送る
Pocket

こんにちは。お久しぶりの更新です。

「入力内容に対する類似テキストの検索」を実装する機会があったので、FuelPHPでInnoDBの全文検索を利用してみました。
とはいえFuel自体には全文検索をサポートする操作は特に無いので、だいたい自前で書きました。

手間無くそこそこの精度が出せたので、導入から使用例までメモとして残します。

前提

このような環境での動作確認しています。

名前 バージョン
OS CentOS 6.5(64bit)
MySQL 5.6.20
PHP 5.4.37
Mecab 0.996

最低限必要なのはMySQLのバージョンです。
InnoDBのFULLTEXTインデックスはMySQL5.6.4からのみ利用できます。1

また、InnoDBのFULLTEXTは空白区切りの単語検索しか対応していない2ため、
Mecabを使用して分かち書きして保存・検索します。

MySQL, PHPはインストール済みの前提で話を進めます。
また、Fuelの基本的な知識があり、セットアップは済んでいるものとします。
Mecabは次の環境構築にてインストールしていきます。

環境構築

phpでmecabを使う手順
http://qiita.com/Keech/items/3b51a60c89b9e803b256

こちらの記事を参考に環境構築をしたのですが、自分の環境ではコピペでは動かない箇所があったのでそれを込みでインストールコマンド全てを貼り付けます。

sudoは省略しているため、コマンドが動かない場合はルートになるか適宜sudoの追加をして下さい。

InnoDBの全文検索用の設定はこちらの記事が参考になりました。

MySQL5.6でInnoDBのFULLTEXT INDEXで全文検索する http://www.petitec.com/2013/04/mysql5-6-fulltext-index/

インストールできたか確認しておきます。

こんな感じの出力になっていればOKです。

全文検索用のカラムを追加する

今回は、「既に作成済みのテーブルに全文検索の仕組みを入れる」というシチュエーションで行きます。

記事用のサンプルとして、こんなテーブルが存在するとします。
名前は適当にbooksとでもしておきます。

カラム名
title VARCHAR(50)
content TEXT

このテーブルに全文検索用のカラムcontent_splitedを追加します。

カラム名
content_splited TEXT

Fuelのマイグレーションファイルに直すとこんな感じです。

既にレコードが存在している場合、
追加した分かち書き用カラムにcontentをパースした結果を足して更新する必要があるかと思いますが、この記事では本筋から外れるため割愛しています。

マイグレーションを実行して動けばOKです。

分かち書きオブザーバを定義

booksテーブルを扱うModel_Bookクラスを作成します。
他のテーブルとJOINしたり何だりが楽なので、後々を考えてOrm\Modelを継承して実装します。

content_splitedは検索用のメタ情報のようなものなので、モデル内部で黙字的に更新されるべきです。
コントローラがその存在を気にしなくて良いように内部で完結させます。

DBへの INSERT前UPDATE前 に、contentカラムのデータを分かち書きして、
content_splitedカラムへ代入するオブザーバを作成します。

モデルの定義としてはこんなイメージです。

オブザーバのコードはやや長くなるのでgistに上げました。
環境構築時の設定で、2文字未満の単語は検索する際に無視する設定にしているので、2文字に満たない単語は保存しない処理が入っています。

FuelPHPでMecabを使用して分かち書きするオブザーバ
https://gist.github.com/Leko/6c98685bdb048b949392#file-wakati-php

Creating – Observers – Orm Package – FuelPHP ドキュメント http://fuelphp.jp/docs/1.7/packages/orm/observers/creating.html

分かち書きした結果を保存する

このオブザーバを使用することで、contentプロパティに文章を指定して更新すれば、勝手に分かち書きした結果がcontent_splitedに格納されるようになります。

こんな感じです。

全文検索用のメソッドを作成する

お待ちかねの本題です。全文検索のメソッドを実装します。

全文検索はMATCH(カラム名) AGAINST ('+ほげ +ふー +ばー' IN BOOLEAN MODE)のように扱います。
とてもざっくりした説明なので、詳しい説明は公式のリファレンスを参照して下さい。

Ormパッケージのクエリビルダは涙がでるほどイケてないので色々と気に食わない箇所がありますが、
今回はサンプルなので動けばいいやということで。実装する際にはいい感じにメソッド化したほうが良いと思います。

先ほどのgistに上がっているsimilarBooksメソッドがこれにあたります。

本の内容(contentカラム)まで検索キーワードに含めるとノイズが多すぎるので、タイトルだけを検索キーワードにしています。

(言い訳)カラム名と検索用テキスト、ふるい落とす文字数を渡してwhereの中身を1個のDatabase_Expressionのインスタンスとしてwhereに渡したかったのですが、
まだ理解が浅いのかそもそもできないのか、うまくいかなかったので汚い書き方になっています。

全文検索用のメソッドを作成する

先ほどの青空文庫の文章を入れて、例えば

「幸福で愉快な陸軍が飛行機を奪われた」

など本文中に直接は出てこないが本文中の単語を含んだ文で検索をかけると、検索に引っかかります。

まとめ

駆け足で説明していきましたが、いかがでしたでしょうか。
「全文検索」って言葉は知っていたけど実装したことはなかったので、「こんな言葉で引っかかるのか!すげえ!」とデバッグしながら一人で盛り上がってました。

mecabの説明を一切出さずモデルの裏側に隠してしまったのですが、
せっかく環境を作ったので、次はmecabと係り受け解析のライブラリとか使って文章の要約でもしてみようかなと思います。

LINEで送る
Pocket