In my previous project I had posts and comments as two tables:
post
id
text
timestamp
userid
comment
id
message
timestamp
userid
postid
Now I've got to design replies to comments. The replies is just one level, so users can only reply to comments, not to replies. The tree structure is only 1 level deep. My first idea was to use the same comment table for both comments and replies. I added a new column though:
comment
id
message
timestamp
userid
postid
parentcommentid
Replies have parentcommentid set to the parent comment they belong. Parent comments don't have it (null)
Retrieving comments for a given post is simple:
but this time I need another query to find out the comment replies. This has to be done for each comment:
This doesn't seem to be a good solution, is there a way to have a single query which returns the complete list of comments/replies in the correct order? (dictated by the timestamp and the nesting)
You may use join and achieve result in single query like I provided below:
SELECT *, cc.message as replied_message
FROM `post`
JOIN comment as c
ON c.postid = post.id
JOIN comment as cc
ON cc.id = c.parentcommentid
ORDER BY c.timestamp DESC, cc.timestamp DESC;
Please note that, it works correctly only if 1 comment have 1 reply only.multiple replies on single comment will not support by this query
I know this reply is years too late, but hopefully it will help others facing this problem now.
I came up with single table and a single query that returns all the results in the correct order that I use on my own site, it's slightly different but the logic could be used to fit this question.
table name : comments
id / varchar(32)
userid / int(10)
comment / text
ordering / int(10)
ordering_secondary / int(10)
source / tinyint(4)
state / tinyint(4)
created / timestamp
edited / timestamp
There is a primary key on (id, ordering, ordering_secondary, source), so no duplicate of these four columns combined will insert.
Inserting a comment you first check "ordering" and increment the new comment by 1.
SELECT ordering FROM comments WHERE id="page id" AND source=0 ORDER BY ordering DESC LIMIT 1
"source" column will be "0" for parent, "1" for a reply for example. So just insert the comment with the ordering value incremented by 1 for each comment on a specific id.
When inserting a reply comment, use the same "ordering" value as the parent but increment the "ordering_secondary" column.
SELECT ordering FROM comments WHERE id="page id" AND source=1 AND ordering="parent comment ordering" ORDER BY ordering DESC LIMIT 1
So the data would look like :
In the table there are two parent comments and two replies to the second parent comment. No replies to first parent comment.
This approach is obviously slightly more overhead on inserts as you have to look up the ordering value of the last comment on an "id" but querying the data is simple.
SELECT * FROM comments WHERE id=? ORDER BY ordering DESC, ordering_secondary ASC LIMIT 30
If you're using a database that supports JSON or object aggregation, you can get a nicer result from the query where each top-level comment is a row (and is not duplicated), and the replies are nested in an array/JSON within each row.
This gives you flexibility with what you do with it and also makes it easier to ensure the ordering and nesting is correct.
An example using Postgres:
SELECT
p.id AS post_id,
c.id AS comment_id,
c.message,
JSON_AGG(
JSON_BUILD_OBJECT('comment', r.comment, 'timestamp', r.timestamp)
ORDER BY r.timestamp
) AS child_comments
FROM
post AS p
INNER JOIN comment AS c
ON c.post_id = p.id
LEFT JOIN comment AS r
ON r.parent_id = c.id
WHERE
post.id = <some id>
AND c.parent_id IS NULL
GROUP BY
post.id,
c.id,
c.message
ORDER BY
c.timestamp DESC
;
Note that, as above, this example will only retrieve the top-level and their first-level replies. It won't get replies to replies. You can use recursive commands or additional subqueries to do that.
Related
I have a 'comment' system that allows for parent comments and also replies.
The following query groups the comments as follows
| Parent Comment Newest
-- Reply
-- Reply
| Parent Comment Oldest
SELECT *
FROM comments c
WHERE c.thred = 50
GROUP BY c.id
ORDER BY
IF(parent_id IS NULL, c.id, parent_id) DESC,
parent_id IS NOT NULL,
c.id ASC
However I want to modify this query to also take into consideration TIME.
I would like the PARENT comments to be sorted by 'last replied time' - so that the comments with the most recent activity are at the top.\
I can modify the code to allow for a 'last_reply' column on the parent post to facilitate that if it makes the query easier - however I am at a loss to how to allow 'time' to factor into this query above.
An example of a system that does this would be Yammer. Posts are sorted by Newest posted but also old posts are pushed back to the top if there have been recent responses/replies.
You know how the Facebook home feed lists all the recent posts? It shows the user that posted, their actual post and then the first few comments attached to that post. That's what I'm trying to achieve, but I'm having a hard time building a single query that can gather all that data.
I have 3 tables: Uses, Posts and Comments. Each has a unique ID, but they reference each other's IDs. i.e, the Comments table has columns for the user_id of the user who posted and the post_id of the post it is attached to.
At the minute I'm querying SQL to gather all the posts. I join my Users and Comments tables to learn the Username of the poster and a total of how many comments the post has, like so:
$query = "
SELECT `posts`.`id`,`posts`.`message`,`posts`.`link`,
`posts`.`posted`,`posts`.`category`,`posts`.`user_id`,
`users`.`username`,
count(`comments`.`id`)
FROM `posts`
INNER JOIN `users`
ON `posts`.`user_id`=`users`.`id`
JOIN `comments`
ON `comments`.`post_id`=`posts`.`id`
WHERE `posts`.`group_id` = '$id'
AND `posts`.`category`='$filter'
GROUP BY `posts`.`id`
ORDER BY `posts`.`posted`
DESC
";
But instead of finding how many comments a post has, I would instead like to read the first few posts. Can anyone think of a way to achieve this with just the one query?
You can use the LIMIT clause to the the "first" posts. By making the "first posts" a subquery and then joining in the comments you can get everything in a single query. The comments should be left-joined in case of posts with no comments.
Notes:
This query is untested, but it should be close.
This will get all comments for the first few posts, so you'll need to limit the display of "max 3 comments per post" using the front-end display code.
It may be possible to limit comments to 3 per post within this query using variables, but that's not something I know how to do.
Here's the query:
SELECT
FirstPosts.id,
FirstPosts.message,
FirstPosts.link,
FirstPosts.posted,
FirstPosts.posts,
FirstPosts.user_id,
FirstPosts.username,
comments.<< your comment column >>
FROM (
SELECT `posts`.`id`,`posts`.`message`,`posts`.`link`,
`posts`.`posted`,`posts`.`category`,`posts`.`user_id`,
`users`.`username`
FROM `posts`
INNER JOIN `users`
ON `posts`.`user_id`=`users`.`id`
WHERE `posts`.`group_id` = '$id'
AND `posts`.`category`='$filter'
ORDER BY `posts`.`posted` DESC
LIMIT 20) FirstPosts
LEFT JOIN comments ON FirstPosts.id = comments.post_id
ORDER BY FirstPosts.Posted, comments.<< column you use to determing comment order >>
If you determine comment order by a date or sequence, you'll have to ORDER BY FirstPosts.Posted, comments.whatever DESC.
Hope this helps!
Can't seem to find a good answer for this. I currently have two tables, one with Facebook posts, the other with comments. I now need to add replies in addition to this, since FB recently did this.
My current query selects from the posts and joins to the comments. What I'm hoping to do for the replies is to add another entry in the comments but with a parent ID. Whatever query I end up having, I would like the results to look like this:
postID commentID parentID
1
2 1
2 2 1
2 3 1
3 4
So post 1 has no comments, post 2 has one comment with two replies to that comment, and post 3 only has one comment. In my comments table, comment 1-4 are all separate entries in the same table. Is there anyway to do this with one query and not having to have another join to the comments table?
Edit, current query. This query doesn't take care of replies, it's only for posts and one level of comments.
select facebookFeeds.*, facebookComments.userID, facebookComments.name, facebookComments.message as cMessage, facebookComments.createdTime as cCreatedTime from facebookFeeds left join facebookComments on facebookFeeds.id = facebookComments.feedID where facebookComments.accountID= 24 order by createdTime desc
I figured it out, have it working using an if in the order clause. The only disadvantage is that the parent comment ID must be a lower number than its replies. Otherwise, the replies will show before the parent comment. When receiving the data, the replies come after the comment, so it should be fine.
select facebookFeeds.*, facebookComments.id as cID, parentID, facebookComments.userID, facebookComments.name, facebookComments.message as cMessage, facebookComments.createdTime as cCreatedTime from facebookFeeds left join facebookComments on facebookFeeds.id = facebookComments.feedID where facebookComments.accountID = 24 order by facebookFeeds.createdTime desc, if(parentID is null, cID, parentID)
I am trying to code a forum website and I want to display a list of threads. Each thread should be accompanied by info about the first post (the "head" of the thread) as well as the last. My current database structure is the following:
threads table:
id - int, PK, not NULL, auto-increment
name - varchar(255)
posts table:
id - int, PK, not NULL, auto-increment
thread_id - FK for threads
The tables have other fields as well, but they are not relevant for the query. I am interested in querying threads and somehow JOINing with posts so that I obtain both the first and last post for each thread in a single query (with no subqueries). So far I am able to do it using multiple queries, and I have defined the first post as being:
SELECT *
FROM threads t
LEFT JOIN posts p ON t.id = p.thread_id
ORDER BY p.id
LIMIT 0, 1
The last post is pretty much the same except for ORDER BY id DESC. Now, I could select multiple threads with their first or last posts, by doing:
SELECT *
FROM threads t
LEFT JOIN posts p ON t.id = p.thread_id
ORDER BY p.id
GROUP BY t.id
But of course I can't get both at once, since I would need to sort both ASC and DESC at the same time.
What is the solution here? Is it even possible to use a single query? Is there any way I could change the structure of my tables to facilitate this? If this is not doable, then what tips could you give me to improve the query performance in this particular situation?
You could do something with a subquery and joins:
SELECT first.text as first_post_text, last.text as last_post_text
FROM
(SELECT MAX(id) as max_id, MIN(id) as min_id FROM posts WHERE thread_id = 1234) as sub
JOIN posts first ON (sub.max_id = first.id)
JOIN posts last ON (sub.min_id = last.id)
But that doesn't solve your problem of doing it without subqueries.
You could add columns to your threads table so that you keep the id of the first and last post of each thread. The first post would never change, but every time you added a new post you would have to update that record in the threads table, so that would double your writes, and you may need to use a transaction to avoid race conditions.
Or you could go so far as to duplicate information about the first and last post in the threads row. Say you needed the user_id of the poster, the timestamp it was posted, and the first 100 characters of the post. You could create 6 new columns in the threads table to contain those pieces of data for the first and last post. It duplicates data, but it means you may be able to display a list of threads without needing to query the posts table at all.
I have two tables Posts and comments
Post Table
Post_id
Post_content
Comments table
comment_id
Comment
post_id
created_date
One post can have multiple comments or zero comments
My requirement is getting the latest comment for the posts using left outer join .
I mean result should be one record for the post with the below columns .
post_id,post_content ,comment_id,comment
In simple words posts should be getting along with their latest comment if it exists.
( Currently the system is getting the posts first and then going to the server again to get the latest comments to display , thought of getting them in one shot since we are displaying only one comment initially ... Not sure what should be the best approach if wants to display more than one comment ..?)
Thank You
Regards
Kiran
SELECT Post.post_id, post_content, comment_id, comment
FROM
Post LEFT JOIN Comments
ON Post.post_id = Comments.post_id
AND created_date = (
SELECT MAX(created_date)
FROM Comments
WHERE Post.post_id = Comments.post_id
)
BTW, you should consider indexing Comments {post_id, created_date} for optimal performance, but be mindful of the non-primary key index overhead in case you are using InnoDB (see the "Disadvantages of clustering" section in this article).