I'm building some very simple forum software as a sort of self-test for my PHP and MySQL skills, but I'm not quite sure how to accomplish this task. I have a table called Threads which contains a list of all threads. I also have a table called Posts, which contains all posts. Each table's primary key is an auto-increment ID. Each row in Posts also contains the ID of the thread it belongs to.
While I can easily retrieve all of the threads in a certain subforum with a query like this:
SELECT * FROM Threads WHERE ForumID='$forumid'
...I'm not sure how to sort that based on the latest post from each thread. I can get the posts for any given thread like this:
SELECT * FROM Posts WHERE ThreadID='$threadid'
...but I don't know how to incorporate that data into my initial query to sort it. Since each post has a unique ID, posts with higher IDs will always be more recent, so there's no need to compare dates or anything like that. I'm just unclear on how to actually do the query.
I'm pretty sure that this is possible in just MySQL, but if it's not, what would be the most efficient PHP solution? Thanks!
Try this with INNER JOIN
SELECT p.*,t.*
FROM Threads t
INNER JOIN Posts p ON t.id = p.ThreadID
ORDER BY
//whatever column you want like this p.date
p.date
DESC
Thomething like this can helps you
SELECT DISTINCT t.* FROM Posts p LEFT JOIN Threads t ON p.ThreadID = t.id ORDER BY p.last_modified_time DESC
Or
SELECT DISTINCT t.* FROM Posts p LEFT JOIN Threads t ON p.ThreadID = t.id ORDER BY p.id DESC
A simple JOIN statement is the solution:
SELECT *
FROM Threads t
JOIN Posts p ON t.ThreadID = p.ThreadID
ORDER BY t.ModificationDate DESC -- or whatever column you want
This gives you the latest posts over all threads.
All of the given answers were very close, but I ended up using the following query:
SELECT t.*
FROM Threads t
INNER JOIN Posts p ON t.id = p.threadId
GROUP BY t.id
ORDER BY MAX(p.id) DESC
This is due to DUPLICATE causing problems due to its auto-grouping. See this answer for more info on the topic.
Related
I'm putting together a simple forum script with topics/posts. I'm trying to order topics by latest post and I've had some luck with the answer in the following question: MYSQL Order from another Table, but the problem I have is that some topics don't have any posts yet, so the query doesn't select those topics at all.
Current coding is:
SELECT DISTINCT forum.*
FROM forum
INNER JOIN forum_posts
ON forum.id = forum_posts.parent_id
GROUP BY forum.id
ORDER BY forum_posts.parent_id DESC,
forum_posts.time_created DESC
LIMIT ?,?
I want to tell the ORDER BY to order by forum.time_created if there are no matches in forum_posts.parent_id for a topic.
On a side note, I would also like to know how to put a WHERE clause into this query as well. I want to only get rows from forum table "WHERE access <= ?", but can't work out where to put that snippet in.
Any help much appreciated!
EDIT:
Target is to return topics (from forum table)
According to following details:
WHERE forum.access <= ?
LIMIT ?,?
ORDER BY
Latest Post From forum_posts table with forum_posts.parent_id matching on forum.id,
or forum.time_created
EDIT 2:
An example SQLfiddle with relevant data. This doesn't work as the order should really come out as 11,10,9,1,2
http://sqlfiddle.com/#!2/83535/2
Have a look at this: http://sqlfiddle.com/#!2/be909/1
Here's my final query:
SELECT forum.*, recent_forum_posts.time_created as latest_post
FROM forum
LEFT JOIN (SELECT MAX(forum_posts.time_created) as time_created, forum_posts.parent_id
FROM forum_posts
GROUP BY forum_posts.parent_id) AS recent_forum_posts
ON forum.id = recent_forum_posts.parent_id
WHERE forum.access > 2
ORDER BY recent_forum_posts.time_created IS NULL DESC,
recent_forum_posts.time_created DESC,
forum.time_created DESC
LIMIT 5
Essentially, the subquery (the bit in brackets after LEFT JOIN) selects the most recent post for each parent_id from the forum_posts table.
That is then LEFT JOINed (as we want to list all forum's even if there are no posts yet).
I have created a simple blog in php/mysql that show in homepage latest 100 posts and for every post show the comments's number.
This is the pseudocode:
Mysql query to get latest 100 posts.
While cicle:
Get title and body of each post.
Mysql query to get the comments's number of the post.
Database structure:
Post:
-id
-title
-body
-date
Comments:
-id
-id_post
-id_user
-body
-date
Is there a way to avoid 100 queries ?
It's a fairly straightforward query:
SELECT p.id, p.title, p.body. p.date, COUNT(c.id) AS comment_count
FROM Post p
LEFT JOIN Comments c ON p.id=c.id_post
GROUP BY p.id, p.title, p.body. p.date
ORDER BY p.id DESC
LIMIT 100
(Please note it's untested, take it as a starting point.)
I know it's a common believe that a database is nothing but a fancy file system and SELECT * FROM data is all the SQL you'll ever need to know but investing some time in learning basic SQL it's absolutely worth the effort.
You can write a sql query that JOINS both of your tables together, to return all of the information you want:
SELECT
p.id,
p.title,
p.body,
p.date,
COUNT(c.id)
FROM Post p
LEFT JOIN Comments c ON p.id = C.post_id
GROUP BY p.id, p.title, p.body, p.date
ORDER BY p.date DESC
LIMIT 100
Jeff Atwood has a great visual guide explaining how joins work.
We are effectively selecting the top 100 posts in date order, counting the total comments (if they exist - that's why we use a LEFT JOIN as otherwise, if we use an INNER JOIN, we will ONLY return posts that have comments).
We GROUP BY as that's how COUNT (and other aggregation functions like SUM and AVG work - we need to tell them what the rule is for counting/summing/averaging our rows.
We ORDER BY the post date, in DESCending order (latest first) to ensure we return posts in the order they were made.
The LIMIT 100 statement only returns the first 100 rows in MySql. Change it to any number you wish (a top 10, 50 etc) if you want to vary the number of posts in the summary.
The overall result is that you now have a result set that contains the post information, and the number of comments, all in one query. You can then display these results in your web application any way you see fit.
Just join your tables and then count the number of comments for that post.
SELECT p.id, p.title, p.body, COUNT(pc.id) as comments
FROM Post p
LEFT JOIN Comments pc ON (p.id = pc.id_post)
GROUP BY pc.id_post LIMIT 100
Hope that is what you are after.
Join your posts table with your posts-info tables, and select them at the same time
select p.id,pi.comments from posts as p, post_info as pi where p.id == pi.id
or something like that.
EDIT:
select p.title,p.body,p.date,count(c.id) from post as p, comments as c where p.id == c.id_post
I believe.
I know this question has been asked multiple times (however, I could still not find a solution):
PHP MYSQL showing posts with comments
mysql query - blog posts and comments with limit
mysql structure for posts and comments
...
Basic question: having tables posts, comments, user... can you with one single select statement select and show all posts and all comments (with comment.user, comment.text, comment.timestamp)? How would such a select statement look like? If not, what is the easiest solution?
I also tried to JOIN the comments table with the posts table and use GROUP BY, but I got either only one comment in each row or each comment but also those posts multiple times!?
I tried the solution of the first link (nested mysql_query and then fetch) as well as the second link (with arrays). However, the first caused a bunch of errors (the syntax in that post seems to be not correct and I could not figure out how to solve it) and in the second I had problems with the arrays.
My query looks like this till now:
SELECT p.id, p.title, p.text, u.username, c.country_name, (SELECT SUM(vote_type) FROM votes v WHERE v.post_id = p.id) AS sum_vote_type FROM posts p LEFT JOIN user u ON ( p.user_id = u.id ) LEFT JOIN countries c ON ( c.country_id = u.country_id ) ORDER BY $orderby DESC
I was wondering if this issue was not very common, having posts and comments to show...?
Thank you for every help in advance!
Not knowing your database structure, it should look something like this. Note that you should replace the * characters with more explicit lists of columns you actually need.
SELECT p.*, c.*, u.* FROM posts p
LEFT JOIN comments c ON c.post_id = p.id
LEFT JOIN users u ON u.id = p.author_id
Note that if you're just trying to get counts, sums and things like that it's a good idea to cache some of that information. For instance, you may want to cache the comment count in the post table instead of counting them every query. Only count and update the comment count when adding/removing a comment.
EDIT:
Realized that you also wanted to attach user data to each comment. You can JOIN the same table more than once but it gets ugly. This could turn into a really expensive query. I also am including an example of how to alias columns so it's less confusing:
SELECT p.*, c.*, u.name as post_author, u2.name as comment_author FROM posts p
LEFT JOIN comments c ON c.post_id = p.id
LEFT JOIN users u ON u.id = p.author_id
LEFT JOIN users u2 ON u2.id = c.author_id
I'm working on building a forum with kohana. I know there is already good, free, forum software out there, but it's for a family site, so I thought I'd use it as a learning experience. I'm also not using the ORM that is built into Kohana, as I would like to learn more about SQL in the process of building the forum.
For my forum I have 4 main tables:
USERS
TOPICS
POSTS
COMMENTS
TOPICS table: id (auto incremented), topic row.
USERS table: username, email, first and last name and a few other non related rows
POSTS table: id (auto incremented), post-title, post-body, topic-id, user-id, post-date, updated-date, updated-by(which will contain the user-id of the person who made the most recent comment)
COMMENTS table: id (auto incremented), post-id, user-id and comment
On the main forum page I would like to have:
a list of all of the topics
the number of posts for each topic
the last updated post, and who updated it
the most recently updated topic to be on top, most likely an "ORDER BY updated-date"
Here is the query I have so far:
SELECT topics.id AS topic-id,
topics.topic,
post-user.id AS user-id,
CONCAT_WS(' ', post-user.first-name, post-user.last-name) AS name,
recent-post.id AS post-id,
post-num.post-total,
recent-post.title AS post-title,
recent-post.update_date AS updated-date,
recent-post.updated-by AS updated-by
FROM topics
JOIN (SELECT posts.topic-id,
COUNT(*) AS post-total
FROM POSTS
WHERE posts.topic-id = topic-id
GROUP BY posts.topic-id) AS post-num ON topics.id = post-num.topic-id
JOIN (SELECT posts.*
FROM posts
ORDER BY posts.update-date DESC) AS recent-post ON topics.id = recent-post.topic-id
JOIN (SELECT users.*,
posts.user-id
FROM users, posts
WHERE posts.user-id = users.id) as post-user ON recent-post.user_id = post-user.id
GROUP BY topics.id
This query almost works as it will get all of information for topics that have posts. But it doesn't return the topics that don't have any posts.
I'm sure that the query is inefficient and wrong since it makes two sub-selects to the posts table, but it was the only way I could get to the point I'm at.
Dash is not a valid character in SQL identifiers, but you can use "_" instead.
You don't necessarily have to get everything from a single SQL query. In fact, trying to do so makes it harder to code, and also sometimes makes it harder for the SQL optimizer to execute.
It makes no sense to use ORDER BY in a subquery.
Name your primary key columns topic_id, user_id, and so on (instead of "id" in every table), and you won't have to alias them in the select-list.
Here's how I would solve this:
First get the most recent post per topic, with associated user information:
SELECT t.topic_id, t.topic,
u.user_id, CONCAT_WS(' ', u.first_name, u.last_name) AS full_name,
p.post_id, p.title, p.update_date, p.updated_by
FROM topics t
INNER JOIN
(posts p INNER JOIN users u ON (p.updated_by = u.user_id))
ON (t.topic_id = p.topic_id)
LEFT OUTER JOIN posts p2
ON (p.topic_id = p2.topic_id AND p.update_date < p2.update_date)
WHERE p2.post_id IS NULL;
Then get the counts of posts per topic in a separate, simpler query.
SELECT t.topic_id, COUNT(*) AS post_total
FROM topics t LEFT OUTER JOIN posts p USING (topic_id)
GROUP BY t.topic_id;
Merge the two data sets in your application.
to ensure you get results for topics without posts, you'll need to use LEFT JOIN instead of JOIN for the first join between topics and the next table. LEFT JOIN means "always return a result set row for every row in the left table, even if there's no match with the right table."
Gotta go now, but I'll try to look at the efficiency issues later.
This is a very complicated query. You should note that JOIN statements will limit your topics to those that have posts. If a topic does not have a post, a JOIN statement will filter it out.
Try the following query.
SELECT *
FROM
(
SELECT T.Topic,
COUNT(AllTopicPosts.ID) NumberOfPosts,
MAX(IFNULL(MostRecentPost.Post-Title, '') MostRecentPostTitle,
MAX(IFNULL(MostRecentPostUser.UserName, '') MostRecentPostUser
MAX(IFNULL(MostRecentPost.Updated_Date, '') MostRecentPostDate
FROM TOPICS
LEFT JOIN POSTS AllTopicPosts ON AllTopicPosts.Topic_Id = TOPICS.ID
LEFT JOIN
(
SELECT *
FROM Posts P
WHERE P.Topic_id = TOPICS.id
ORDER BY P.Updated_Date DESC
LIMIT 1
) MostRecentPost ON MostRecentPost.Topic_Id = TOPICS.ID
LEFT JOIN USERS MostRecentPostUser ON MostRecentPostUser.ID = MostRecentPost.User_Id
GROUP BY T.Topic
)
ORDER BY MostRecentPostDate DESC
I'd use a left join inside a subquery to pull back the correct topic, and then you can do a little legwork outside of that to get some of the user info.
select
s.topic_id,
s.topic,
u.user_id as last_updated_by_id,
u.user_name as last_updated_by,
s.last_post,
s.post_count
from
(
select
t.id as topic_id,
t.topic,
t.user_id as orig_poster,
max(coalesce(p.post_date, t.post_date)) as last_post,
count(*) as post_count --would be p.post_id if you don't want to count the topic
from
topics t
left join posts p on
t.id = p.topic_id
group by
t.topic_id,
t.topic,
t.user_id
) s
left join posts p on
s.topic_id = p.topic_id
and s.last_post = p.post_date
and s.post_count > 1 --0 if you're using p.post_id up top
inner join users u on
u.id = coalesce(p.user_id, s.orig_poster)
order by
s.last_post desc
This query does introduce coalesce and left join, and they are very good concepts to look into. For two arguments (like used here), you can also use ifnull in MySQL, since it is functionally equivalent.
Keep in mind that that's exclusive to MySQL (if you need to port this code). Other databases have other functions for that (isnull in SQL Server, nvl in Oracle, etc., etc.). I used coalesce so that I could keep this query all ANSI-fied.
I'm having trouble forming a MySQL query that performs the following action:
Select all threadIDs from the threads table ordering by the timestamp (in descending order) of the most recent post (largest timestamp) with threadID equal to the threadID of each thread. So basically I want to go through the threads and have MySQL check the database for let's say thread 0. It then checks all of the posts that have threadID of 0 and sorts thread0 based on the largest timestamp of the posts inside of thread0. Then it repeats this for thread1, thread2, etc and sorts them accordingly.
Is this even possible? It is to create the "bump-system" effect of the forum, where the most recently active thread is bumped to the top of the list continuously until the thread dies out, then it drops to the bottom. I used to use a different implementation where I stored a lastActivity timestamp in the threads table and updated it when a new post was submitted into the thread, but this query would make things a lot more efficient.
Thanks a lot! There are two tables relevant here: threads and posts. The posts have a threadID field that stores the ID of the thread it belongs to, and it also has a timestamp field. Threads has a field threadID that corresponds to the post's threadID.
The following has worked for me in the past on MySql. If you want to include more about each thread in the query you'll have to add the columns to the SELECT and the GROUP BY.
select thread.threadID, max(comments.modifiedOn) as threadUpdated
from thread inner join comments on thread.threadID = comments.threadID
group by 1
order by 2 desc;
This query serves your primary request, which is a list of threads ordered my most recent comment. It will not return threads with no comments as-is, you would need to change the join to an outer join to do that.
SELECT *
FROM threads t
LEFT JOIN
posts p
ON p.id =
(
SELECT p.id
FROM posts pi
WHERE pi.threadID = t.threadID
ORDER BY
pi.timestamp DESC
LIMIT 1
)
Having an index on posts (threadID, timestamp) will greatly improve this query.
Note that unlike GROUP BY solutions, this query also selects all fields from posts and works even if you have duplicates on the latest posts.timestamp.
SELECT t.*, p1.`timestamp`
FROM threads t
JOIN posts p1 ON (p1.threadid = t.id)
LEFT JOIN posts p2 ON (p2.threadid = t.id
AND p1.`timestamp` < p2.`timestamp`)
WHERE p2.threadid IS NULL
ORDER BY p1.`timestamp` DESC;