Использование подзапросов Laravel

Ромчик
0

Доброго времени суток. Давно я не писал статей в свой блог. Много работы и мало времени. В данной статье мы рассмотрим, как использовать подзапросы в Laravel. Допустим у нас есть две таблицы: users и comments. Таблица users имеет отношение hasMany к таблице comments. И мы хотим вывести всех пользователей с пагинацией (по 10) и показать дату последнего комментария для пользователя. Но тут возникает несколько проблем…

И так, возникает несколько проблем:

  1. N+1 запрос
  2. Используется много памяти.

Дальше мы и разрешим эти вопросы проблемы.

Пагинация в 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

Понравилась статья? Поделись с друзьями.
  • Add to favorites
  • Добавить ВКонтакте заметку об этой странице
  • Twitter
  • Facebook
  • Мой Мир
  • LiveJournal
  • Одноклассники
  • Блог Я.ру
  • MySpace
  • FriendFeed
  • В закладки Google
  • Google Buzz
  • Яндекс.Закладки
  • Reddit
  • StumbleUpon
  • Technorati
  • del.icio.us
  • БобрДобр
  • LinkedIn
  • Memori.ru
  • Сто закладок
  • Blogger

©2012-2019 По всем вопросам обращайтесь через форму обратной связиПолитика конфиденциальности

Яндекс.Метрика