15
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

複合一意制約を使ってupsertを実行する

Posted at

以前upsertメソッドを以下の記事にて紹介させていただきましたが、今回は複合一意制約を使ったupsert実行処理の紹介になります。upsertメソッドってなんだっけ?という方は以下の記事をチェックしていただければと思います。

Eloquentの upsert メソッドでは、対象となるレコードが存在する場合はupdateを、存在しない場合は新規insertするという挙動を実現

upsertメソッドの復習

upsertメソッドですが、migrationファイルなどで検索対象となるカラムをprimaryキーもしくはuniqueキーで設定しないとupdateされず全てinsertとなってしまいます。
例えば以下のようなテーブルがある場合、student_idがuniqueキーで設定されているため、upsertメソッドではテーブル内で同じstudent_idが存在する場合はそのレコードを更新、存在しなければ新規追加する処理となります。

    public function up()
    {
        Schema::create('student_courses', function (Blueprint $table) {
            $table->id();
            $table->foreignId('student_id')->constrained()->unique()->onDelete('cascade');
            $table->foreignId('course_id')->constrained()->onDelete('cascade');
            $table->date('date_enrolled');
            $table->string('grade')->nullable();
        });
    }

upsertはeloquentで以下のようになります。

StudentCourse::upsert($arrayStudentCourses, ['student_id']);

しかしこの例の場合、course_idがあるということで授業コースがいくつかあると想定できますが、student_idがuniqueキーのため2つ目の授業コースを登録することができません。

複数カラムでuniqueとしたい

そこで考えたのが、student_idとcourse_idを結合させたカラム(student_id_course_id_concat)を作り、それをuniqueキーとすればやりたかったことの実現は可能になります。

    public function up()
    {
        Schema::create('student_courses', function (Blueprint $table) {
            $table->id();
            $table->foreignId('student_id')->constrained()->onDelete('cascade'); // 1の場合
            $table->foreignId('course_id')->constrained()->onDelete('cascade'); // 100の場合
            $table->string('student_id_course_id_concat')->unique(); // 結合させて1100とする
            $table->date('date_enrolled');
            $table->string('grade')->nullable();
        });
    }

しかし、カラムのデータを足しただけのカラムを増やすのは効率的とは言えませんし、格納するタイミングや、エラーが起きた場合の処理などリスクがあるためできる限り避けたいです。そんな時に使えるのが複合一意制約です。

複数カラムでuniqueとする書き方

    Schema::create('student_courses', function (Blueprint $table) {
            $table->id();
            $table->foreignId('student_id')->constrained()->onDelete('cascade');
            $table->foreignId('course_id')->constrained()->onDelete('cascade');
            $table->date('date_enrolled');
            $table->string('grade')->nullable();
            $table->unique(['student_id', 'course_id']); // 複合一意制約の書き方
    });

この書き方ですとuniqueキーを複数カラムに跨がせる形で指定することができ、student_idとcourse_idを合わせて配列に入れています。student_idとcourse_idが組み合わせでuniqueとなっているため、それぞれ同じレコードが存在する場合は更新され、どちらか片方だけ同じレコードが存在する場合はuniqueではないため新規追加されます。つまり

// テーブル内にstudentId = 2, courseId = 2のレコードが存在している場合
studentId = 1, courseId = 1 // insert
studentId = 2, courseId = 1 // insert
studentId = 2, courseId = 2 // update

といった判定となります。よって同じstudent_idでも異なるcourseが登録できますし、異なるstudent_idで同じcourseが登録できます。両idどちらもテーブルに存在しない場合は新規登録されますし、どちらも存在する場合は更新されます。

複合カラムに名前を付けることもできる

以下のように組み合わせに名前をつけることもできます。

// student_id_course_id_concatで名前付け
$table->unique(['student_id', 'course_id'], 'student_id_course_id_concat');

upsertのコードは以下のようになります。気を付けることは第二引数には組み合わせの名前ではなく、カラム名をそれぞれ指定しないとupsertとならないことです。

// ok!
StudentCourse::upsert($arrayStudentCourses, ['student_id', 'course_id']);
// ng
StudentCourse::upsert($arrayStudentCourses, ['student_id_course_id_concat']);

以上になります!使う機会があった場合はこのようにシンプルに書いていきましょう!

参考リンク

15
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?