php - 如何优化此方法并避免 foreach 循环
问题描述
我的 Laravel 应用程序中有以下模型:课程、活动、学生、注册、出勤。
- 一门课程有许多事件。
- 当学生注册课程时,会生成一个注册,因此一个课程有很多注册。
- 一个学生有很多出勤记录。
- 出勤记录属于一个事件和属于一个学生。
我想检索至少有一名学生的出勤记录少于课程中预期事件总数的课程列表。例如,如果一门课程有 10 个事件,我需要获取所有课程,其中至少有一个学生的出勤记录少于这些事件的 10 个预期出勤记录。
作为临时解决方案,我在 Course 模型中使用了嵌套的 foreach 循环:
public function getEventsWithExpectedAttendanceAttribute()
{
return $this->events()->where(function($query) {
$query->where('exempt_attendance', '!=', true);
$query->where('exempt_attendance', '!=', 1);
$query->orWhereNull('exempt_attendance');
})->where('start', '<', Carbon::now(env('COURSES_TIMEZONE'))->toDateTimeString())->get();
}
public function getMissingAttendanceAttribute()
{
$eventsWithMissingAttendanceCount = 0;
// loop through every event supposed to have attendance
foreach ($this->events_with_expected_attendance as $event)
{
// loop through every student
foreach ($this->enrollments as $enrollment)
{
// if the student has no attendance record for this event
if (Attendance::where('student_id', $enrollment->student_id)->where('event_id', $event->id)->count() == 0)
{
// count one and break loop
$eventsWithMissingAttendanceCount++;
break;
}
}
}
return $eventsWithMissingAttendanceCount;
}
但这在性能方面确实不漂亮,而且效率也非常低下。但是,到目前为止,我一直无法找到更好的解决方案。任何帮助将不胜感激!
更新:这是数据库模式
CREATE TABLE `courses` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`));
CREATE TABLE `events` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`course_id` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`));
CREATE TABLE `enrollments` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`student_id` int(10) unsigned NOT NULL,
`course_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`));
CREATE TABLE `students` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`));
CREATE TABLE `attendances` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`student_id` int(10) unsigned NOT NULL,
`event_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`));
解决方案
您的方法具有二次 n^2 性能,因为查询数乘以事件和出勤数。
减少查询数量的第一步可能是从最内层循环中提取此查询
Attendance::where('student_id', $enrollment->student_id)->where('event_id', $event->id)->count() == 0
并提前获取出勤率
public function getMissingAttendanceAttribute()
{
$eventsWithMissingAttendanceCount = 0;
$eventsIDs = $this->events_with_expected_attendance->pluck('id');
$studentIDs = $this->enrollments->pluck('student_id');
// Collection of attendances
$attendances = Attendance::whereIn('student_id', $studentIDs)
->whereIn('event_id', $eventsIDs)
->get();
// loop through every event supposed to have attendance
foreach ($this->events_with_expected_attendance as $event)
{
// loop through every student
foreach ($this->enrollments as $enrollment)
{
$hasNotAttended = $attendances->where('student_id', $enrollment->student_id)
->where('event_id', $event->id)
->isEmpty();
if ($hasNotAttended) {
$eventsWithMissingAttendanceCount++;
break;
}
}
}
return $eventsWithMissingAttendanceCount;
}
这应该会提高性能,因为查询的数量不会随着事件和注册的数量而扩展,而是保持不变。
例如,您可以提取$hasNotAttended
到注册模型的方法。
public function getMissingAttendanceAttribute()
{
$eventsWithMissingAttendanceCount = 0;
$eventsIDs = $this->events_with_expected_attendance->pluck('id');
$studentIDs = $this->enrollments->pluck('student_id');
// Collection of attendances
$attendances = Attendance::whereIn('student_id', $studentIDs)
->whereIn('event_id', $eventsIDs)
->get();
// loop through every event supposed to have attendance
foreach ($this->events_with_expected_attendance as $event)
{
// loop through every student
foreach ($this->enrollments as $enrollment)
{
// pass preloaded attendances.
// alternatively you could preload this relationship then no need to pass $attendances
if ($enrollment->hasNotAttended($event->id, $attendances)) {
$eventsWithMissingAttendanceCount++;
break;
}
}
}
return $eventsWithMissingAttendanceCount;
}
然后,您可以通过提取以下内容来扩展$this->enrollments
集合https://laravel.com/docs/6.x/collections#extending-collections 。
if ($enrollment->hasNotAttended($event->id, $attendances)) {
$eventsWithMissingAttendanceCount++;
break;
}
所以,它会被简化为
public function getMissingAttendanceAttribute()
{
$eventsWithMissingAttendanceCount = 0;
$eventsIDs = $this->events_with_expected_attendance->pluck('id');
$studentIDs = $this->enrollments->pluck('student_id');
// Collection of attendances
$attendances = Attendance::whereIn('student_id', $studentIDs)
->whereIn('event_id', $eventsIDs)
->get();
// loop through every event supposed to have attendance
foreach ($this->events_with_expected_attendance as $event)
{
if ($this->enrollments->hasNotAttended($event->id, $attendances)) {
$eventsWithMissingAttendanceCount++;
}
}
return $eventsWithMissingAttendanceCount;
}
推荐阅读
- java - Map-Reduce 作业未能交付预期的分区文件
- java - 如何从 Java 生成 CloudFormation 代码?
- google-shopping-api - 谷歌购物 - 上传后获取产品网址
- javascript - Jquery动画和反应
- reactjs - 如何在 Semantic UI React 中创建卡片?
- c - 传递 const 变量时不兼容的指针类型
- php - 如何在wordpress中允许/调用多种帖子类型
- javascript - document.getElementById 失败在 asp.net 中
- android - AutocompleteDropdownlist 锚定到错误的 textView
- vba - 尝试基于多个键对数据进行排序时出现 VBA 错误 1004