Posts, comments and advanced sql filtering - mysql

I have a database table of posts. I have another db table with comments, with a column for comment_id (ai) and a column for the post_id it's attached to.
I want to make a query list all of my posts once, sorted by last posted comment.
The problem is I can't figure out the sql query to do it. Whatever I try I get all the comments listed too. I'm very skilled or experienced with mysql, but I tried using different "joins". Is there any way to do it?

SELECT posts.*, comments.last_one
FROM posts
LEFT JOIN
(SELECT MAX(updated) as last_one, post_id
FROM comments
GROUP BY post_id
) as comments
ON comments.post_id = posts.id
ORDER BY comments.last_one DESC

SELECT distinct p.* FROM posts as p, comments as c
WHERE p.post_id = c.post_id ORDER BY c.time DESC
I just assumed your relational schema. You might need to adapt the query.
Alternative: create a view with the last_comment_time as a field.
CREATE VIEW posts AS
(SELECT *, (SELECT time FROM comments WHERE post_id = p.post_id ORDER BY time DESC LIMIT 1) as last_comment_time
FROM posts as p);

Related

I want to get all posts which has more than 250 likes(likes are stored in different tabe)

I have two mysql database table's posts and post_likes...I want to get all posts which has more than 250 likes
right now i am using this query :-
SELECT posts.*, #total_likes := COUNT(nft_likes.id) as total_likes FROM posts inner join nft_likes on nft_likes.nft_id=posts.auction_id where #total_likes>1 group by posts.id
This is the first time i have asked a question.so pls forgive for bad way of telling
post_likes table schema
post table schema
In the WHERE clause you can only refer to a row's data. The result of a COUNT, however, refers to the aggregation of several rows. Use the HAVING clause for limitations on these results.
SELECT
p.*,
COUNT(l.id) AS total_likes
FROM posts p
INNER JOIN nft_likes l ON l.nft_id = p.auction_id
GROUP BY p.id
HAVING COUNT(l.id) > 1
ORDER BY p.id;

Avoiding the “n+1 selects” problem when I want a subset of related resources

Imagine I'm designing a multi-user blog and I have user, post, and comment tables with the obvious meanings. On the main page, I want to show the ten most recent posts along with all their related comments.
The naive approach would be to SELECT the ten most recent posts, probably JOINed with the users that wrote them. And then I can loop through them to SELECT the comments, again, probably JOINed with the users that wrote them. This would require 11 selects: 1 for the posts and 10 for their comments, hence the name of the famous anti-pattern: n+1 selects.
The usual advice for avoiding this anti-pattern is to use the IDs from the first query to fetch all related comments in a second query which may look something like this:
SELECT
*
FROM
comments
WHERE
post_id IN (/* A comma separated list of post IDs returned from the first query */)
As long as that comma separated list is in reasonably short we managed to fetch all the data we need by issuing only two SELECT queries instead of eleven. Great.
But what if I only want the top three comments for each post? I didn't try but I can probably come up with some LEFT JOIN trickery to fetch the most recent posts along with their top three comments in a single query but I'm not sure it would be scalable. What if I want the top hundred comments which would exceed the join limit of 61 tables of a typical MySQL installation for instance?
What is the usual solution for this other than reverting to n+1 selects anti-pattern? What is the most efficient way to fetch items with a subset of items related to each one in this fairly typical scenario?
It is usually a better option to run as few queries as possible, and then implement some application logic on top of it if needed. In your use case, I would build a query that returns both the most recent posts and the most recent associated comments, with proper ordering to make the application processing easier. Then your application can take care of displaying them.
Assuming that you use MySQL (since you mentionned it in your question), let's start with a query that gives you the 10 most recent posts:
SELECT * FROM posts ORDER BY post_date DESC LIMIT 10
Then you can join this with the corresponding comments:
SELECT
p.*,
c.*
FROM
(SELECT * FROM posts ORDER BY post_date DESC LIMIT 10) p
INNER JOIN comments c ON c.post_id = p.id
Finally, let's set up a limit on the number of comments per posts. For this, you can use ROW_NUMBER() (available in MySQL 8.0) to rank the comments per post, and then filter only the a given number of comments. This gives you the 10 most recent posts along with each of their 3 most recents comments:
SELECT *
FROM (
SELECT
p.*,
c.*,
ROW_NUMBER() OVER(PARTITION BY p.post_id ORDER BY c.comment_date DESC) rn
FROM
(SELECT * FROM posts ORDER BY post_date DESC LIMIT 10) p
INNER JOIN comments c ON c.post_id = p.id
) x
WHERE rn <= 3
ORDER BY p.post_date DESC, c.comment_date DESC
Query results are ordered by post, then by comment date. So when your application fetches the resuts, you get 1 to 3 records per post, in sequence.
If you want the last 10 posts
SELECT p.post_id
FROM post p
ORDER BY p.publish_date DESC
LIMIT 10
Now if you want the comment of those posts:
SELECT c.comment_id, u.name
FROM comments c
JOIN users u
on c.user_id = u.user_id
WHERE c.post_id IN ( SELECT p.post_id
FROM post p
ORDER BY p.publish_date DESC
LIMIT 10 )
Now for the last 3 comments is where rdbms version is important so you can use row_number or not:
SELECT *
FROM (
SELECT c.comment_id, u.name,
row_number() over (partition by c.post_id order by c.comment_date DESC) as rn
FROM comments c
JOIN users u
on c.user_id = u.user_id
WHERE c.post_id IN ( SELECT p.post_id
FROM post p
ORDER BY p.publish_date DESC
LIMIT 10 )
) x
WHERE x.rn <= 3
You can do this in one query:
select . . . -- whatever columns you want here
from (select p.*
from posts p
order by <datecol> desc
fetch first 10 rows only
) p join
users u
on p.user_id = u.user_id join
comments c
on c.post_id = p.post_id;
This returns the posts/users/comments in one table, mixing the columns. But it only requires one query.

How can I group mysql results based on a calculated colum?

I'm trying to show the most commented posts of my web but is being impossible .-. I just got this mysql error all the time (Can't group on 'comments')
the relation between tables is:
table: post / colums: id_post, title, id_comment
table : comment / colums: id_comment, text, id_post
and this is the query I'm trying to use
SELECT p.title AS title, COUNT(c.id_comment) AS comments
FROM post p
INNER JOIN comment c ON p.id_post=c.id_post
GROUP BY comments DESC
Please any alternative or solution for this?
Why would you want to group by on the number of comments? You need order by clause to get the most commented to the top and group by posts:
SELECT p.title AS title, COUNT(c.id_comment) AS comments
FROM post p
INNER JOIN comment c ON p.id_post=c.id_post
GROUP BY p.id_post, p.title
ORDER BY comments DESC
You may want to have a limit clause as well to get the top N commented posts only.

Can I use a join query to do this?

I have to admit trying to understand JOINs makes my brain explode so I need some help.
What I'm trying to accomplish is return info on the last 25 postings in a forum, but the main posts table only returns numbers for the Topic and Forum, whereas I need the textual names of the topic and forum, which I can retrieve from two other tables. In my very limited understanding of joins, it seems I can use one to do all of this in a single query rather than coding 3+ queries with loops and other perhaps unneeded code.
This would be the main query:
SELECT post_id, topic_id, forum_id, post_time
FROM posts
ORDER BY post_id DESC
LIMIT 25
But for each of the 25 results I also want included forum_title from table forums where forum_id in that table matches the forum_id from the main query results, as well as topic_title from table topics where the topic_id in that table matches the topic_id in the main query results.
I'm hoping just even seeing what this would look like will help my understanding of how JOINs work.
Thanks
EDIT: I realized I should have used the exact column and table names so that I wouldn't be editing suggestions. Using the exact names, this is how Aquinas' suggestion would look:
SELECT post_id, topic_id, forum_id, post_time, Forum_Title, Topic_Title
FROM phpbb3_posts
INNER JOIN phpbb3_topics
on phpbb3_topics.topic_id = phpbb3_posts.topic_id
INNER JOIN phpbb3_forums
on phpbb3_forums.forum_id = phpbb3_posts.forum_id
ORDER BY post_id DESC
LIMIT 25
but I get this error (this is in mysql)
1052 - Column 'topic_id' in field list is ambiguous
SELECT post_id, topic_id, forum_id, post_time, Forum_Title, Topic_Title FROM posts
INNER JOIN topics on topics.topic_id = posts.topic_id
INNER JOIN forums on forums.forum_id = posts.forum_id
ORDER BY id DESC LIMIT 25

MySQL sort by grouped date in joined table

I have a table, called Posts, and a second table, called Comments. These are linked by an id column in the posts table, and a postid column in the comments table.
Both tables have a date column, which is the date when they were posted. I want to be able to sort my posts based on the newest activity, so they should be sorted by the post's date (if there are no comments) or the newest comment's date.
In order to do this, I've constructed this simple query:
SELECT Posts.id FROM Posts
INNER JOIN Comments ON Posts.id = Comments.postid
ORDER BY Comments.date ASC
Unfortunately, this has a very obvious problem. If there are no comments on a post, it will be ignored. If there are multiple comments on a post, it will show up multiple times in the results.
How can I construct a query to satisfy these requirements?
You need to use a LEFT JOIN, that returns all rows from Posts, and rows from Comments only when the JOIN succeedes. If the join doesn't succeed because there are no comments with comments.postid=posts.id, you still get all values from Posts, but values from Comments will be Null.
You then have to use GROUP BY, so you will get only one row for each ID, and you can use MAX() aggregate function to get the maximum date in comments table.
If there are no comments, max(comments.date) will be Null, so COALESCE will return Posts.date instead.
And the final query is this:
SELECT Posts.id
FROM Posts LEFT JOIN Comments
ON Posts.id = Comments.postid
GROUP BY Posts.id
ORDER BY coalesce(max(Comments.date),Posts.date) ASC
You need a left outer join to take into account that there may be no comments:
SELECT Posts.id
FROM Posts left JOIN
Comments
ON Posts.id = Comments.postid
group by posts.id
ORDER BY coalesce(max(Comments.date), posts.date) ASC