I am implementing a voting system with MySQL and Node.js, it's working good for now, but there is 1 question. I have a table articles with 2 relations upvotes and downvotes.
If I fetch all my articles, I'd like to have the count of upvotes and downvotes. First table is working with
SELECT articles.*, count(downvotes.articles_id)
as downvotes
from articles
left join downvotes
on (articles.id = downvotes.articles_id)
where articles.communities_id = '52'
group by articles.id
ORDER BY created_at
DESC [![Sequel Pro][1]][1]
How can I add the upvotes in the query too?
Thanks!
matz
Same way you added the downvotes. Also, make sure you get into the habit of formatting your SQL, makes it far easier to read and debug.
SELECT
articles.*,
COUNT(downvotes.articles_id) AS downvotes,
COUNT(upvotes.articles_id) AS upvotes
FROM
articles
LEFT JOIN
downvotes ON (articles.id = downvotes.articles_id)
LEFT JOIN
upvotes ON (articles.id = upvotes.articles_id)
WHERE
articles.communities_id = '52'
GROUP BY articles.id
ORDER BY created_at DESC
Add another left join for upvotes
SELECT articles.*, count(downvotes.articles_id) as downvotes
from articles
left join downvotes on (articles.id = downvotes.articles_id)
left join upvotes on (articles.id = upvotes.articles_id)
where articles.communities_id = '52'
group by articles.id
ORDER BY created_at DESC
Okay, a friend posted the correct answer:
SELECT a.id,
(SELECT COUNT(*) FROM downvotes d WHERE a.id=d.articles_id) AS `downs`,
(SELECT COUNT(*) FROM upvotes u WHERE a.id=u.articles_id) AS `ups`
FROM articles a
ORDER BY a.id ASC
He told me the problem is the group at the end. The results from the first left join will be overwritten with the second left join.
Related
I'm currently creating a small application where users can post a text which can be commented and the post can also be voted (+1 or -1).
This is my database:
Now I want to select all information of all posts with status = 1 plus two extra columns: One column containing the count of comments and one column containing the sum (I call it score) of all votes.
I currently use the following query, which correctly adds the count of the comments:
SELECT *, COUNT(comments.fk_commented_post) as comments
FROM posts
LEFT JOIN comments
ON posts.id_post = comments.fk_commented_post
AND comments.status = 1
WHERE posts.status = 1
GROUP BY posts.id_post
Then I tried to additionally add the sum of the votes, using the following query:
SELECT *, COUNT(comments.fk_commented_post) as comments, SUM(votes_posts.type) as score
FROM posts
LEFT JOIN comments
ON posts.id_post = comments.fk_commented_post
AND comments.status = 1
LEFT JOIN votes_posts
ON posts.id_post = votes_posts.fk_voted_post
WHERE posts.status = 1
GROUP BY posts.id_post
The result is no longer correct for either the votes or the comments. Somehow some of the values seem to be getting multiplied...
This is probably simpler using correlated subqueries:
select p.*,
(select count(*)
from comments c
where c.fk_commented_post = p.id_post and c.status = 1
) as num_comments,
(select sum(vp.type)
from votes_posts vp
where c.fk_voted_post = p.id_post
) as num_score
from posts p
where p.status = 1;
The problem with join is that the counts get messed up because the two other tables are not related to each tother -- so you get a Cartesian product.
You want to join comments counts and votes counts to the posts. So, aggregate to get the counts, then join.
select
p.*,
coalesce(c.cnt, 0) as comments,
coalesce(v.cnt, 0) as votes
from posts p
left join
(
select fk_commented_post as id_post, count(*) as cnt
from comments
where status = 1
group by fk_commented_post
) c on c.id_post = p.id_post
left join
(
select fk_voted_post as id_post, count(*) as cnt
from votes_posts
group by fk_voted_post
) v on v.id_post = p.id_post
where p.status = 1
order by p.id_post;
I am working on a forum system (mysql) and I'm not sure which path to choose for better performance when retrieving in a single query posts, up and down votes and if the current user voted for each post.
The first option is this:
SELECT posts.post_id, post_content, display_name,
(SELECT COUNT(post_id) FROM post_votes WHERE post_votes.post_id=posts.post_id AND post_votes.user_id='+user_id+') voted,
(SELECT COUNT(post_id) FROM post_votes WHERE post_votes.post_id=posts.post_id AND up_vote=1) upvotes,
(SELECT COUNT(post_id) FROM post_votes WHERE post_votes.post_id=posts.post_id AND up_vote=0) downvotes
FROM posts JOIN users ON users.user_id=posts.user_id WHERE parent_id ='+parent_id+' ORDER BY post_id DESC
The second option is to replace all the count sub-queries with LEFT JOIN and count.
Are there any advantages to one method over the other?
Edit:
Since I'm looking to retrieve all posts rather than a single row that groups posts, I came up with this query (with some inspiration from here):
SELECT p.post_id, post_content, display_name,
COALESCE(v.upvotes, 0) AS upvotes,
COALESCE(v.downvotes, 0) AS downvotes,
COALESCE(v.voted, 0) AS voted
FROM posts p
LEFT JOIN (
SELECT post_id,
SUM(vt.up_vote = 1) AS upvotes,
SUM(vt.up_vote = 0) AS downvotes,
MAX(IF(vt.user_id = ' + user_id + ', vt.up_vote, NULL)) voted
FROM post_votes vt
GROUP BY vt.post_id
)
v ON v.post_id = p.post_id
JOIN users ON users.user_id=p.user_id
WHERE parent_id =' + parent_id + ' ORDER BY post_id DESC
I have ran both solutions on my demo db (tiny at the moment, contains less than 100 rows in each table) and the durations were identical.
The question is which one will be faster for the long term.
I can hardly think of anything where a subquery was faster than a join.
In this case you don't even need a join. Do it all in one query:
SELECT
p.post_id,
p.post_content,
u.display_name,
COUNT(pv.post_id) AS voted,
SUM(pv.up_vote = 1) AS upvotes,
SUM(pv.up_vote = 0) downvotes
FROM posts p
JOIN users u ON u.user_id = p.user_id
LEFT JOIN post_votes pv ON posts.post_id = pv.post_id AND pv.user_id ='whatever'
WHERE p.parent_id ='+parent_id+'
GROUP BY p.post_id
ORDER BY p.post_id DESC
The pv.up_vote = 'whatever' inside the SUM() function returns either true or false, 1 or 0. That's why we use the SUM() function here. And voila, everything in one query.
I'm a little bit confused about a stupid query:
I get rows from the table posts joined with the table authors and the table comments, in a way like this:
SELECT posts.*, authors.name, COUNT(comments.id_post) AS num_comments
FROM posts JOIN authors ON posts.id_author = authors.id_author
LEFT JOIN comments ON posts.id_post = comments.id_post
WHERE posts.active = 1
AND comments.active = 1
this doesn't work, of course.
What I try to do is to retrieve:
1) all my active post (those that were not marked as deleted);
2) the names of their authors;
3) the number of active comments (those that were not marked as deleted) for each post (if there is at least one);
What's the way? I know it's a trivial one, but by now my brain is in offside…
Thanks!
Presumably, id_post uniquely identifies each row in posts. Try this:
SELECT p.*, a.name, COUNT(c.id_post) AS num_comments
FROM posts p JOIN
authors a
ON p.id_author = a.id_author LEFT JOIN
comments c
ON p.id_post = c.id_post
WHERE p.active = 1 AND c.active = 1
GROUP BY p.id_post;
Note that this uses a MySQL extension. In most other databases, you would need to list all the columns in posts plus a.name in the group by clause.
EDIT:
The above is based on your query. If you want all active posts with a count of active comments, just do:
SELECT p.*, a.name, SUM(c.active = 1) AS num_comments
FROM posts p LEFT JOIN
authors a
ON p.id_author = a.id_author LEFT JOIN
comments c
ON p.id_post = c.id_post
WHERE p.active = 1
GROUP BY p.id_post;
Since you are doing a count, you need to have a group by. So you will need to add
Group By posts.*, authors.name
You should you GROUP BY clause together with aggregate functions. Try something similar to:
SELECT posts.*, authors.name, COUNT(comments.id_post) AS num_comments
FROM posts JOIN authors ON posts.id_author = authors.id_author
LEFT JOIN comments ON posts.id_post = comments.id_post
-- group by
GROUP BY posts.*, authors.name
--
WHERE posts.active = 1
AND comments.active = 1
I found the correct solution:
SELECT posts.id_post, authors.name, COUNT(comments.id_post) AS num_comments
FROM posts JOIN authors
ON posts.id_author = authors.id_author
LEFT OUTER JOIN comments
ON (posts.id_post = comments.id_post AND comments.active = 1)
WHERE posts.active = 1
GROUP BY posts.id_post;
Thanks everyone for the help!
Basically I want to select all the posts that have at least one comment and get the comment count for each. What I cam up with so far is only giving me one result, which is has an accurate count but there are more than one posts that have comments. Can any recommend changes to make this work
SELECT
posts.id,
COUNT(DISTINCT comments.post_id) AS count
FROM
posts
LEFT JOIN comments ON posts.id = comments.post_id
Using aggregate functions you must group them see here GROUP BY (Aggregate) Functions for the posts must contain atleast one comment you can use HAVING count >= 1
SELECT
posts.id,
COUNT(DISTINCT comments.post_id) AS `count`
FROM
posts
LEFT JOIN comments ON posts.id = comments.post_id
GROUP BY posts.id
HAVING `count` >= 1
You need a group by statement. And you can change the join to an inner join because you want only posts with comments:
SELECT p.id, COUNT(*) AS count
FROM posts p INNER JOIN
comments c
ON p.id = c.post_id
GROUP BY p.id;
The expression count(distinct c.post_id) will return 1 for each row, because there is only one distinct post id for each row. COUNT(*) will get the number of comments.
I have following tables
questions -> id, question_data, user_id
users -> id, fname, lname
question_connect-> id, question_id, user_id
My initial query was as follows
select questions.id, questions.question_data, users.id, users.fname from questions, users where questions.user_id = users.id limit 30
But over here, i want count of users on that question, so i tried following query
select questions.id, questions.question_data, users.id, users.fname, count(questions_connect.id) from questions, users LEFT JOIN questions_connect ON `questions`.`id` = `questions_connect`.`question_id` where questions.user_id = users.id group by `questions_connect`.`id` limit 30
this shows error
Unknown column 'questions.id' in 'on clause'
SO can we make 1 call with natural join and left join and if yes, where i am going wrong..?
Using an explicit join should sort you out:
select questions.id, questions.question_data, users.id, users.fname, count(questions_connect.id)
from questions
join users on questions.user_id = users.id
left join questions_connect on `questions`.`id` = `questions_connect`.`question_id`
group by `questions_connect`.`id`
limit 30
You're better off specifying all your joins explicitly, try to forget that implicit joins exist.
I believe you don't need to put the quotes on the joins
ON questions.id = questions_connect.question_id