Reducing Query Load Times By Limiting Results on Eloquent Relationship - php

I have the following scope.
public function scopeWithPosts($query)
{
return $query->with(
['posts' => function($query) {
$query->limit(2); // Line 5
}]
// further eager loads
// e.g. ['posts.*']
);
}
My relationship is as follows:
public function posts()
{
return $this->hasMany('Models\Post', 'user_id', 'user_id');
}
I am attempting to do the above to reduce the load time on my query. Problem is that whenever I attempt limit, it doesn't seem to work.
For example, with Line 5 I get 1 result, and if I remove I get 5 results.
Any ideas where i'm going wrong?
Thanks in advance.

Did you already tested the take method?
$query->take(2);
For even more precise limitation you could use
$query->skip(10)->take(2);

Related

Laravel - sort array from other class by SQL table column

I am calling an array of all the comments of a poll by using the following code:
$poll = Poll::find($id);
return view('pages.poll', ['poll' => $poll, 'comments' => $poll->comments]);
and the links between Comments and Polls are the following:
Comment.php
public function poll() {
return $this->belongsTo(Poll::class, 'poll_id');
}
Poll.php
public function comments() {
return $this->hasMany(Comment::class, 'poll_id');
}
Finally, I would like to sort the array comments coming from $poll->comment by the column likes in the Comment table, something like DB::table('comment')->orderBy('likes')->get();.
Is there any way to do that?
$poll->comments->sortBy('likes')
There's a number of ways you can do this.
Add orderBy('likes') directly to your comments relationship:
Poll.php:
public function comments() {
return $this->hasMany(Comment::class, 'poll_id')->orderBy('likes');
}
Now, any time you access $poll->comments, they will be automatically sorted by the likes column. This is useful if you always want comments in this order (and it can still be overridden using the approaches below)
"Eager Load" comments with the correct order:
In your Controller:
$poll = Poll::with(['comments' => function ($query) {
return $query->orderBy('likes');
})->find($id);
return view('pages.poll', [
'poll' => $poll,
'comments' => $poll->comments
]);
with(['comments' => function ($query) { ... }]) adjusts the subquery used to load comments and applies the ordering for this instance only. Note: Eager Loading for a single record generally isn't necessary, but can be useful as you don't need to define an extra variable, don't need to use load, etc.
Manually Load comments with the correct order:
In your Controller:
$poll = Poll::find($id);
$comments = $poll->comments()->orderBy('likes')->get();
return view('pages.poll', [
'poll' => $poll,
'comments' => $comments
]);
Similar to eager loading, but assigned to its own variable.
Use sortBy('likes'):
In your Controller:
$poll = Poll::find($id);
return view('pages.poll', [
'poll' => $poll,
'comments' => $poll->comments->sortBy('likes')
]);
Similar to the above approaches, but uses PHP's sorting instead of database-level sorting, which can be significantly less efficient depending on the number of rows.
https://laravel.com/docs/9.x/eloquent-relationships#eager-loading
https://laravel.com/docs/9.x/eloquent-relationships#constraining-eager-loads
https://laravel.com/docs/9.x/collections#method-sortby
https://laravel.com/docs/9.x/collections#method-sortbydesc

Getting count of nested relationship with 'withCount' method

I'm in need of help of figuring out how to get withCount() to work with nested relationships.
I have so far tried this
return CharityArea::with('campaigns.sponsor', 'campaigns.charityArea', 'campaigns.charityDetail')->withCount('campaigns.users')->where($matchTheseThings)->get();
Basically, I want to get the count of the users in the campaigns model.
The relationship on the CampaignsModel looks like this:
public function users(){
return $this->hasMany('App\UserPreferences', 'campaign_id', 'id');
}
The relationship to campaigns in CharityArea looks like this
public function campaigns(){
return $this->hasMany('App\Campaigns', 'charity_area_id', 'id');
}
Laravel throws and error saying that 'campaigns.users' is not found.
Any ideas on how else to do this?
Thanks.
You can use first set a ManyThrough relation in your CharityArea model.
function users()
{
return $this->hasManyThrough('App\UserPreferences', 'App\Campaigns');
}
Then you can call withCount() on it:
return CharityArea::with('campaigns.sponsor', 'campaigns.charityArea', 'campaigns.charityDetail')
->withCount('users')
->where($matchTheseThings)
->get();

How to eager load Aggregates in Laravel with Eloquent?

in my application I have a long list of Categories. I load them with this:
$categories = Categories::all();
In the Categories there is this function:
public function transactions()
{
return $this->hasMany(Transactions::class);
}
public function getPreviousActivityAttribute() {
return $this->transactions()
->where('date', '<' Carbon::now()->firstOfMonth())
->sum('amount');
}
I render this list of Categories as a table. Each row is calling $category->previousActivity.
This results in n+1 database queries, where n is the number of Categories (and its a lot). Apart from previousActiviy I display other Aggregates aswell (some do SUM others do AVG, etc.).
By the way. There are a lot of transactions. I cannot load them all using Categories::with('transactions')->get(). This would require too much memory.
Is there a way to eager load aggregates? There is a withCount method on the query builder, but it does not really help here.
Thank you for your help.
Attach a function to the withCount attribute in the modelĖšs constructor like this to always eager load this count.
public function __construct(array $attributes = array())
{
parent::__construct($attributes);
$this->withCount['transactions as total'] = function ($query) {
$query->select(DB::raw(
'SUM(IFNULL(amount, 0)) as relationsum'
));
};
}
A clean solution would be to use withCount as follows:
In Categories.php:
public function withPreviousActivity()
{
return $this->withCount([
'transactions as previous_activity' => function ($query) {
$query->select(DB::raw("SUM(IFNULL(amount,0)) as transactions_count"))->where('date', '<', Carbon::now()->firstOfMonth());
},
]);
}
Somewhere else:
dump(Categories::withPreviousActivity()->get()->toArray());

laravel pick last comment posted related to each post

I have got three tables in laravel like so:
Users, posts, and comments
I'm trying to come up with a query that fetches me all the user's posts, plus the date of last comment with each post.
Approach i've taken that's not working perfectly is:
$posts = User::find($userId)->posts()->with('latestComment')->get();
In my Post model I have:
public function latestComment()
{
return $this->hasOne(Comment::class)->latest();
}
In my findings, i haven't been to see a way to get the date from the lastComment load.
Any pointers welcome,
Thanks
Just discovered one needs to add the foreign key to the select method like so:
return $this->hasOne(Comment::class)->latest()->select('field','foreign_key');
You should use eager loading constraint. Code from the other answers will first load all comments, which you don't want.
$posts = Post::where('user_id', $userId)
->with(['comments' => function($q) {
$q->taletst()->take(1);
}])
->get();
You can use the existing relationship and get the latest comment.
public function comments() {
return $this->hasMany(Comment::class);
}
public function latestComment() {
return $this->comments()->last();
}

Laravel Multiple Models Eloquent Relationships Setup?

I have 3 models
User
Pick
Schedule
I'm trying to do something like the following
$picksWhereGameStarted = User::find($user->id)
->picks()
->where('week', $currentWeek)
->first()
->schedule()
->where('gameTime', '<', Carbon::now())
->get();
This code only returns one array inside a collection. I want it to return more than 1 array if there is more than 1 result.
Can I substitute ->first() with something else that will allow me to to return more than 1 results.
If not how can I set up my models relationship to allow this to work.
My models are currently set up as follow.
User model
public function picks()
{
return $this->hasMany('App\Pick');
}
Schedule model
public function picks()
{
return $this->hasMany('App\Pick');
}
Pick model
public function user()
{
return $this->belongsTo('App\User');
}
public function schedule()
{
return $this->belongsTo('App\Schedule');
}
Since you already have a User model (you used it inside you find method as $user->id), you can just load its Pick relationship and load those Picks' Schedule as follows:
EDIT:
Assuming you have a schedules table and your picks table has a schedule_id column. Try this.
$user->load(['picks' => function ($q) use ($currentWeek) {
$q->join('schedules', 'picks.schedule_id', '=', 'schedules.id')
->where('schedules.gameTime', '<', Carbon::now()) // or Carbon::now()->format('Y-m-d'). See what works.
->where('picks.week', $currentWeek);
}])->load('picks.schedule');
EDIT: The code above should return the user's picks which have a schedules.gameTime < Carbon::now()
Try it and do a dump of the $user object to see the loaded relationships. That's the Eloquent way you want.
Tip: you may want to do $user->toArray() before you dump $user to see the data better.
EDIT:
The loaded picks will be in a form of Collections so you'll have to access it using a loop. Try the following:
foreach ($user->picks as $pick) {
echo $pick->schedule->gameTime;
}
If you only want the first pick from the user you can do: $user->picks->first()->schedule->gameTime
I think a foreach loop may be what you're looking for:
$picks = User::find($user->id)->picks()->where('week', $currentWeek);
foreach ($picks as $pick){
$pickWhereGameStarted = $pick->schedule()->where('gameTime', '<', Carbon::now())->get();
}
Try this and see if it's working for you

Categories