Использование подзапросов Laravel
Доброго времени суток. Давно я не писал статей в свой блог. Много работы и мало времени. В данной статье мы рассмотрим, как использовать подзапросы в Laravel. Допустим у нас есть две таблицы: users и comments. Таблица users имеет отношение hasMany к таблице comments. И мы хотим вывести всех пользователей с пагинацией (по 10) и показать дату последнего комментария для пользователя. Но тут возникает несколько проблем…
И так, возникает несколько проблем:
- N+1 запрос
- Используется много памяти.
Дальше мы и разрешим эти вопросы проблемы.
Пагинация в Laravel реализуется очень просто. Для этого используется метод paginate($el). Что такое пагинация и как ее реализовать вы можете прочитать в статье «Laravel пагинация». А в статье «Laravel Ajax пагинация» описано как реализовать ajax пагинацию в Laravel.
Мы немного отклонились от темы. Давайте решать наши задачи. И первое, что приходит на ум – это использовать жадную или ленивую загрузку. Что это такое вы можете прочитать в статье «Жадная и ленивая загрузка в Laravel. Методы with() и load()».
$users = User::with('comments')->paginate(10);
Таким образом мы решили первую проблему N+1 запрос, но вторая проблема осталась. Ведь представьте, если у нас 50 пользователей и каждый оставил в среднем по 100 комментариев. То будет загружено 5000 записей!!!
Вторая проблема с использованием большого количества памяти осталась. Вот тут нам на помощь и приходят подзапросы в Laravel.
Подзапросы в Laravel
По сути, подзапросы Laravel позволяют выбирать дополнительные столбцы или атрибуты в запросе первичной таблицы.
$lastComment = Comment::select('created_at') ->whereColumn('user_id', 'users.id') ->latest() ->limit(1) ->getQuery(); $users = User::select('users.*') ->selectSub($lastComment, 'last_comment_at') ->get(); @foreach ($users as $user) <tr> <td>{{ $user->name }}</td> <td>{{ $user->email }}</td> <td> @if ($user->last_comment_at) {{ $user->last_comment_at->format('M j, Y \a\t g:i a') }} @else Нет комментариев @endif </td> </tr> @endforeach {{ $users->paginate(10) }}
Если рассмотреть какой sql запрос у нас получился, то увидим следующее:
select "users".*, ( select "created_at" from "comments" where "user_id" = "users"."id" order by "created_at" desc limit 1 ) as "last_comment_at" from "users"
Таким образом мы решили наши две проблемы: избавились от N+1 запрос и минимизировали использование объема памяти.
Подзапрос Laravel в scope
Подзапросы Laravel можно использовать в Scope. Открываем модель User и добавляем в нее следующий метод:
function scopeWithLastCommentDate($query) { $query->addSubSelect('last_comment_at', Comment::select('created_at') ->whereColumn('user_id', 'users.id') ->latest() ); }
И теперь мы можем использовать:
$users = User::withLastCommentDate()->get();
Спасибо за внимание.
Весь код тестировался в Laravel 5.7
Статья написана по статье: http://tisuchi.com/posts/deep-diving-into-laravel-subquery-14