laravel
全文検索
FULLTEXT

Laravelで単一入力値による複数カラムに対する曖昧検索の実装方法、他

新規登録や更新でMassAssignmentException

Laradockで簡単アプリ作成しようとしたら、速攻でDB周り/日本語問題で躓いたけど、チャチャッと解決 - Qiitaに続けて、サンプル通りに実装してから動作確認で、新規作成や更新をしてみたら...

MassAssignmentExceptionのエラー

結果的には、モデルで指定するprotected $guardedprotected $fillableの内、$guardedを指定していたけれど、スペルをミスっていたというオチ
Orz

カラム追加などのマイグレーションのお作法

とりあえず、CRUDの一通りができるアプリができたので、カラム追加などの拡張をする前に、そのあたりのお作法について、お勉強

カラム変更の前に、ライブラリの追加が必要

composer require doctrine/dbal

あと、マイグレーションファイル内に記載されているclass名に関する留意点。

テーブル定義を変更する場合は、class名が被らないようにした方がよいらしい。
コマンド言うと、マイグレーションファイルを作成する際に指定する {migration_class_name} の部分。

php artisan make:migratoin {migration_class_name} --table=users

これが、プロジェクト全体で被らないようにした方がよい、とのこと。

都度、php artisan migrate しているときには発生しなくて、新規環境で、一括でmigrateを実行したときに発生するっぽいので、しばらくして、新規でDB作り直すかぁ、とかならないと気づかないってことかしら...

だとすると、開発中は、カラムの抜き差し等が頻繁に発生しそうなので、初回リリースのタイミング、もしくは、ある程度、テーブル構造が固まったタイミングで、一度、まっさらな新規作成のマイグレーションファイルを作成するようにして、それ以降、変更管理をすると良いかもしれないなぁ。

つまり、初回リリース時/ある程度、テーブル構造が固まった時に、開発中に行った追加変更のマイグレーションをすべてマージして、一つのマイグレーションファイルに集約するようなイメージ

単一入力値による複数カラムに対する曖昧検索の実装

で、この作業のゴールは、複数カラムに対する全文検索をどう実装すれば良いかを知ることだったので、ざっくりの実装方法を理解できたところで、全文検索についてクリッピング

アナログな実装

標準的な機能を利用して、実装するなら、検索対象のカラムの数だけ、or検索を指定すれば良さそう。

Model::where('target_column1', 'LIKE', "%$query_keyword%")->orWhere('target_column2', 'LIKE', "%$query_keyword%")

MySQLの生成列とFULLTEXTで実装

もっと今時な?実装をするなら、MySQLの5.7.6から利用できるようになったgenerated columns、日本語訳だと、生成列とか仮想列とか呼ばれているカラム定義で、これにFULLTEXTインデックスを定義すれば、行けそう

留意点としては、generated columnを指定する際、生成列のデータの持ち方が初期値はVIRTUALなので計算後の値が格納されず、FULLTEXTのインデックスを設定できないため、STOREDを設定する必要がある

具体的には、マイグレーションファイルに、以下のような記述をするのかな。
(参考ページのサンプルを丸っと転機して少し修正w)

# カラム定義
Schema::create('sales', function (Blueprint $table) {
  $table->string('name');
  $table->double('price_eur');
  $table->integer('amount');
  $table->double('total_eur')->storedAs('price_eur * amount');
  $table->double('total_usd')->storedAs('total_eur * xrate');
  $table->double('xrate');
});

# ALTER TABLE か CREATE INDEX で追加設定
DB::statement('ALTER TABLE `sales` ADD FULLTEXT INDEX `ft_idx_total_eur` (`total_eur`) WITH PARSER ngram');
DB::statement('CREATE FULLTEXT INDEX `ft_idx_total_eur` ON `sales` (`total_eur`) WITH PARSER ngram');

generated columnsは、サブクエリに対応していないことによる影響の有無を、事前に検討しておいた方が良さそう

ちょっとクリティカルな問題として、性能に難あり、らしい

性能に課題はありそうだが、数百〜数千レコーデあれば、FULLTEXTを利用して、Laravelから全文検索する方法

いずれも、MATCH ... AGAINST ...で検索する方法

Laravelが提供している全文検索の機能で実装

LaravelのScoutのことがよくわからなかったんだけど、これ読んで、たぶん、理解できた。

全文検索のためのインデックスは外部で処理して、フレームワークからは気軽にメソッドで検索できるようにしたものってことかな

対象カラムは、各モデルごとにtoSearchableArrayメソッドで定義できるってことかな...。

ただ、これも複数のテーブルにまたがるような検索や、カラムの値はIDでしか持っていないような項目を対象にした場合は、工夫が必要そう...。

おまけ

ちょっと話が逸れるけど、結合先のテーブルを検索条件に加えるという話も出てきそうなので、メモ