Strange order of results when adding joins - mysql

I'm trying to build a commenting system on my website but having issues with ordering the comments correctly. This is a screenshot of what I had before it went wrong:
And this is the query before it went wrong:
SELECT
com.comment_id,
com.parent_id,
com.is_reply,
com.user_id,
com.comment,
com.posted,
usr.username
FROM
blog_comments AS com
LEFT JOIN
users AS usr ON com.user_id = usr.user_id
WHERE
com.article_id = :article_id AND com.moderated = 1 AND com.status = 1
ORDER BY
com.parent_id DESC;
I now want to include each comment's votes from my blog_comment_votes table, using a LEFT OUTER JOIN, and came up with this query, which works, but screws with the order of results:
SELECT
com.comment_id,
com.parent_id,
com.is_reply,
com.user_id,
com.comment,
com.posted,
usr.username,
IFNULL(c.cnt,0) votes
FROM
blog_comments AS com
LEFT JOIN
users AS usr ON com.user_id = usr.user_id
LEFT OUTER JOIN (
SELECT comment_id, COUNT(vote_id) as cnt
FROM blog_comment_votes
GROUP BY comment_id) c
ON com.comment_id = c.comment_id
WHERE
com.article_id = :article_id AND com.moderated = 1 AND com.status = 1
ORDER BY
com.parent_id DESC;
I now get this order, which is bizarre:
I tried adding a GROUP BY clause on com.comment_id but that failed too. I can't understand how adding a simple join can alter the order of results! Can anybody help back on the correct path?
EXAMPLE TABLE DATA AND EXPECTED RESULTS
These are my relevant tables with example data:
[users]
user_id | username
--------|-----------------
1 | PaparazzoKid
[blog_comments]
comment_id | parent_id | is_reply | article_id | user_id | comment
-----------|-----------|----------|------------|---------|---------------------------
1 | 1 | | 1 | 1 | First comment
2 | 2 | 1 | 1 | 20 | Reply to first comment
3 | 3 | | 1 | 391 | Second comment
[blog_comment_votes]
vote_id | comment_id | article_id | user_id
--------|------------|------------|--------------
1 | 2 | 1 | 233
2 | 2 | 1 | 122
So the order should be
First comment
Reply to first comment +2
Second Comment

It's difficult to say without looking at your query results, but my guess is that it's because you are only ordering by parent id and not saying how to order when two records have the same parent id. Try changing your query to look like this:
SELECT
com.comment_id,
com.parent_id,
com.is_reply,
com.user_id,
com.comment,
com.posted,
usr.username,
COUNT(c.votes) votes
FROM
blog_comments AS com
LEFT JOIN
users AS usr ON com.user_id = usr.user_id
LEFT JOIN
blog_comment_votes c ON com.comment_id = c.comment_id
WHERE
com.article_id = :article_id AND com.moderated = 1 AND com.status = 1
GROUP BY
com.comment_id,
com.parent_id,
com.is_reply,
com.user_id,
com.comment,
com.posted,
usr.username
ORDER BY
com.parent_id DESC, com.comment_id;

Related

Mysql join tables without repeating rows which belong to same row

I've been trying to figure this out for hours. But with no luck.
This works perfectly, but the problem i got with these. For e.g if same report has more than 1 comment, then this will create new row instead of unite the comments with same row with the report.
How it's now:
{"text":"My first report","comment":"Great Report","display_name":"Xavier"},
{"text":"My First report","comment":"Do you call this a report?","display_name":"Logan"}
How i would like it to be:
{"text":"My first report","comments":[{comment: "Great Report","display_name":"Xavier"}, {comment: "Do you call this a report?","display_name":"Logan"}],
Current Setup
Report
ID | User_ID | TEXT |
15 3 My first report
Users
ID | DISPLAY_NAME |
1 Xavier
2 Logan
3 Cyclops
Report_Comments
ID | User_ID | Report_ID | TEXT as comment |
3 1 15 Great Report
4 2 15 Bad Report
How it should be:
Report_Comments
ID | User_ID | Report_ID | TEXT as comment |
3 1, 2 15 Great Report, Bad Report
SELECT report.text,
report_comments.text AS comment,
users.display_name
FROM report
LEFT JOIN users
ON users.id = report.user_id
LEFT JOIN report_comments
ON report_comments.report_id = report.id
WHERE report.user_id = :userId
You can do it if you group by report and use GROUP_CONCAT() for user ids and names and comment texts:
SELECT r.text,
GROUP_CONCAT(c.user_id ORDER BY c.ID) AS User_ID,
GROUP_CONCAT(u.display_name ORDER BY c.ID) AS User_Name,
r.id,
GROUP_CONCAT(c.text) AS comment
FROM report r
LEFT JOIN report_comments c ON c.report_id = r.id
LEFT JOIN users u ON u.id = c.user_id
-- WHERE report.user_id = :userId
GROUP BY r.id, r.text
See the demo.
Results:
> text | User_ID | User_Name | id | comment
> :-------------- | :------ | :----------- | :- | :----------------------
> My first report | 1,2 | Xavier,Logan | 15 | Bad Report,Great Report

MySQL Sort users by highest total count from counts on multiple tables

Similar to stackoverflow, I have a database of users who vote, comment, and make other actions. I am trying to return a sorted result of the top 10 users who have made the most actions based on the combined count of all of the actions a user has made, along with the actual count of total actions said user made.
Below is my table structure.
Users Table
Typical users data such as an incrementing id, username, email, etc.
| id | username |
-----------------
| 1 | bob |
| 2 | jane |
Votes Table
Has an incrementing id, user_id fk and type of vote made.
| id | user_id | type |
| 1 | 1 | up_vote |
| 2 | 2 | up_vote |
Comments Table
Same as the votes table, typical stuff here.
| id | user_id | comment |
---------------------------------
| 1 | 1 | hello, world |
| 1 | 1 | goodbye, world |
Intended results:
results needed
| total_actions | user_id | username |
-------------------------------------|
| 3 | 1 | bob |
| 1 | 2 | jane |
What I actually know how to do, albeit probably not the most efficient way...
Users sorted by most votes, along with the count
select `users`.*,
(
select count(*)
from `votes`
where `users`.`id` = `votes`.`user_id`
) as `votes_count`
from `users`
order by `votes_count` desc
limit 10
Users sorted by most comments, along with the count
select `users`.*,
(
select count(*)
from `comments`
where `users`.`id` = `comments`.`user_id`
) as `comments_count`
from `users`
order by `comments_count` desc
limit 10
Any help would be greatly appreciated!
You can left join aggregate queries that compute the total votes and comments per user, and sort in the outer query, like so:
select
coalesce(v.cnt, 0) + coalesce(c.cnt, 0) total_actions,
u.id,
u.username
from users u
left join (select user_id, count(*) cnt from votes group by user_id) v
on v.user_id = u.id
left join (select user_id, count(*) cnt from comments group by user_id) c
on c.user_id = u.id
order by total_actions desc
limit 10
While I prefer GMB's method (using LEFT JOIN with each subquery) I'll show here how to combine your existing queries. Just use both correlated subqueries, and add them together to get the total.
select `users`.*,
(
select count(*)
from `votes`
where `users`.`id` = `votes`.`user_id`
) +
(
select count(*)
from `comments`
where `users`.`id` = `comments`.`user_id`
) as total_actions
from `users`
order by total_actions desc
limit 10

How to optimize SQL query more?

First at all i am nood into SQL thing, Now i am working on a class project where
I have some tables like
Table user
user_id | username | name
1 | nihan | Nihan Dip
2 | dip | Meaw ghew
more | more | more
Table Friend
you | friend_id
1 | 2
1 | 27
2 | 9
more | more
Table Follow
user_id | follows
1 | 99
7 | 34
Table post
post_id | user_id | type | content | post_time
1 | 1 | text | loren toren | timestamp
2 | 2 | text | ipsum | timestamp
Now i want to get post by users friend and who he follows and offcourse his so i made this SQL
SELECT
username, name,content, post_time
FROM
post
INNER JOIN
user ON user.user_id = post.user_id
WHERE
post.user_id IN (SELECT
friend_id
FROM
friend
WHERE
you = 1
UNION ALL
SELECT
follows
FROM
follow
WHERE
user_id = 1)
OR post.user_id = 1
ORDER BY post_time DESC
LIMIT 10
this query works just fine. I just wanted to know is there anymore optimization could be done? Then how? Please teach me :)
Instead of using IN try it with JOIN add add few more indexes.
SELECT DISTINCT u.name, u.username,
p.content, p.post_time
FROM post p
INNER JOIN user u
ON u.user_id = p.user_id
INNER JOIN
(
SELECT friend_id id
FROM friend
WHERE you = 1
UNION ALL
SELECT follows id
FROM follow
WHERE user_id = 1
) s ON p.user_id = s.ID
ORDER BY post_time DESC
LIMIT 10

Mysql multiple join with optional value in ON clause

Ok, First see this http://www.sqlfiddle.com/#!2/bbc3a/2
Here the query return the count and all, but what i want is that it selects a forum and return the count of topics and post in it then next forum and all topics and post counts from it and so on with forums name, url and desc. I thought doing this in one query instead of many queries in loop would be better. I don't know what i am doing with that mysql query though, i am not very good with mysql and i haven't sleep for so long. Thanks for help.
EDIT:
This is what i expect to see from the data provided in te fiddle above^
| FORUMNAME | FORUMURL | FORUMDESC | FORUMTIME | TOPICCOUNT | POSTCOUNT |
| Forum 1 | Forum-1 | Forum 1 Desc | 343243243 | 1 | 1 |
| Forum 2 | Forum-2 | Forum 2 Desc | 343243243 | 0 | 0 |
You just need to add a GROUP BY clause
SELECT f.Name ForumName,
f.Url ForumUrl,
f.Desc ForumDesc,
f.Time ForumTime,
COUNT(DISTINCT t.ID) TopicCount,
COUNT(DISTINCT p.ID) PostCount
FROM name_forums f
LEFT JOIN name_topics t ON t.ForumId = f.ID
LEFT JOIN name_posts p ON p.TopicId = t.ID
WHERE f.CategoryId = 1
GROUP BY f.name, f.url, f.desc
Here is SQLFiddle demo

Get latest entry and join another table

I'm trying to get the username and timestamp of the most recent post where topicID is, for example, 88.
Users
id | username
--------|----------
45234 | kaka
32663 | lenny
52366 | bob
Posts
id | message | topicID | timestamp | userID
--------|-----------|---------|-----------|-------
675 | hello | 88 | 100 | 32663
676 | hey | 88 | 200 | 45234
677 | howdy | 88 | 300 | 52366
So here I would want postID 677 and user bob.
Can I do this in a single sql query?
Would be great if I could implent it into this:
SELECT topics.id, topics.subject, topics.forum_id
FROM topics WHERE topics.forumID = 16
Assuming that table Topic is linked with table Post by Topic.ID = Post.TopicID and you want to get the latest post associated with it, you can have a subquery which basically gets the latest id (assuming it's set as auto-incremented column) for each topicID and join the result on table Post to get the other columns. Also you need to join on table User in order to get the name of the user who posted the entry.
SELECT a.id,
a.subject,
a.forumid,
b.message,
b.timestamp,
d.username
FROM topic a
INNER JOIN Posts b
ON a.id = b.topicID
INNER JOIN
(
SELECT topicID, MAX(id) id
FROM Posts
GROUP BY topicID
) c ON b.topicID = c.topicID AND
b.id = c.ID
INNER JOIN users d
ON b.userID = d.id
WHERE a.forumID = 16
if you remove the WHERE clause, you will get all the latest entry for each forumID.
SQLFiddle Demo
Untested, but off the top of my head, I think the following query will get you what you want:
SELECT Users.username, Posts.timestamp
FROM Users JOIN Posts on Users.id = Posts.userID
WHERE Posts.topicID = 88
ORDER BY Posts.timestamp DESC
LIMIT 1