九保すこひ@フリーランスエンジニア|累計300万PVのブログ運営中
さてさて、これは皆さんも感じていると思うのですが、プログラムで何かをつくるとき、解決方法は1つじゃないことが多いですよね。
送信だけとっても、HTTP
送信に加えてAjax
での送信もできますし、サーバーだってクラウドを使わずVPS
を使って実装している場合も多いでしょう。
ちなみに、どの技術を使うかは「クライアントさんの状況」によって変わってくると思いますので、プログラマは「クライアントさんの希望に沿う」というコンサルティング的な要素も必要になってくると考えています。
そして、そんな選択肢の中からチョイスするものには「DBテーブルの検索」も含まれていると思います。
つまり、長い文章を高速検索できるようになる、
全文検索(Full-Text Search)
ですね。
全文検索とは、簡単に言うと「文章を前もって単語に分けておいて、その単語をから高速に検索する」という技術です。
例えば、「マトリッツォ」というキーワードで検索をする場合、通常だと全てのデータに「マトリッツォが入ってる?」の繰り返しをしないといけません。
しかし、全文検索にしておくと、「マトリッツォが入ってるのはココとココのデータです」というショートカットが用意されるので、検索が早くなるというわけです。
そこで❗
今回はLaravel + MySQL + mecab
でこの「全文検索」を実装してみたいと思います。
ぜひ参考になりましたら嬉しいです😊✨
※ なお、実際につくる機能は、「文書管理システム」を想定しています。
「マトリッツォって
クマが泡吹いて倒れてるように
見えません❓」
開発環境: Laravel 8.x、Ubuntu 20.04、MySQL 8.0
目次
mecab 本体をインストールする
まずはmecab
本体のインストールです。
ターミナルを開き、以下のコマンドを実行してください。
sudo apt remove mecab libmecab-dev mecab-ipadic-utf8
これでmecab
がインストールされました。
なお、コマンドラインでmecab
を使う方法は過去記事をご覧ください。
📝 参考記事: mecab本体のインストール
mecab プラグインをインストールする
次に、mecab
がMySQL
で使えるようプラグインをインストールします。
※ ちなみに、mecab
プラグインのインストールはどうやら環境によってパスや設定方法が違うようです。そのため、今回は私が最終的に成功した方法をご紹介しますが、おそらくUbuntu 20.04
でしたらそのままでOKじゃないでしょうか。
準備
まず、mecab
の設定ファイルは/etc/mecab
になるのですが、どうやらこの位置にあるとmysql
から実行ができないようなので、このファイルをコピーします。
以下のコマンドを実行してください。
sudo cp /etc/mecabrc /etc/mysql/mecabrc
そして、MySQL
がこのコピーされたファイルを参照できるよう設定します。
以下のコマンドを実行してください。
sudo vi /etc/mysql/my.cnf
そして、以下の部分を追加します。
[mysqld] loose-mecab-rc-file=/etc/mysql/mecabrc innodb_ft_min_token_size=2
※ innodb_ft_min_token_size
は、「単語に区切るときに何文字以上にするか」を決定します。つまり今回のケースでは2文字以上のものを使うという意味になります。
では、設定が終わったらMySQL
を再起動します。
以下のコマンドを実行してください。
sudo systemctl restart mysql
インストールを実行
続いて、プラグイン本体のインストールです。
コマンドでMySQL
にログインしてください。
mysql -u root -p
※ ユーザー名は適宜変更してください。
すると、パスワードを聞かれるので入力してエンターキーを押します。(実はターミナルのパスワードでもコピペが使えますよ👍)
すると、MySQL
にログインできるので以下のコマンドを実行してください。
INSTALL PLUGIN mecab SONAME 'libpluginmecab.so';
※ 最後のセミコロンも忘れずに❗
これでmecab
のプラグインがインストールできました。
では、念のためそのままの状態で以下のコマンドを実行してください。
SHOW PLUGINS;
インストールされていれば以下のように表示されます。
データベースにテーブルをつくる
では、続いてLaravel
側の作業になります。
以下のコマンドでモデル&マイグレーションを作成してください。
php artisan make:model Document -m
すると、モデルとマイグレーションのファイルが自動的に作成されるので、中身を以下のように変更します。
database/migrations/****_**_**_******_create_documents_table.php
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateDocumentsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('documents', function (Blueprint $table) { $table->id(); $table->string('title')->comment('タイトル'); $table->text('body')->comment('本文'); $table->timestamps(); }); // 全文検索:設定を追加 \DB::statement('ALTER TABLE `documents` ADD FULLTEXT INDEX document_body_index (body) WITH PARSER mecab'); } /** * Reverse the migrations. * * @return void */ public function down() { // 全文検索:設定の削除 Schema::table('documents', function($table) { $table->dropIndex('document_body_index'); }); Schema::dropIfExists('documents'); } }
なお、Laravel
には全文検索用のメソッドは存在していないので、SQL文を直書きで実行させています。
テストデータをつくる
次に、開発しやすいようにテストデータを追加するFactory
ファイルを作成します。
以下のコマンドを実行してください。
php artisan make:factory DocumentFactory
自動でDocumentFactory.php
が作成されるので中身を以下のようにします。
database/factories/DocumentFactory.php
// 省略 public function definition() { static $number = 1; return [ 'title' => 'テストタイトル '. $number++, 'body' => $this->faker->realText() ]; }
【⚠ご注意】なお、faker
の言語設定を日本語にしていない場合は、config/app.php
内にあるfaker_locale
を以下のように変更しておいてください。
'faker_locale' => 'ja_JP',
では、作成したFactory
はそのままでは動きませんので、Laravel
側へセットします。
database/seeders/DatabaseSeeder.php
<?php namespace Database\Seeders; use App\Models\Document; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { /** * Seed the application's database. * * @return void */ public function run() { // 省略 Document::factory()->count(25)->create(); } }
では、これで設定は完了しましたので以下のコマンドを実行して、DBを再構築してみましょう。
php artisan migrate:fresh --seed
すると、実際のテーブルは次のようになりました。
コントローラーをつくる
では、ここからは実際にブラウザで操作する部分をつくっていきます。
まずはコントローラーです。
以下のコマンドを実行してください。
php artisan make:controller DocumentController
すると、ファイルが作成されるので中身を次のように変更します。
app/Http/Controllers/DocumentController.php
<?php namespace App\Http\Controllers; use App\Models\Document; use Illuminate\Http\Request; class DocumentController extends Controller { public function index() { return view('document.index'); } public function list(Request $request) { $sql = 'MATCH(body) AGAINST(?)'; $params = [$request->keyword]; return Document::whereRaw($sql, $params)->get(); } }
※ ちなみに、脆弱性になりますので、以下のように直接SQL文にキーワードを埋め込むような書き方はしないでください。?
を使ったプリペアドステートメントが安全です。
// ⚠ これは危険な例です!ダメ。ゼッタイ😫 $sql = 'MATCH(body) AGAINST("'. $request->keyword .'")';
ビューをつくる
では、先ほどコントローラーのindex()
でセットしたビューをつくります。
以下のファイルを作成してください。
resources/views/document/index.blade.php
<html> <head> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css"> </head> <body> <div id="app" class="p-5"> <div class="row"> <div class="col-3"> <input type="text" class="form-control" placeholder="キーワードを入力" v-model="keyword"> </div> <div class="col-3"> <button type="button" class="btn btn-primary" @click="search">検索する</button> </div> </div> <div class="row mt-3 bg-light" v-if="documents.length"> <div class="col-12" v-text="documents"></div> </div> <div class="row mt-3 bg-light" v-else> 検索データが見つかりません。 </div> </div> <script src="https://unpkg.com/vue@3.0.11/dist/vue.global.prod.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script> <script> Vue.createApp({ data() { return { keyword: '', documents: [] } }, methods: { search() { const url = '{{ route('document.list') }}?keyword='+ this.keyword; axios.get(url) .then(response => { this.documents = response.data; }); } } }).mount('#app'); </script> </body> </html>
内容としては、キーワードを入力し、Ajax
で送信するというシンプルなものになっています。
ルートをつくる
そして最後にルートです。
以下を追加してください。
use App\Http\Controllers\DocumentController; // 省略 Route::get('document', [DocumentController::class, 'index'])->name('document.index'); Route::get('document/list', [DocumentController::class, 'list'])->name('document.list');
これで作業は完了です❗
お疲れ様でした😊
テストしてみる
では、「http://******/document」にブラウザでアクセスすると以下のような検索フォームが表示されます。
では、「仕事」と入力して検索してみましょう。
すると・・・・・・
はい❗
「仕事」が含まれたデータを取得することができました。
では、ここで「仕事」を分割して「仕」で検索してみましょう。通常の検索ならこれでもデータが取得されますが、全文検索ではどうでしょうか・・・???
はい❗
データを取得することができませんでした。
これは、2文字以上でインデックスをつくっているからですね。(もちろん対象が2文字以上なので、「スコップ」や「ジョバンニ」でも検索できます)
成功です😊✨
企業様へのご提案
今回の「全文検索」機能を使うと、長い文書を保存したとしても通常の検索より高速にデータを探し出すことができ、業務の効率化につなげることができます。
もしこういった文書管理システムをご希望でしたら、ぜひお問い合わせからお気軽にご連絡ください。
どうぞよろしくお願いいたします。m(_ _)m
おわりに
ということで、今回はLaravel
で全文検索をつくってみました。
なお、全文検索のデメリットとしては、「文章を単語化する際に、もし登録された単語がなければ検索には引っかからない」というものがあります。
つまり、新語や流行語には対応しにくいということになります。
とはいえ、例えば「東京スカイツリー」というテキストは、以下全てのキーワードでも検索可能なので、ある程度の融通は効かせられるかと思います。
- 東京
- スカイ
- ツリー
- スカイツリー
- 東京スカイ
- 東京スカイツリー
ぜひ皆さんも全文検索を有効活用してみてくださいね。
ではでは〜❗
「メカブが食べたくなってきた(笑)」