I'm going through my laravel application and trying to fix any n+ issues I can find. I have come across one scenario which isn't really an n+ but not sure what to call it.
I have 2 models Post, Comment. A post has many comments and a comment belongs to a post
When I loop through all my posts I would like to display a count of how many comments they contain. I've been able to do this fine. But the problem it is 2 queries.
How do I update the following Eloquent query to add a column for comments count.
Post::where('status', 1)->get();
Thanks
Update
As of Laravel 5.2.32, a new method was added to the query builder to help with this. When you add the withCount($relation) method to your query, it will add a {relation}_count field to the results, which contains the count of the supplied relation.
So, your query would be:
$posts = Post::where('status', 1)->withCount('comments')->get();
foreach($posts as $post) {
echo $post->comments_count;
}
You can read more in the documentation here.
Original
#JarekTkaczyk has a good blog post that does what you're looking for. Check out the article here.
Basically, you'll be creating a relationship that contains the count of comments for the post, and you'll eager load the relationship (thus avoiding the n+1). He also has some syntactic sugar in there for accessing the count through an attribute accessor.
Either just use count on the relationship, or if you think it's necessary, you could add a 'num_comments' to the Post model and increment it on the creation of a comment:
$post->comments()->count();
or in the comments model:
public function create( $commentData ){
$result = $this->fill( $commentData );
$this->post()->increment('num_comments');
return $result;
}
Related
I'm trying to display a list of all the users and how many posts they've each made this week, and how many they've made last week, inside of a form
I've used a hasMany relation between the two tables and the relationship it's self is working.
return $this->hasMany('App\applications', 'user_id');
inside of the view what I have that's displaying the post count is
{{$user->applications->count()}}
The main thing I'm stuck on is the SQL Query or using Carbon inside of the controller function to achieve this.
If anyone has done this before your help would be greatly appreciated!
Thank you.
Since you want to count posts for many users, eager load the data and use withCount():
User::withCount(['applications' => function($q) {
$q->whereBetween('created_at', [Carbon::now()->startOfWeek(), Carbon::now()]);
}])->get();
If you want to count the number of results from a relationship without actually loading them you may use the withCount method, which will place a {relation}_count column on your resulting models.
https://laravel.com/docs/5.4/eloquent-relationships#counting-related-models
In the Laravel 5 I can find all posts that have at least one comment using has method:
// Retrieve all posts that have at least one comment...
$posts = App\Post::has('comments')->get();
As theres no method like notHas, how can I find all posts that have no comments?
(remembering that it's a hasMany relationship)
Reference:
Querying Relationship Existence
http://laravel.com/docs/5.1/eloquent-relationships#querying-relations
Old topic but updated answer for those who came from Google like me. Nowadays you can just use doesntHave().
$posts = App\Post::doesntHave('comments')->get();
Reference:
Querying Relationship Absence
https://laravel.com/docs/5.7/eloquent-relationships#querying-relationship-absence
There isn't a notHas() method as far as I'm aware, but finding records with a relationship count less than one usually satisfies this requirement:
$posts = App\Post::has('comments', '<', 1)->get();
I am wanting to list all comments. Comments have a Polymorphic relationship with Blog, News etc. When listing all the comments I want to convert the commentable_id to the slug I have saved in the Blog/News table.
So far I have:
public function getCommentableIdAttribute($value)
{
// $post_type = $this->commentable_type;
$post_detail = Blog::find($value);
return $post_detail['slug'];
}
I want to use $post_type and find the value based on that. Naturally though, I am getting a non-object issue if I replace Blog::find() with $post_type->find().
I don't know at this point which table I want to reference.
• Is there a better, more Laravel way of reversing this? The docs seem to suggest that I need to know the Model first.
• If not, any ideas how to make this work?
Thanks in advance for taking the time to help.
There was question about old L3 eager loaded paginations, not using eloquent. But i want to use eloquent to get eager loaded relationship with pagination.
Main model: Topic that has one to many relationship with Posts,
So that one Topic has many Posts. I get all data with this function:
public function findById($id)
{
return $this->topic->with('posts', 'posts.user', 'posts.user.profile')
->find($id);
}
And later I make loop to display all results, but they are not paginated:
#foreach($topic->posts as $post)
... unpaginated content ...
#endforeach
So, i could make a workaround and select separately all posts that has $id of topic and use ->paginate() instead of ->get() and would got paginated $pots,
but is there possibility to use eager loaded relationship posts that is paginated ? And how can it be done ?
To clarify something: paginating eager-loaded relationships is somewhat of a misconception. The point of eager loading is to retrieve all relationships in as few queries as you can. If you want to retrieve 10 topics, all of which have 35 posts, you will only need two queries. Sweet!
That said, paginating an eager-loaded relationship is not going to work. Consider two scenarios when eager loading happens:
You want to retrieve and list topics, and maybe list the first five posts for each topic. Great! Eager loading is perfect. Now, you wouldn't want to paginate the eager-loaded posts on a page like this, so it doesn't matter.
You load a single topic, and you want to paginate the posts for that topic. Great! Relatively easy to do. However, if you've already eager-loaded all posts belonging to this topic, you've just potentially retrieved a lot of extra resources that you don't need. Therefore eager loading is actually hurting you.
That said, there are two potential solutions:
Option 1: Create a custom accessor that paginates the Eloquent relationship.
/**
* Paginated posts accessor. Access via $topic->posts_paginated
*
* #return \Illuminate\Pagination\Paginator
*/
public function getPostsPaginatedAttribute()
{
return $this->posts()->paginate(10);
}
Pros: Paginates very easily; does not interfere with normal posts relationship.
Cons: Eager loading posts will not affect this accessor; running it will create two additional queries on top of the eager loaded query.
Option 2: Paginate the posts Collection returned by the eager-loaded relationship.
/**
* Paginated posts accessor. Access via $topic->posts_paginated
*
* #return \Illuminate\Pagination\Paginator
*/
public function getPostsPaginatedAttribute()
{
$posts = $this->posts;
// Note that you will need to slice the correct array elements yourself.
// The paginator class will not do that for you.
return \Paginator::make($posts, $posts->count(), 10);
}
Pros: Uses the eager-loaded relationship; creates no additional queries.
Cons: Must retrieve ALL elements regardless of current page (slow); have to build the current-page elements manually.
You may want to take a look at following:
$category = 'Laravel';
$posts = Post::with('user', 'category')->whereHas('category', function($query) use ($category) {
$query->where('name', '=', $category);
})->paginate();
By Zenry :
Source:http://laravel.io/forum/02-25-2014-eager-loading-with-constrains-load-post-based-on-category-name
Hope this helps
I need to be able to specify conditions on a relationship (one-to-many).
Example being; We have a Post which has many Comments, but I only want the Posts with Comments made by User A and then return the Post object(s).
The only way I can currently think of doing this is with a Fluent query, which wouldn't return the Post object I desire!
EDIT:
The comment has to of been made by User A. The relationship of the User who made the Comment to Post isn't a direct one. It would go through the Comment table.
RE EDIT:
Would it also be possible to say have distinct Posts? Rather than return 3 of the same Post Object?
You can query relationships. You would end up with something like this:
$postsWithComments = $user->posts()->has('comments', '>=', 1)->get();
Here is an extract from documentation: http://laravel.com/docs/eloquent
Querying Relations
When accessing the records for a model, you may wish to limit your results based on the existence of a relationship. For example, you wish to pull all blog posts that have at least one comment. To do so, you may use the has method:
Checking Relations When Selecting
$posts = Post::has('comments')->get();
You may also specify an operator and a count:
$posts = Post::has('comments', '>=', 3)->get();