孫リレーション先のカラムを合計する方法/Laravel/N+1問題

孫リレーション先のカラムを合計する方法を解説してきます。

今回は詳しく解説していきませんが、この処理をすることでN+1問題を解決できる機会も多くあるかと思います。

子リレーションの場合は、withSum(‘{{子リレーション}}’, ‘{{対象カラム}}’)を利用して合計していけると思います。

同様に、withSum(‘{{子リレーション.孫リレーション}}’, ‘{{孫の対象カラム}}’)でいけるかな?と思っていたらNGでした。

■解決方法

①:親→孫リレーションを作成
②:親→孫リレーションにて、withSumで集計

という2ステップで実施します。

親子孫のモーチーフとしては、
学校毎のテスト点数の合計を集計する方法を考えていきます。
School → Student → Score とつながっていきます。

①:親→孫リレーションを作成

まずは、親から孫を繋ぐためのリレーションを作成します。

Modelを親、子、孫 2つ用意します。

子:Student Model

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Student extends Model
{
    use HasFactory;

    protected $fillable = [
        'name',
        'school_id',
    ];

    public function score()
    {        
        return $this->hasMany(Score::class, 'student_id', 'id');
    }

}

孫:Score Model

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Score extends Model
{
    use HasFactory;

    protected $fillable = [
        'score',
        'student_id',
    ];

    public function student()
    {    
        return $this->belongsTo(Student::class, 'id', 'student_id');
    }
}

親:School Model

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class School extends Model
{
    use HasFactory;

    protected $fillable = [
        'name',
    ];

    // このリレーションが今回の本題です。 親→孫へのリレーションを作成
    public function studentscore()
    {
        return $this->hasManyThrough(
            Score::class, // 孫 Model
            Student::class, // 子 Model
            'school_id', // 子テーブルの親ID
            'student_id', // 孫モデルの子ID
            'id',    // 親テーブルのローカルキー
            'id'    // 子テーブルのローカルキー
        )
    }

}

ポイントは、School Model の hasManyThroughを利用したリレーションメソッドです。
(親→子、子→孫へのリレーションもお忘れなく作成しておいてください。)

上記にて、親→孫リレーションを作成が出来ました。

②:親→孫リレーションにて、withSumで集計

②-1:孫からデータを合計

学校毎の学生の点数を合計する処理を書いていきます。
任意のControllerにて、

$school_score = School::
    withSum('student_score', 'score')
    ->get();

こちらで、学校毎に学生の点数合計が取得出来ます。

②-2:合計データはどこに?

合計データはどこに含まれるのでしょうか。

A. 親データに新しいカラムが追加されます。 カラム名は、

{{親孫のリレーション名}}_sum_{{合計したカラム名}}

$school_scoreの中身に、下記のカラムが追加さています。

studentscore_sum_score

ddでご確認を!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です