Eloquent: Оптимизация подсчёта Моделей по Отношениям
Допустим, у вас есть отношение User -> manyToMany -> Role
, и вам нужно подсчитать количество пользователей на роль.
Самый простой (и худший) способ:
use App\Models\Role;
use App\Models\User;
class UserController extends Controller
{
public function index()
{
return [
'administrators' => User::whereHas('roles',
fn($query) => $query->where('id', 1))->count(),
'editors' => User::whereHas('roles',
fn($query) => $query->where('id', 2))->count(),
'viewers' => User::whereHas('roles',
fn($query) => $query->where('id', 3))->count(),
];
}
}
Это вызовет три запроса к базе данных, просматривая один и тот же список пользователей, фильтруя его по разным критериям.
Оптимизация 1. Загрузите все данные и отфильтруйте коллекцию
Если загрузить всех пользователей одним запросом к базе данных, то всякий раз, когда нужно подсчитать количество больше не нужно обращаться к базе данных:
$users = User::with('roles')->get();
return [
'admins' => $users->filter(fn ($user) => $user->roles->contains('id', 1))->count(),
'editors' => $users->filter(fn ($user) => $user->roles->contains('id', 2))->count(),
'viewers' => $users->filter(fn ($user) => $user->roles->contains('id', 3))->count(),
];
Это вызовет один запрос к базе данных вместо трёх.
Но у него есть обратная сторона: вы загружаете всех пользователей в память. Если у вас много пользователей, например 100000+, производительность может быть ещё хуже, даже при меньшем количестве запросов. Поэтому предлагаю использовать этот метод только в случае меньшего объёма данных.
Оптимизация 2. Инверсия того, что вам действительно нужно
Если нужны только count()
с отношениями, нужно подсчитать отношения, основная Модель даже не нужна.
Итак, вместо загрузки всех User
с отношениями, нужно загрузить Role
с количеством User
.
$roles = Role::withCount('users')->get()->keyBy('id');
return [
'admins' => $roles[1]->users_count,
'editors' => $roles[2]->users_count,
'viewers' => $roles[3]->users_count,
];
Это вызовет только один запрос к базе данных, не загружая всех пользователей и вернёт только то, что действительно нужно.