У меня есть небольшое приложение Laravel, которое в основном представляет собой несколько таблиц, как показано ниже, и у меня возникла проблема с Laravel Eloquent, генерирующим больше SQL-запросов, чем нужно. В основном я пытаюсь отобразить все «оценки» для вошедшего в систему студента, но свести количество запросов к минимуму.
Вот мой контроллер
public function index(Request $request)
{
$student = $request->user();
return view('dashboard.index', [
'user' => $student,
'grades' => $student->grades->paginate(5)
]);
}
Вот мои структуры таблиц
курсы
select id, name from courses;
+----+--------------------+
| id | name |
+----+--------------------+
| 1 | English Literature |
| 2 | Stats |
| 3 | Biology |
+----+--------------------+
оценки
select id, student_id, course_id, score, letter_grade from grades where student_id = 1;
+----+------------+-----------+--------+--------------+
| id | student_id | course_id | score | letter_grade |
+----+------------+-----------+--------+--------------+
| 1 | 1 | 1 | 90.00 | A |
| 2 | 1 | 2 | 50.00 | D |
| 3 | 1 | 3 | 100.00 | A |
+----+------------+-----------+--------+--------------+
курс_студент
select * from course_student;
+-----------+------------+
| course_id | student_id |
+-----------+------------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
+-----------+------------+
студенты
select id, email from students limit 1;
+----+---------------------------+
| id | email |
+----+---------------------------+
| 1 | [email protected] |
+----+---------------------------+
Мои отношения Laravel в моделях выглядят следующим образом: Студент.php
public function grades(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Grade::class);
}
public function courses(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(Course::class);
}
Оценка.php
class Grade extends Model
{
use HasFactory;
public function student(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(Student::class);
}
public function course(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(Course::class);
}
}
Курс.php
class Course extends Model
{
use HasFactory;
public function department(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(Department::class, 'id', 'department_id');
}
public function students(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(Student::class);
}
public function teachers(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(Teacher::class);
}
}
DashboardController.php
public function index(Request $request)
{
$student = $request->user();
return view('dashboard.index', [
'user' => $student,
'grades' => $student->load('grades', 'courses')->grades->paginate(10)
]);
}
представление панели инструментов/index.blade.php
<div class = "py-2">
<div class = "max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class = "bg-white dark:bg-gray-800 sm:rounded-lg">
{{ $grades->links() }}
<table>
<tr>
<td>Course</td>
<td>Grade</td>
<td>Score</td>
<td>Date</td>
</tr>
@foreach ($grades as $key => $grade)
<tr>
<td>{{ $grade->course->name }}</td>
<td>{{ $grade->letter_grade }}</td>
<td>{{ $grade->score }}</td>
<td>{{ $grade->created_at }}</td>
</tr>
@endforeach
</table>
</div>
</div>
</div>
Используя Laravel Debugbar, я вижу все сгенерированные запросы - у меня нет никаких ошибок, но SELECT * из курсов можно оптимизировать для использования IN(), а не всего SELECT * из курсов по первичному ключу каждый раз, как мне оптимизировать логику моего контроллера/модели
select * from `students` where `id` = 1 and `students`.`deleted_at` is null limit 1
select * from `grades` where `grades`.`student_id` = 1 and `grades`.`student_id` is not null
select * from `courses` where `courses`.`id` = 1 limit 1
select * from `courses` where `courses`.`id` = 2 limit 1
select * from `courses` where `courses`.`id` = 3 limit 1
select * from `courses` where `courses`.`id` = 4 limit 1
select * from `courses` where `courses`.`id` = 5 limit 1
Ожидаемый результат будет использовать меньше запросов к таблице курсов и НЕ откладывать запросы на загрузку.
Вы проделали отличную работу по настройке отношений в каждой Модели.
Для вашего вопроса вы можете добиться этого, используя вложенную нетерпеливую загрузку.
$student = Student::query()
->where('id', auth()->id())
->with('grades.courses')
->first();
$grades = $student->grades->paginate(5);
return view('dashboard.index', compact('student', 'grades'));
Как описано здесь: https://laravel.com/docs/9.x/eloquent-relationships#nested-eager-loading
Теперь ваш laravel должен выполнять только 3 запроса по таблице студентов, оценок и курсов.
И если вы хотите получать оценки только с разбиением на страницы, попробуйте выполнить этот запрос.
$user = $request->user(); // or auth()->user()
$grades = Grade::query()
->where('student_id', $user->id)
->with('courses')
->paginate(5);
return view('dashboard.index', compact('user', 'grades'));
Это по-прежнему будет выполнять только 3 запроса.
примечание: $request->user()
то же, что auth()->user()
и в запросе я использую auth()->id()
, чтобы получить идентификатор вошедшего в систему пользователя
это правда @ N69S, спасибо, что исправили это своим ответом
Ваша разбивка на страницы не нажимает queryBuilder
, а вместо этого Collection
: выполнение $student->grades
уже извлекает все оценки учащегося, поэтому запрос не имеет ограничений.
Вы используете отношение courses
Grade::class в своем лезвии, но вы загрузили отношение courses
Student::class в свой контроллер.
Чтобы улучшить все, вы можете сделать это так
public function index(Request $request)
{
$student = $request->user();
return view('dashboard.index', [
'user' => $student,
'grades' => $student->grades()->with('courses')->paginate(10),
]);
}
Вызов $student->grades()
возвращает QueryBuilder вместо вызова $student->grades
, который возвращает коллекцию.
$grades = $student->grades->paginate(5);
не позволяет разбивать оценки на страницы, поскольку они загружаются в экземпляр$student
без разбиения на страницы.