MySQL sort within a group with multiple joins - mysql

I am attempting to work with the following tables:
users (id, name)
topics (id, name, slug)
posts (id, title, content, topicid, authorid, datetime, slug)
replies (id, authorid, threadid, content, datetime)
I want to create a list of posts ordered by latest reply (or the post date if there are no replies). Each listing should contain: the post title, the number of replies, the date of the post, the date of the latest reply, the name of the author, the name of the person who last replied, etc.
I can get everything except the date and author of the latest reply. The date can be done via MAX(replies.datetime), but I'm not sure how to get the latest author.
My last attempt was trying to use an exclusion join:
select posts.id, posts.title, posts.authorid, posts.topicid, posts.content, posts.datetime, count(replies.id) as replies, r2.datetime, replies.datetime, GREATEST(posts.datetime, coalesce(max(replies.datetime), posts.datetime)) as latesttime, users.name as author, commenters.name as commenter, r2.id
from posts
left join replies on replies.threadid = posts.id
left join users on users.id = posts.authorid
left join users commenters on commenters.id = replies.authorid
left join replies as r2 on replies.id = r2.id and replies.datetime < r2.datetime
where r2.id is null
group by posts.id
order by latesttime DESC, replies.datetime DESC
limit 20;
Unfortunately, this still won't retrieve the latest comment author. Any ideas?

You have to first start with an inner most query on the max ID for a given thread and what it's REPLY ID was joined to the original replies to get ONE instance... From that, you can get the reply author's information. THEN, get original posting information as needed.
SELECT
p.id,
p.title,
p.content,
p.topicid,
p.authorid,
p.datetime as postdate,
p.slug,
postUser.Name as PostAuthor,
coalesce( MaxReplyUser.NumReplies, 0 ) NumReplies,
coalesce( MaxReplyUser.name, '' ) ReplyUser,
coalesce( MaxReplyUser.AuthorID, 0 ) ReplyAuthor,
coalesce( MaxReplyUser.ThreadID, 0 ) ReplyThread,
coalesce( MaxReplyUser.DateTime, '' ) ReplyDateTime,
coalesce( MaxReplyUser.Content, '' ) ReplyContent
from
Posts p
left join
( SELECT
u1.name,
r1.authorid,
r1.threadid,
r1.datetime,
r1.content,
MaxReplies.NumReplies
from
( SELECT
threadid,
COUNT(*) as NumReplies,
MAX( id ) as MaxReplyID
from
replies
group by
threadID ) MaxReplies
INNER JOIN replies r1
ON MaxReplies.MaxReplyID = r1.id
INNER JOIN Users u1
ON r1.AuthorID = u1.ID ) MaxReplyUser
ON p.id = MaxReplyUser.threadID
inner join users postUser
ON p.authorid = postuser.id
order by
if( MaxReplyUser.threadID IS NULL, p.DateTime, MaxReplyUser.DateTime ) DESC

uld you test it?
SELECT posts.id, posts.title, posts.authorid, posts.topicid, posts.content, posts.datetime,
(SELECT name FROM users WHERE id = posts.autorid) "Author",
(SELECT COUNT(*) FROM replies WHERE threadid = posts.id) "Replies",
(SELECT MAX(datetime) FROM replies r WHERE threadid = posts.id) "Lastreplytime",
(SELECT (SELECT name FROM users WHERE id = r.authorid LIMIT 1) FROM replies r WHERE threadid = posts.id ORDER BY datetime DESC LIMIT 1)
FROM posts
ORDER BY Lastreplytime DESC
LIMIT 20;

Related

SQL Query to select posts from user and followed users is not including own posts

I'm building a query that should return the last 10 posts (default ordering) by $userId and the users it is following.
SQL Fiddle with a minimal example of the query: https://www.db-fiddle.com/f/i5ByFAXwADj5pfjCTn1g1m/2
The database structure is pretty simple:
posts (id, root, user, content)
users (id, username)
following (user_id, followed)
This is the query I'm currently using to get all posts:
SELECT posts.id, posts.content, users.id AS user_id, users.username
FROM posts
LEFT JOIN users ON posts.user = users.id
WHERE LIMIT 10
The query is working but it is listing posts from every user without distinction.
This is the query I built to exclude posts from users that $userId is not following, but it doesn't include $userId's own posts:
SELECT posts.id, posts.content, users.id AS user_id, users.username
FROM following
LEFT JOIN posts ON posts.user = '$userId' OR posts.user = following.followed
LEFT JOIN users ON posts.user = users.id
WHERE (following.user_id = '$userId' OR following.user_id = NULL) LIMIT 10
I've tried replacing the LEFT JOIN posts with an INNER JOIN and a RIGHT JOIN with no success whatsoever. I'm not able to find the error, why isn't the query including posts made by $userId?
I have also tried selecting from posts and joining followers, but it is returning duplicated content:
SELECT posts.id, posts.content, users.id AS user_id, users.username
FROM posts
LEFT JOIN following ON following.user_id = '$userId'
LEFT JOIN users ON posts.user = users.id
WHERE (posts.user = '$userId' OR posts.user = following.followed)
LIMIT 10;
I was about to post a UNION solution
SELECT
post_id,
content,
user_id,
username
FROM
(SELECT
posts.id post_id,
content,
users.id user_id,
username
FROM
posts INNER JOIN
users
ON user = users.id
UNION SELECT
posts.id,
content,
users.id,
username
FROM
posts INNER JOIN (
following INNER JOIN
users
ON user_id = users.id
) ON user = followed
) p
WHERE
user_id = 1
LIMIT 10;
Then I saw #Gordon Linoff's solution which might be better - more concise, at least - but I don't think it works as posted.
SELECT
posts.id,
content,
users.id,
username
FROM
posts INNER JOIN
users
ON user = users.id
WHERE
users.id = 1
OR EXISTS (
SELECT
*
FROM
following
WHERE
followed = user
AND user_id = 1
)
LIMIT 10;
Get the posts from the table posts under your conditions and join to users:
select p.id, p.content, u.id AS user_id, u.username
from (
select *
from posts
where user = '$user_id'
or user in (select user_id from following where followed = '$user_id')
) p inner join users u on u.id = p.user
order by p.id desc limit 10
Note that as it is your requirement the results may not contain posts by the user '$user_id' if the last 10 posts are from the users that this user follows.
See the demo.
I'm building a query that should return the last 10 posts by $userId and the users it is following.
So, there are two tasks here:
Get first N records per group
Apply query to given user PLUS the same for the related users
I would do something like this (pseudo code):
ids = query('SELECT user_id FROM following WHERE followed = :id)', userId).pluck('user_id');
ids.push(userId);
SELECT x.id, x.user_id, x.content
FROM (
SELECT #num := IF(#id = user_id, #num + 1, 1) AS num,
#id := posts.user_id as x_user_id,
posts.*
FROM
(SELECT #num := null, #id := null) x,
posts
WHERE posts.user_id IN(:ids)
ORDER BY posts.id DESC
) AS x
WHERE x.num <= 10
(https://www.db-fiddle.com/f/aiBUwqDApJp6foyq13ZZ2u/1)
See:
S.O.: get first N records per group
S.O.: prepared statement with WHERE IN
If I understand correctly, you basically want an EXISTS clause:
SELECT p.id, p.content, u.id AS user_id, u.username
FROM posts p JOIN
users u
ON p.user = u.id
WHERE u.id <> ? -- ? is the userid AND
EXISTS (SELECT 1
FROM following f
WHERE f.followed = ? AND
f.user_id = u.id
)
LIMIT 10;

MySQL - UPDATE query based on SELECT from multiple tables

Help me pls.
I have 3 tables:
users
id rating
posts
id_post rating id_author
photos
id_photo rating id_user
Need query for update users rating based on formula: avg(users posts ratings) + avg(user photos ratings).
Tryed this but works only if user have and posts and photos. If user have photos, and no posts then ratings become 0:
UPDATE users U
inner JOIN
(select id_user, avg(rating) as avgrating
from photos P
group by id_user
) P
on U.id = P.id_user
inner JOIN
(select author_id, avg(rating) as avgrating_posts
from posts PS
group by author_id
) PS
on U.id = PS.author_id
SET
U.rating = (
P.avgrating +
PS.avgrating_posts
)
UPDATE users SET
rating = (
( SELECT AVG(rating) FROM photos
WHERE photos.id_user = users.id AND deleted = 0 AND hidden = 0 AND date_published > 0 ) +
( SELECT AVG(rating) FROM posts
WHERE posts.author_id = users.id AND published_at > 0
) )
Use left join for your inner queries, so if there are no associated records found for a user a null value will be returned.
UPDATE users U
LEFT JOIN (SELECT author_id,
AVG(rating) AS avgrating_posts
FROM posts PS
GROUP BY author_id) PS
ON U.id = PS.author_id
LEFT JOIN (SELECT id_user,
AVG(rating) AS avgrating
FROM photos P
GROUP BY id_user) P
ON U.id = P.id_user
SET U.rating = COALESCE(P.avgrating,0) + COALESCE(PS.avgrating_posts,0)

mysql - get all post with comment count and user from users, posts and comments table

I have 3 tables of users, posts and comments. Users table fields are
Id, name, gender, phone
posts table fields are
post_id, user_id, posts, time
comments table fields are
id, user_id, comment, post_id, time
i wants to get value of these fields
name, gender, posts, time, count(comments)
SELECT
x.*, CONCAT(u.FirstName, ' ', u.LastName) AS name, u.Gender AS gender
FROM (
SELECT
p.id , p.post, p.date_time, p.user_id AS uid, COUNT(c.post_id) AS 'count'
FROM
posts p
LEFT JOIN
comments c ON p.id = c.post_id
GROUP BY
p.id
) x
LEFT JOIN
users u ON u.id = uid
Try as this:
Here 0 will replace if comments.id is null
SELECT User.name, User.gender, posts.posts, count(distinct ifnull(comments.id, 0)) as commentcount
FROM User
JOIN posts on user.id = posts.user_id
JOIN comments on user.id =comments.user_id
GROUP User.name, posts.posts
If you want to group with gender add User.gender to group by.

join two mysql queries to order the first query by the result of the second

I initially had a web service which ran the first query (post details) and within the while loop of its results, I was running the second query to retrieve the number of comments on a post. I need to try and combine the two as now I am having to order the webservice by number of comments.
1. SELECT ReportID, Title, Description, posts.Pic, DatePosted, posts.UserID, FName, SName, users.Pic as userPic,
photoWidth, photoHeight
FROM posts
INNER JOIN Users
ON Users.UserID = posts.UserID
WHERE private = 0
ORDER BY ReportID Desc
LIMIT ?, 10
2. SELECT COUNT(ReportID) as numComments FROM Comments WHERE ReportID =? AND Comment IS NOT NULL
I'm unsure how to achieve this. Will I need to make a derived table?
My initial attempt:
SELECT ReportID, Title, Description, posts.Pic, DatePosted, posts.UserID, FName, SName, users.Pic as userPic,
photoWidth, photoHeight, numComments
FROM posts
INNER JOIN Users
ON Users.UserID = posts.UserID
WHERE private = 0 AND numComments = (SELECT COUNT(ReportID) as numComments FROM Comments WHERE ReportID = ReportID AND Comment IS NOT NULL)
ORDER BY numComments DESC
This gives the issue unknown column numComments in field list
Posts:
- ReportID (primary)
- Title
- Description
- Pic
- private
- DatePosted (epoch)
- photoWidth
- photoHeight
Comments:
- CommentID (primary)
- UserID
- ReportID (linking key)
- Comment (can be null if type = 'like')
- dateposted (epoch)
- type ('comment' or 'like')
If I understand your question correctly I think what you want is the following:
SELECT Posts.*, count(Comments.ReportID) as CommentCount FROM Posts
LEFT JOIN Comments
ON Comments.ReportID = Posts.ReportID
WHERE private = 0
GROUP BY Comments.ReportID
ORDER BY CommentCount, ReportID Desc;
Obviously, you will need to adjust it to contain all the fields you want and any other joins you want to do.
Here is a demo.
This will get all the posts as well as the number of Comments in each post.
I don't have the data structures, but i think you could use this, using the count in a sub query
SELECT
ReportID, Title, Description, posts.Pic, DatePosted, posts.UserID, FName, SName, users.Pic as userPic,
photoWidth, photoHeight, numComments.numComments
FROM posts
INNER JOIN Users
ON Users.UserID = posts.UserID
WHERE private = 0 AND ReportID = (SELECT COUNT(ReportID) as numComments FROM Comments WHERE AND Comment IS NOT NULL GROUP BY ReportID) numComments
ORDER BY numComments DESC
Suggestion: JOIN the Comments table too and GROUP BY on it.
SELECT ReportID, Title, Description, posts.Pic, DatePosted, posts.UserID,
FName, SName, users.Pic as userPic, photoWidth, photoHeight,
COUNT(CommentID) AS numComments
FROM posts
INNER JOIN Users ON Users.UserID = posts.UserID
LEFT JOIN Comments ON Comments.ReportID = posts.UserID
WHERE private = 0
GROUP BY Comments.ReportID
ORDER BY numComments DESC
LIMIT ?, 10
EDIT: Changed the second JOIN to a left LEFT JOIN, so reports without any comments will also be retrieved.

Selecting the last record and comparing a datetime

I'm building a discussion board and I want to get a list of unread topics.
A topic should be unread and selected if the created_at datetime for the last post in a topic is greater than the last time the currently logged in user viewed this topic.
http://sqlfiddle.com/#!2/4e2e99/1
If you delete all of the user view inserts ALL of the topics should be listed.
I have three tables:
topics
id
user_id
created_at
topic_posts
id
topic_id
created_at
topic_user_views
id
topic_id
created_at
My query so far (but it doesn't work):
SELECT DISTINCT `topics`.`id`, `topics`.`name`
FROM `topics`
INNER JOIN `topic_user_views`
ON `topic_user_views`.`topic_id` = `topics`.`id`
INNER JOIN `topic_posts`
ON `topic_posts`.`topic_id` = `topics`.`id`
WHERE `topic_posts`.`created_at` > `topic_user_views`.`created_at`
AND `topic_user_views`.`user_id` = 1
ORDER BY `id` DESC
I don't know how to compare a topic's last post's created_at column post to the last time this user has viewed the topic.
here is one way of doing it. we will use exists to test if the topic was viewed before the last topic post was created. see the sql fiddle - http://sqlfiddle.com/#!2/4e2e99/11
select t.id as topic_id, t.name as topic_name
from topics t
where not exists(
select tuv.topic_id, max(tuv.created_at) as last_view, max(tp.created_at) as last_post
from topic_user_views tuv
inner join topic_posts as tp
on tuv.topic_id=tp.topic_id and tuv.created_at > tp.created_at
group by topic_id
having t.id=topic_id)
ORDER BY id DESC
With this example, topics also will be selected if user have not seen anything yet:
SELECT DISTINCT
`topics`.`id`,
`topics`.`name`
FROM `topics`
LEFT JOIN `topic_user_views` AS tuv
ON `tuv`.`topic_id` = `topics`.`id`
WHERE (SELECT
1
FROM topic_posts AS tp
WHERE tp.topic_id = `topics`.`id`
AND (tp.created_at > tuv.created_at
OR tuv.created_at IS NULL)
LIMIT 1)IS NOT NULL
ORDER BY `id` DESC;
Here is what I came up with :
SELECT t1.topic_id, t3.account_user_id, t1.created_at last_post_date, t4.created_at as last_seen_date
FROM topic_posts t1
INNER JOIN (SELECT topic_id, MAX(created_at) as created_at
FROM topic_posts
GROUP BY topic_id) t2
USING (topic_id, created_at)
INNER JOIN topic_user_views t3
USING (topic_id)
INNER JOIN (SELECT topic_id, account_user_id, created_at
FROM topic_user_views
INNER JOIN (SELECT topic_id, account_user_id, MAX(created_at) as created_at
FROM topic_user_views
GROUP BY topic_id, account_user_id) _
USING (topic_id, created_at, account_user_id)) t4
ON t1.topic_id = t4.topic_id and t3.account_user_id = t4.account_user_id and t3.created_at = t4.created_at
WHERE t1.created_at > t4.created_at
AND t3.account_user_id = 1;
This idea is to join the date of the last posted message in a topic (t2) and the date of the last seen message in a topic by a user (t4), and then you just have to filter out the results you don't want.
If I'm not mistaken, in the SQLFiddle you provided, there are no topics that have not already been seen by users, so this returns nothing. I slightly modified it (you can see it here) and it seems to work as wanted.
To consider the topics never viewed by a user, I think the best solution is to use the account table with a RIGHT OUTER JOIN. Something similar to :
SELECT t1.topic_id, t3.account_user_id, t1.created_at last_post_date, t4.created_at as last_seen_date
FROM topic_posts t1
INNER JOIN (SELECT topic_id, MAX(created_at) as created_at
FROM topic_posts
GROUP BY topic_id) t2
USING (topic_id, created_at)
INNER JOIN topic_user_views t3
USING (topic_id)
INNER JOIN (SELECT topic_id, account_user_id, created_at
FROM topic_user_views
INNER JOIN (SELECT topic_id, account_user_id, MAX(created_at) as created_at
FROM topic_user_views
GROUP BY topic_id, account_user_id) _
USING (topic_id, created_at, account_user_id)) t4
ON t1.topic_id = t4.topic_id and t3.account_user_id = t4.account_user_id and t3.created_at = t4.created_at
RIGHT OUTER JOIN account
ON account.user_id = t3.account_user_id
WHERE t1.created_at > t4.created_at or t4.account_user_id IS NULL
AND t3.account_user_id = 1;
which I did not test.