I created a simple blog that has posts and comments. I want to find all the posts that have at least one comment and also find all the posts with no comments. Is there a cakephp way to do this? I.E. maybe something like
$this->Post->find('all', ???);
I ended up writing my own query, the example below finds all the posts with at least 1 comment
SELECT *
FROM (
select posts.*, count(comments.id) as comment_count
from posts left join comments on posts.id = comments.post_id
group by posts.id
) as T
WHERE comment_count != 0
but there seems like there would be a better way to do this.
Note: a Post hasMany Comment and Comment belongsTo Post
$grouped_comments = $this->Comment->find('all', array('group' => 'Comment.post_id'));
This will give you an array of all Comments grouped by post_id so you will have exactly one comment for each post, which is what you want. From there you can do whatever you want with that data.
Let's say you wanted to post a list of all post titles with comments.
echo "<H1>Posts with comments:</H1>";
foreach ($grouped_comments as $comment) {
echo $comment['Post']['Title'] . "<br>";
}
This of course only works if you have your model relationships set up in your comment.php model.
One solution would be:
$this->Post->find('all');
And your relationships are setup as you say above, the comments will already be returned with this information, you just need to run a count on them and handle the processing from there... something like:
$posts = $this->Post->find('all');
count($posts[0][Comment]);
There will likely be a more elegant way - as I'm new to CakePHP myself, but hopefully this has some value.
I'm still fairly new to CakePHP, but I think you could use counterCache to track the # of Comments per Post model record in a field in your Posts table, and then use some simple conditions in your Find to get what you are looking for. counterCache automatically updates the cache count field in the Parent model record field when children model records are added/deleted.
counterCache for CakePHP 1.2: http://book.cakephp.org/view/816/counterCache-Cache-your-count
Related
I have two tables: posts and votes, and I want to display only one post on home page, and then displaying them in random order. But I want to exclude posts that are already upvoted. So I want to loop only between not-voted posts. I am using Laravel 5.4
Structure for posts table:
id
user_id
...
Structure for votes table:
id
user_id
post_id
...
I just need a query for excluding posts, everything else I already have written.
Thanks
Assuming you have votes relationship in your Post model (I assume you use Eloquent) you can use:
$posts = Post::doesntHave('votes')->get();
to get posts that don't have any votes.
Obviously it might be not a good idea to run get() here as you will get all posts that don't have votes - and what in case there are 100 000 posts matching this criteria? You should probably only get some of them and then choose one random for example:
$posts = Post::doesntHave('votes')->latest()->take(100)->get();
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();
I'm relatively new to coding in general, first year CS student and all of that. I'm using PHP to display a list of school classes categories and a list of assignment posts within each category. The desired output would be something like this:
CATEGORY 1
-Assign Post 1
-Assign Post 2
CATEGORY 2
-Assign Post 0
Using join gives me partial functionality, because it gets all the assign posts, but when I loop through and post the data it posts the Category title more than once. I then tried using group_by but it's only posting one post per category.
I'm not gonna post all my code, simply the db queries. I'll happily post the other code if you think the problem may be there.
//Select our classes, and get assignments from each class
$this->db->select('*');
$this->db->from('categories');
$this->db->join('assignments', 'categories.cat_id = assignments.assign_cat_id');
$this->db->group_by("cat_id");
//Return the classes and assignments in an array
$query = $this->db->get();
return $query->result_array();
Not to tricky right? Any help is of course appreciated :).
UPDATE
I have figured out that group_concat will post the assignment names, and then I can explode the results. Is this the only way, or is there a way to get it to post in a nice friendly array. The only reason I ask is I feel this adds un-necessary code in my view file, where I'm exploding the data.
Thank you!
Dont use group by ...use sort by Category id and while showing/or preparing array of data you can club records as per category..you cant achive this by single SQL
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?