im running a query that usses 3 tabels "post, likes and comment" i need to get the ammount of likes and comments the post has got, and at the same time get the basic info from the post table so im using the query bellow but the problem is that it copys the value likeAmount to commentAmount if likes is bigger unless comments is 0.
SELECT post.*, COUNT(likes.id) as 'LikeAmount', COUNT(comment.id) as 'commentAmount' FROM post
LEFT JOIN likes ON post.id = likes.post
LEFT JOIN comment ON post.id = comment.post
GROUP BY post.id
ORDER BY LikeAmount DESC"
so that doesnt work but when i add distinct it does work, so when its like this:.
SELECT post.*, COUNT(distinct likes.id) as 'LikeAmount', COUNT(distinct comment.id) as 'commentAmount' FROM post
LEFT JOIN likes ON post.id = likes.post
LEFT JOIN comment ON post.id = comment.post
GROUP BY post.id
ORDER BY LikeAmount DESC";
i dont see why it works with distinct and doesnt with out, and does distinct mather performance wise or does it make no diffrence sinds it will be used in a website that has a lott of trafic..
try this, not short, but readable:
SELECT
p.*,
pl.like_count,
pc.comment_count
FROM post p
#join likes
LEFT OUTER JOIN (
SELECT
post,
COUNT(*) AS like_count
FROM likes
GROUP BY post
) AS pl
ON pl.post = p.id
#join comments
LEFT OUTER JOIN (
SELECT
post,
COUNT(*) AS comment_count
FROM comment
GROUP BY post
) AS pc
ON pc.post = p.id
Perhaps using SUM instead of COUNT to handle the records where there is no join would work, and should perform just as fast:
SELECT post.id,
SUM(IF(likes.id IS NULL,0,1)) as 'LikeAmount',
SUM(IF(comment.id IS NULL,0,1)) as 'commentAmount'
FROM post
LEFT JOIN likes ON post.id = likes.post
LEFT JOIN comment ON post.id = comment.post
GROUP BY post.id
ORDER BY LikeAmount DESC"
Related
i am trying to get all topics based on a forum ID.
Those topics need to be ordered by sticky first and then by last post date secondly.
I have this query, working almost fine but it doesn't order the topics in the way i want.
SELECT
forum_posts.posted_by,
forum_posts.posted,
forum_topics.id,
forum_topics.subject,
forum_topics.sticky,
forum_topics.closed
FROM
forum_posts
LEFT JOIN
forum_topics
ON
forum_topics.id=forum_posts.topic_id
WHERE forum_topics.forum_id=$forumdata->id
GROUP BY forum_topics.id
ORDER BY forum_posts.posted DESC
If I read your question correctly, then you only need to make a slight change to the ORDER BY clause:
ORDER BY
forum_topics.sticky, -- just add this
forum_posts.posted DESC;
However, as you are selecting non aggregate columns, my hunch is that you should really be using a subquery to figure out the latest post:
SELECT
ft.*, fp1.*
FROM forum_posts fp1
INNER JOIN
(
SELECT topic_id, MAX(posted) AS max_posted
FROM forum_posts
GROUP BY topic_id
) fp2
ON fp1.topic_id = fp2.topic_id AND
fp1.posted = fp2.max_posted
LEFT JOIN forum_topics ft
ON fp1.id = ft.topic_id
ORDER BY
ft.sticky;
if you need sticky first an then post then you need order by orum_topics.sticky, forum_posts.posted eg:
SELECT
forum_posts.posted_by,
forum_posts.posted,
forum_topics.id,
forum_topics.subject,
forum_topics.sticky,
forum_topics.closed
FROM
forum_posts
LEFT JOIN
forum_topics
ON
forum_topics.id=forum_posts.topic_id
WHERE forum_topics.forum_id=$forumdata->id
GROUP BY forum_topics.id
ORDER BY forum_topics.sticky DESC, forum_posts.posted DESC
This is my table structure in database
I want to get number of likes for each post by counting post field in likes table and display it for every post using foreach loop.
My question is
Is there a way i can do this with one query with JOIN, GROUP BY and COUNT tables and not create multiple queries.
select p.id, p.title, p.content, count(l.id) as likes_count
from posts p
left join likes l on l.post = p.id
group by p.id, p.title, p.content
http://sqlfiddle.com/#!9/bca8ee/1
SELECT p.*, COUNT(l.id)
FROM posts p
LEFT JOIN likes l
ON l.post = p.id
GROUP BY p.id
I have two tables :
posts : id,title,content,show,created_at
comments: id,post_id,created_at
I'm trying to order posts by most commented.
SELECT *, COUNT(comments.id) AS total_comments
FROM comments LEFT JOIN posts ON posts.id = comments.post_id
WHERE posts.show = '1'
GROUP BY complains.id
ORDER BY total_comments DESC
The problem is that the posts with 0 comments don't appear.
Any help would be much appreciated.
With your join above, you are incorrectly joining to get commens that have posts
You should have done a right join or swap the tables in left join like below.
Select *, COUNT(comments.id) as total_comments
FROM posts
LEFT outer JOIN comments on posts.id = comments.post_id
WHERE posts.show = '1'
GROUP BY posts.id
ORDER BY total_comments DESC
You need to do a RIGHT JOIN instead of a LEFT JOIN. Or swap the tables in the LEFT JOIN clause.
While there are many ways to solve this, I think this code is easy to read and understand.
The query in the LEFT JOIN can be copied out and run on its own to help debug. Then you join that result set to the posts table and order the results.
SELECT p.*, IFNULL(c.total_comments, 0) as total_comments
FROM posts p
LEFT JOIN (select post_id, count(post_id) as total_comments from comments group by post_id) as c ON p.id = c.post_id
WHERE p.show = '1'
ORDER BY c.total_comments DESC
This is my database schema:
Post:
id
title
body
date
Tag:
id
title
Post_Tag:
id
id_post
id_tag
Comment:
id
id_post
body
date
There is a many to many relationship between post and tag.
I need to print in homepage this for the latest 10 posts:
POST_TITLE
POST_BODY
TAG_TITLE_1
TAG_TITLE_2
TAG_TITLE_3
COMMENTS_NUMBER
What is the best query to do that ?
I have tryed this but it doesn't work well because I get multiple rows for each post:
SELECT p.title, p.id, p.date, t.title, t.id, COUNT(c.id)
FROM post p
LEFT JOIN post_tag pt
ON p.id=pt.id_post
LEFT JOIN tag t
ON t.id=pt.id_tag
LEFT JOIN comment c
ON p.id=c.id_post
GROUP BY p.title, p.id, p.date, t.title
ORDER BY p.date DESC
You probably wouldn't want to do this as a single query, but in theory you could do.
SELECT
Post.id AS post_id,
Post.title AS post_title,
Post.body AS post_body,
GROUP_CONCAT(CONCAT(Tag.id, "|", Tag.title) SEPARATOR '#') AS tags,
COUNT(Comment.id) AS comment_count
FROM Post
LEFT JOIN Comment ON Post.id = Comment.id_post
LEFT JOIN Post_Tag ON Post.id = Post_Tag.id_post
LEFT JOIN Tag ON Tag.id = Post_Tag.id_tag
GROUP BY Post.id
ORDER BY Post.date ASC
I haven't checked this as I don't have access to your data, but it should work. You'll need to manually split the tags, which would appear in the format of "ID|TITLE#ID|TITLE", but that's the only extra processing required.
Alternatively, you can avoid the GROUP_CONCAT for tags by splitting this workload between two separate queries:
SELECT
Post.id AS post_id,
Post.title AS post_title,
Post.body AS post_body,
COUNT(Comment.id) AS comment_count
FROM Post
LEFT JOIN Comment ON Post.id = Comment.id_post
GROUP BY Post.id
ORDER BY Post.date ASC
From this, you'd store all of the individual post_ids, and use them in a second query as follows:
SELECT
Tag.id,
Tag.title
FROM Post_Tag
INNER JOIN Tag ON Post_Tag.id_tag = Tag.id
WHERE Post_Tag.id_post IN (**comma-separated list of Post IDs**)
This means you can do two queries, where otherwise you'd have to do one to get all of the posts, then another for EACH of those posts to retrieve the tags - THAT is an N+1 query, whereas what I propose above is a common way around the issue.
If I understand you correctly, you're trying to make a "posts with tags and comments"-system, with the following relationships
One query to rule them all
As suggested by Stephen Orr's answer, you should do a GROUP_CONCAT for the tags to avoid duplicate posts. You'd also have to do some minor post processing to format the tags, unless you just want the tag title, but that's about it.
Here's an example
SELECT
p.ID,
p.title,
p.body,
p.c_date,
GROUP_CONCAT(DISTINCT CONCAT_WS('|', CAST(t.ID AS CHAR), t.title) SEPARATOR ';') AS tags,
COUNT(c.ID) AS comments
FROM Post p
LEFT JOIN Comment c ON p.ID = c.id_post
LEFT JOIN Post_Tag pt ON p.ID = pt.id_post
LEFT JOIN Tag t ON pt.id_tag = t.ID
GROUP BY p.ID, p.title, p.body, p.c_date
ORDER BY p.c_date DESC
Note that I use explicit type-casting on the tag id.
Here are some references to GROUP_CONCAT and CONCAT_WS.
On the home page of my website I want to display the latest posts to the forum however I don't want to show the same topic twice. How can I modify the code below to do this?
http://punbb.informer.com/wiki/punbb13/integration#recent_10_posts
Basically show the latest posts, but only once for each forum topic/thread.
Add a condition to keep only records where the post is the last post in the topic:
WHERE p.id = (
SELECT pp.id
FROM posts AS pp
WHERE pp.topic_id = t.id ORDER BY pp.posted DESC LIMIT 1
)
If you want only one value per topic, you could group by topic, and from each topic select the most recent post. Then, you could choose the top 10 topics.
I'll write it in SQL, and you can translate that to PHP:
SELECT p.id, p.message, o.subject
FROM
((SELECT t.id
FROM posts AS p LEFT JOIN topics AS t ON p.topic_id = t.id
GROUP BY t.id
HAVING p.posted = MAX(p.posted) ) ids LEFT JOIN topics AS t ON ids.id = t.id) o
LEFT JOIN posts AS p ON o.id = posts.topic_id
ORDER BY p.posted DESC
LIMIT '0,10'
change this line
'SELECT' => 'p.id, p.message, t.subject',
to
'SELECT DISTINCT' => 'p.id, p.message, t.subject',