Eloquent中一对多关系表的分组和总和

Posted

技术标签:

【中文标题】Eloquent中一对多关系表的分组和总和【英文标题】:Group by and Sum of One-To-Many relation tables in Eloquent 【发布时间】:2022-01-03 15:35:33 【问题描述】:

我有一个要求。 我的数据库有如下表。 这些表具有 OneToMany (1-n) 父子关系。

Table School (id, school_name)

Table Class (id, school_id, class_name)

Table Section (id, class_id, section_name, no_of_seats)

Table Student (id, section_id, student_name, ....)

当 Some Student 注册后,数据会上传到 Student 表。 现在,我想要一个类似的统计数据

| school_name | total_seats | student_registered |

对于特定的学校

| class_name | total_seats | student_registered |

如何在 Laravel/Eloquent 中实现这一点

提前致谢

【问题讨论】:

你能分享一些模型代码和你正在使用的列 上面给出了我的 4 个模型/表以及列名。学校、班级、部门和学生。 在这些模型中是否定义了关系?? 是的,正确定义。例如,Class 表将 school_id 作为 School 表 id 列的外键。 Section 和 Student 表也定义了关系。 您是否已经在任何表中定义了total_seats 列? 【参考方案1】:

可能适用于:

计数/总结HasMany 关系 计数/总结HasManyThrough 关系 计数/总结HasManyDeep 关系

定义

class Section extends Model

    public function students(): HasMany
    
        return $this->hasMany(Student::class);
    

    public function scopeWithRegisteredStudents(Builder $query): Builder
    
        // Count HasMany relation
        return $query->withCount('students as students_registered');
    

// The word "Class" is reserved, so we need to use "SchoolClass" instead
class SchoolClass extends Model

    protected $table = 'classes';

    public function sections(): HasMany
    
        return $this->hasMany(Section::class, 'class_id');
    

    public function students(): HasManyThrough
    
        return $this->hasManyThrough(Student::class, Section::class, 'class_id');
    

    public function scopeWithTotalSeats(Builder $query): Builder
    
        // Summarize field from HasMany relation
        return $query->withSum('sections as total_seats', 'no_of_seat');
    

    public function scopeWithRegisteredStudents(Builder $query): Builder
    
        // Count HasManyThrough relation
        return $query->withCount('students as students_registered');
    

class School extends Model

    public function classes(): HasMany
    
        return $this->hasMany(SchoolClass::class);
    

    public function sections(): HasMany
    
        return $this->hasManyThrough(Section::class, SchoolClass::class, null, 'class_id');
    

    public function students(): HasManyThrough
    
        // https://github.com/staudenmeir/eloquent-has-many-deep
        return $this->hasManyDeep(Student::class, [SchoolClass::class, Section::class], ['school_id', 'class_id', 'section_id'], ['id', 'id', 'id']);
    

    public function scopeWithTotalSeats(Builder $query): Builder
    
        // Summarize field from HasManyThrough relation
        return $query->withSum('sections as total_seats', 'no_of_seat');
    

    public function scopeWithRegisteredStudents(Builder $query): Builder
    
        // Count HasManyDeep relation
        return $query->withCount('students as students_registered');
    

示例

// Fetching simply
Section::query()
    ->withRegisteredStudents()
    ->get();
SchoolClass::query()
    ->withTotalSeats()
    ->withRegisteredStudents()
    ->get();
School::query()
    ->withTotalSeats()
    ->withRegisteredStudents()
    ->get();

// Fetching with nested relations
School::query()
    ->withTotalSeats()
    ->withRegisteredStudents()
    ->with(['classes' => function (HasMany $query) 
        return $query
            ->withTotalSeats()
            ->withRegisteredStudents();
    ])
    ->get();

如果您使用phpStanPsalm 之类的静态分析器,您也可以使用scopes 方法来防止错误。

School::query()
    ->scopes(['withTotalSeats', 'withRegisteredStudents'])
    ->get();

【讨论】:

谢谢@mpyw,非常感谢。它按预期工作。【参考方案2】:

这不是您所要求的,因为它使用 Query Builder 而不是 Eloquent。我没有测试它,因为我目前没有什么要测试的,但这应该可以工作 -

use Illuminate\Support\Facades\DB;

$students_per_section = DB:table('students')
        ->select('section_id', DB::raw('COUNT(id) AS num_students'))
        ->groupBy('section_id')

$query = DB:table('schools')
        ->join('classes', 'schools'.'id', '=', 'classes.school_id')
        ->join('sections', 'classes.id', '=', 'sections.class_id')
        ->leftJoinSub($students_per_section, 'students_per_section', function($join) 
            $join->on('sections.id', '=', 'students_per_section.section_id')
        );

if ($school_id) 
    $query
        ->select('classes.class_name', DB::raw('SUM(no_of_seats) AS total_seats'), DB::raw('SUM(students_per_section.num_students) AS student_registered'))
        ->where('schools.id', '=', $school_id)
        ->groupBy('classes.class_name')
 else 
    $query
        ->select('schools.school_name', DB::raw('SUM(no_of_seats) AS total_seats'), DB::raw('SUM(students_per_section.num_students) AS student_registered'))
        ->groupBy('schools.school_name')


$stats = $query->get();

【讨论】:

以上是关于Eloquent中一对多关系表的分组和总和的主要内容,如果未能解决你的问题,请参考以下文章

无法删除一对多关系中的记录 laravel eloquent

laravel eloquent 中的一对多关系

在 ORM(Eloquent)中,具有 Genre 的 Book 是一对多关系吗?

在 Eloquent 中嵌套一对多关系

Laravel5.6 Eloquent ORM 关联关系,一对一和一对多

Eloquent 一对多关系的错误