I have a basic table article linked to a table category by id (classic), I would like to make a doctrine request to retrieve only 5 articles by category (for all categories)
This request return me all articles of categories, I just want 5 for each
public function getArticlesAndCategs(){
$qb = $this->createQueryBuilder('c')
->orderBy('c.id','DESC')
->leftJoin('c.articles', 'a')
->addSelect('a')
->addOrderBy('a.id','DESC')
->getQuery();
return $qb->execute();
}
Can you help me for that?
thank you
This is no easy task unfortunately. At least I haven't found a satisfying solution yet (satysfying for me ;) ).
Three things you can do:
Iterate through the categories and retrieve 5 posts for each category. This of course cause that many db request as many you have categories.
Return all the categories with joined posts and iterate inside php. The drawback - you'll return all the posts, so if you have many, this can mean a lot of memory used.
If you don't have to stick to Doctrine you can try the solution from here: mySQL Returning the top 5 of each category
Related
I have 4 tables that are related in the database and in models as follows:
Users (hasMany(communities), hasMany(comments), hasMany(submissions))
Communities (belongsTo(user), hasMany(submissions))
Submissions (belongsTo(community), belongsTo(user), hasMany(comments))
Comments (belongsTo(user) , belongsTo(submission))
Now. If I use the relations to get, say the 25 latest submissions, like this:
$submissions = Submission::simplePaginate(25);
I'm met with 76 queries run, after using foreach to loop through the results.
If I use eager loading with the following
$submissions = Submission::with(['user', 'community', 'comments'])->simplePaginate(25);
I'm met with 4 queries which is optimal (I think).
Now, my problem is that, on communities, I have a database field called active which accepts a 1 for an active community, and a 0 for an inactive. As you might have guessed, the previous query returns everything, including inactive communities.
I thought about eager loading constraints, so I used the following:
$submissions = Submission::with([
'community' => function($query) {
$query->active();
}
]
)->with(['user', 'comments'])->simplePaginate(25);
This still does that job with 4 queries, but now I run into another problem. This method filters the inactive communities, but still loads the posts, users and comments. And now I'm left with a result where I get posts from inactive communities.
Finally, I tried, upon reading this community, the following:
$submissions = Submission::whereHas(
'community', function($query) {
$query->active();
}
)->with(['user', 'comments'])->simplePaginate(20);
So, instead of using ->with() I used ->whereHas() as someone suggested. However, while this produces the correct results, i.e. it gives me posts, comments and users from the active communities only, it now does that in 23 queries, as per the feedback I get from Laravel Debugbar.
Is this normal? Is this the only way I can do this through Eloquent and without writing custom queries with joins? Am I blowing 23 queries out of proportion?
Any help would be greatly appreciated.
Your problem is that the active() scope is running a query for every community you are retrieving, in your case 20.
To optimize this you could make a specific relation in the Submission model where you filter the active communities proactively.
// Submission model
public function activeCommunities (){
return $this->hasMany(Community::class)->whereActive(true);
}
Now your query would look like:
$submissions = Submission::with(['user', 'comments', 'activeCommunities])
->simplePaginate(25);
This should perform 4 queries I believe.
Hope this helps you.
So, after tinkering with it for a while, I found a solution, but I'm still not sure. I'm posting this for future reference, since it solved my problem. The code I used is the following
$submissions = Submission::whereHas(
'community', function($query) {
$query->where('is_active', true);
}
)->with(['user', 'community'])->withCount('comments')
->paginate(25);
So, I tried using whereHas() to check for the models existence before actually eager loading the community model, if that makes sense? It seems to get me the results I want, since I now execute a total of 4 queries to get all the submissions, and the related info, filtering the inactive communities.
I have a database with a table articles and a table category.
My table articles have some fields. And one that is category_id and a another orientation.
Categories, in my design, are arranged by orientation. Here is an example
So, I would like to get all category BUT these categories must belong to the right orientation. I want list of all category who have articles with 'web' orientation, by example.
I do not know if it's possible with this architecture and if you understand me.
Any help is welcome
UPDATE : adding the schemas
If you have your models and relations set up, I believe this should do what you want:
$categories = Category::whereHas('articles', function($query) {
$query->where('orientation', 'web')
})->get()
From the Laravel documentation:
If you need even more power, you may use the whereHas and orWhereHas
methods to put "where" conditions on your has queries. These methods
allow you to add customized constraints to a relationship constraint,
such as checking the content of a comment
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;
}
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();
If I had 2 tables, say blog_category and blog, each "blog" can belong in a particular category only so a 1-1 relationship based on a key called "blog_category_id".
Now in my code I would do something like:
//Loop through categories such as
foreach($categories as $cat):
//then for each category create an array of all its posts
$posts = $cat->getPosts(); // This would be another DB call to get all posts for the cat
//do stuff with posts
endforeach;
Now to me this seems like it could end up quite expensive in terms of DB calls depending on the size of $categories. Would this still be the best solution to do this? Or would I be able to do something in the code and first retrieve all the categories, then retrieve all the blogs and map them to their corresponding category via the id somehow? This would in theory be only 2 calls to the DB, now size wise the result set for call 2 (the blogs) would definitely be larger, but would the actual DB call be as expensive?
I would normally go for the first option, but I'm just wondering if there would be a better way of approaching this or is it more likely that the extra processing in PHP would be more costly in terms of performance? Also specifically from an MVC perspective, if the model returns the categories, but it should also return the corresponding blogs for that category, I'm not sure how best to structure this, from my understanding, shouldn't the model return all the data required for the view?
Or would I be better off selecting all categories and blogs using inner joins in the first query and create the output I need of this? Perhaps by using a multi-dimensional array?
Thanks
You can use a simple SQL query to get all categories and posts like the following:
SELECT *
FROM posts p
JOIN categories c ON c.id = p.blog_category_id
ORDER BY c.category_name ASC,
p.posted_date DESC
Then when you loop over the returned records assign the current category id to a variable, which you can use to compare against the next records category. If the category is different then print the category title before printing the record. It is important to note that for this to work you need to get the posts ordered by category first and then post so that all posts in the same category are together.
So for example:
$category_id = null;
foreach($posts as $post) {
if($post['blog_category_id'] != $category_id) {
$category_id = $post['blog_category_id'];
echo '<h2>' . $post['category_name'] . '</h2>';
}
echo '<h3>' . $post['post_title'] . '</h3>';
echo $post['blog_content'];
}
Note: as you have not posted up the schema of these two tables I have had to make up column names that are similar to what I would expect to see in code like this. So the code above will not work with your code without some adjustments to account for this.
The best solution depends on what you are going to do with data.
Lazy loading
Load data when you need it. It's a good solution when you have, for instance, 20 categories and you load posts for only 2 of them. However, if you need to load posts for all of them it won't be efficient at all... It's called a n+1 queries (and it's really bad).
Eager loading
On the other hand, if you have to access to almost all of your posts, you should do an eager loading.
-- Load all your data in a query
SELECT *
FROM categories c
INNER JOIN posts p ON c.id = p.category_id;
// Basic example in JSON of how to format your result
{
'cat1': ['post1', 'post2'],
'cat2': ['post5', 'post4', 'post5'],
...
}
What to do?
In your case I would say an eager loading because you load everything in a loop. But if you don't access to the most of your data, you should re-design your model to perform a lazy loading in such a way that the SQL query to load posts for a specific category is actually performed when a view try to access them.
What do you think?