【Laravel】テーブルの構成変更にキー制約が絡むMigrateファイルの作り方

前回に引き続きLaravelのMigrateについてです。
blog.websandbag.com

ご覧いただいている方々は、次のような経験がないでしょうか?

仕様変更で、以前作ったテーブルの型や桁数を変更があった。
その対象のカラムは外部キー制約がされていて、関連するテーブルも一緒に直さないといけない

稼動前のシステムであれば、テーブル作成時のMigrateファイルを直接書き換えてれば解決します。
しかし、稼動中であったり、チームで開発している場合は得策とは言えません。

さて、
今回はキー制約を解除しつつ、データの型を変える方法について解説します。

イントロダクション

この記事で得られること

  • テーブル構成を変更するMigrateした時、「Foreign key constraint is incorrectly formed」エラーで止まった時の解決方法がわかる。

環境

種類 バージョン
laravel 5.5

注意

稼働中の案件で動かす場合は、データベースのバックアップをする事をお勧めします
既存のデータが、設定したい型と一致しない場合取り返しがつかない事になる可能性があります。

要件

目的は、nameカラムを整数型(integer)から文字型(varchar)に変更する事です。
今回は次のような構成を想定した説明になります。

codesテーブル

  • name:整数型カラム。
    • codesテーブルのユニークキー制約

code_detailsテーブル

  • name:整数型カラム
    • codesテーブルのnameカラムを外部キーに指定している。

対処方法

一個一個紐解いてあげる事が重要です。

  1. 対象テーブルの外部キー制約を解除する
  2. 外部キーの参照元のテーブルのカラムのユニークキー設定をしているテーブルのキー制約を解除する
  3. 外部キーの参照元のテーブルのカラムのデータの型を変更する。
  4. 外部キーの参照元のテーブルのカラムのユニークキー設定をしているテーブルのキー制約を設定する。
  5. 対象テーブルの対象カラムのデータの型を変更する。
  6. 対象テーブルの外部キー制約を設定する。

今回、構築時と解体時のそれぞれの設定について触れます。

構築時の設定

Migrate実行時に実行する設定です。

$ php artisan migrate

Step1 対象のテーブルの外部キー制約解除

対象テーブルに設定している外部キー制約の解除は、dropForeignで実行できます。 次のように設定します。

dropForeign(<対象テーブル名>_<対象カラム>_foreign)

私は試していませんが、公式ドキュメントでは次のように省略できると書いています。
dropForeign(['<対象カラム>'])

設定内容

public function up()
{
    Schema::table('code_details', function (Blueprint $table) {
        $table->dropForeign('code_details_name_foreign');
    });
}

Step2 外部キー参照元のテーブル設定

外部キーで参照しているカラムは、ユニーク規制をしています。
解除したのちデータの型を設定します。

ユニークキー制約の解除は、dropUniqueで実行できます。
次のように設定します。

dropUnique(<対象テーブル名>_<対象カラム>_unique)

設定内容

public function up()
{
    Schema::table('code_details', function (Blueprint $table) {
        $table->dropForeign('code_details_name_foreign');
    });
    Schema::table('codes', function (Blueprint $table) {
        $table->dropUnique('codes_name_unique');
        $table->string('name', 7)->unique()->change();
    });
}

Step3 対象のテーブルの外部キー制約設定しなおし

最後に、対象テーブルの対象カラム型を変更して、外部キーに指定直します。

設定内容

public function up()
{
    Schema::table('code_details', function (Blueprint $table) {
        $table->dropForeign('code_details_name_foreign');
    });
    Schema::table('codes', function (Blueprint $table) {
        $table->dropUnique('codes_name_unique');
        $table->string('name', 7)->unique()->change();
    });
    Schema::table('code_details', function (Blueprint $table) {
        $table->string('name', 7)->change();
        $table->foreign('name')->references('name')->on('codes');
    });
}

解体時の設定

次に、DB解体時に実行する設定です。

$ php artisan migrate:reset

UPの要領で、前の状態に戻します。
手順自体は同じなので手順は割愛しますが、型指定時に前の型に戻してあげれば解決します。

設定内容

public function down()
{
    Schema::table('code_details', function (Blueprint $table) {
        $table->dropForeign('code_details_name_foreign');
    });
    Schema::table('codes', function (Blueprint $table) {
        $table->dropUnique('codes_name_unique');
        $table->unsignedInteger('name')->unique()->change();
    });
    Schema::table('code_details', function (Blueprint $table) {
        $table->unsignedInteger('name')->change();
        $table->foreign('name')->references('name')->on('codes');
    });
}

Tips

キー制約と同時に、外部キー制約付きのテーブルを追加する場合

今回の例では、制約を変えるだけでしたが、一緒に外部キー制約があるテーブルを追加する場合もあります。
その場合は、処理の順番を意図的に変える必要があります。

public function up()
{
    Schema::table('code_details', function (Blueprint $table) {
        # 省略
    });

    # 制約を変えた後に、テーブルを作成。
    Schema::create('new_table', function (Blueprint $table) {
        $table->increments('id');
        $table->string('name', 7);
        $table->foreign('name')->references('name')->on('codes');
        $table->timestamps();
    });
}

public function down()
{
    # 制約修正の前にテーブルを削除。
    # 外部キーが関連してくるので、FOREIGN_KEY_CHECKSを解除してから実行。
    DB::statement('SET FOREIGN_KEY_CHECKS = 0');
    Schema::dropIfExists('new_table');
    DB::statement('SET FOREIGN_KEY_CHECKS = 1');

    Schema::table('code_details', function (Blueprint $table) {
        # 省略
    });
}

関連URL

https://laravel.com/docs/5.6/migrations



以上です。

Migrateは簡単に構築できるだけのものではありません。
変更履歴を保持できるというのも大きな役割です。
その利点を生かすために、変更用のMigrateファイルを書いてみるのも良いかもしれません。