Somewhat Complex MySQL Statement - mysql

I am creating a forum, and have gotten stuck creating the page that will display all the topics for a given forum. The three relevant tables & fields are structured as follows:
Table: forums_topics Table: forums_posts Table: users
-------------------- ------------------- ------------
int id int id int id
int forum_id int topic_id varchar name
int creator int poster
tinyint sticky varchar subject
timestamp posted_on
I've started with the following SQL:
SELECT t.id,
t.sticky,
u.name AS creator,
p.subject,
COUNT(p.id) AS posts,
MAX(p.posted_on) AS last_post
FROM forums_topics AS t
JOIN users AS u
LEFT JOIN forums_posts AS p ON p.topic_id = t.id
WHERE t.forum_id = 1
AND u.id = t.creator
GROUP BY t.id
ORDER BY t.sticky
This appears to be getting me what I want (topic's id number, if its a sticky, who made the topic, the subject of the topic, number of posts for each topic, and timestamp of latest post). If there is a mistake though please let me know.
What I am having trouble with now is how I can add to this to get the name of the lastest poster. Can someone explain how I would edit my SQL to do that? I can provide more details if needed, or restructure my tables if that will make it simpler.

Here is a simple way to do this:
SELECT t.id,
t.sticky,
u.name AS creator,
p.subject,
COUNT(p.id) AS posts,
MAX(p.posted_on) AS last_post,
(SELECT name FROM users
JOIN forums_posts ON forums_posts.poster = users.id
WHERE forums_posts.id = MAX(p.id)) AS LastPoster
FROM forums_topics AS t
JOIN users AS u
LEFT JOIN forums_posts AS p ON p.topic_id = t.id
WHERE t.forum_id = 1
AND u.id = t.creator
GROUP BY t.id
ORDER BY t.sticky
Basically, you do a sub-query to find the user based upon the max id. If your IDs are GUIDs or are not in order for some other reason, you could do the lookup based upon the posted_on timestamp instead.

Related

Table conflict with SQL request

I have 5 tables [structure] :
"medias" to store pictures [id, creatorID (user who create the media), date]
"likes" to store likes on pictures [id, senderID (user who liked), mediaID (media liked)]
"comments" to store comments on pictures [id, mediaID (media commented)]
"follow" to store a follow [id, follow (user X), following (one following of the user X)]
"users" to store users [id]
All tables are made with an ID which increment at insert.
Here my request to display a flux of pictures for an user :
SELECT
m.id as mediaID,
COUNT(l.id) as likesCount,
COUNT(c.id) as commentsCount
FROM medias m
INNER JOIN follow f
ON f.follow = 'user_here' AND m.creatorID = f.following AND m.date < 'timestamp_here'
INNER JOIN users u
ON u.id = m.creatorID
LEFT JOIN likes x
ON m.id = x.mediaID AND x.senderID = 2
LEFT JOIN likes l
ON m.id = l.mediaID
LEFT JOIN comments c
ON m.id = c.mediaID
GROUP BY m.id
When there's more than 1 comment, likesCount take the value of the commentsCount. And when I dislike a picture, the commentCount decrement of 1 comment. So, I really don't know how can I solve it...
The easy way to solve your problem is to use distinct:
SELECT m.id as mediaID,
COUNT(DISTINCT l.id) as likesCount,
COUNT(DISTINCT c.id) as commentsCount
If you have lots of likes and comments, a better way may be to aggregate before joining or use a correlated subquery.

How to output a mixture of data from 2 mySQL tables when joined columns have similar names?

What I want to achieve is to show all the published articles and all the published questions of a specific user_id in one loop ordered by timestamp. In simple words, to show everything mixing articles and questions.
My database structure is as below, and I have put also the profiles table.
My wrong sql query is :
SELECT *
FROM articles
JOIN questions ON articles.user_id = questions.user_id
WHERE articles.user_id = '38'
AND questions.user_id = '38'
AND questions.publish = '1'
AND articles.publish = '1'
ORDER BY questions.timestamp DESC
Articles table
id
publish
user_id
user_name
article_title
article_content
article_category
timestamp
Questions table
id
publish
user_id
user_name
question_title
question_content
question_category
timestamp
Profiles
user_id
Just use a join, an outer join will get all matching records from both table:
SELECT *
FROM articles
FULL OUTER JOIN questions ON articles.user_id = questions.user_id
WHERE articles.user_id = '38'
ORDER BY questions.timestamp DESC

join 2 mysql tables and get the first and last date

I have 2 mysql tables, one with the users details and the second with all the pages that the users saw (1:N)
TABLE "users"
id int(10) auto_increment primay
ip varchar(15)
lang char(2)
...
TABLE "pages"
id int(10) auto_increment primay
uid int(10) index
datetime datetime
url varchar(255)
I know is possibile to join the 2 tables, but i'm a little confused how to get the first and last datetime, and the first url from the "pages" table...
SELECT * FROM users, pages WHERE users.id = pages.uid
I think with GROUP BY / MIN(pages.datetime), MAX(pages.datetime) but I have no idea where to use it, and how I can get the first pages.url
As you mentioned you need to use Group by with MIN & MAX aggregate function to find the first and last datetime per user.
Also don't use comma separated join syntax which is quite old and not much readable use proper INNER JOIN syntax
SELECT U.ID,
MIN(pages.datetime) as First_date,
MAX(pages.datetime) as Last_date
FROM users U
INNER JOIN pages P
ON U.id = P.uid
Group by U.ID
If you want to see the other information like first visited url,etc.. Then you can join above result to the main table to get the related information.
select A.uid,A.url First_URL,C.url as Last_url,First_date,Last_date
from pages A
INNER JOIN
(
SELECT U.ID,
MIN(pages.datetime) as First_date,
MAX(pages.datetime) as Last_date
FROM users U
INNER JOIN pages P
ON U.id = P.uid
Group by U.ID
) B
ON A.ID =B.ID
and A.datetime = B.First_date
INNER JOIN pages C
on C.ID =B.ID
and C.datetime = B.Last_date

mysql complicated join

I have run into some troubles while writing a query for MySQL. I don't know how to describe my problem well enough to search the web for it, so sorry if my question is stupid.
I have 3 tables:
CREATE TABLE posts( id INT, author INT );
CREATE TABLE users( id INT, nick varchar(64) );
CREATE TABLE groups( id INT, name varchar(64) );
CREATE TABLE membership (user INT, group INT, date INT ) ;
Membership contains info about users that have joined some groups. "Date" in the membership table is the time when a user joined that group.
I need a query which will return a post, its author's nick and the name of the group with the least joining date.
All I have currently is:
SELECT p.id, u.nick, g.name
FROM posts AS p
LEFT JOIN users AS u ON u.id = p.author
LEFT JOIN membership AS m ON m.user = p.author
LEFT JOIN groups AS g ON g.id = m.group
WHERE 1;
but of course it returns a random group's name, not the one with earliest joining date.
I also tried the following variant:
SELECT p.id, u.nick, g.name
FROM posts AS p
LEFT JOIN users AS u ON u.id = p.author
LEFT JOIN
(SELECT * FROM membership WHERE 1 ORDER BY date ASC)
AS m ON m.user = p.author
LEFT JOIN groups AS g ON g.id = m.group
WHERE 1;
but it gave me same result.
I would appreciate even pointers to where I could start, because at the moment I have no idea what to do with it.
I don't know why you want what you do, however, if you want the information for the earliest membership date (since there's no date for posting itself), no problem. Now, we have the earliest membership which will always point to the same one person as you are not asking for a specific group.. (or did you want the earliest person PER membership group -- which is what I'll write the query for). Now, we have the earliest user and can link to the posts table (by apparently the author), but what if someone has 20 posts under their name... Do you also want the FIRST ID for that author.
Just copying from your supplied tables as a reference...
posts: id (int), author(int)
users: id (int), nick (varchar)
groups: id (int), name (varchar)
membership: user (int), group (int), date (int)
select
u1.nick,
m2.date,
g1.name,
p1.id as PostID
from
( select m.group,
min( m.date ) as EarliestMembershipSignup
from
Membership m
group by
m.group ) EarliestPerGroup
join Membership m2
on EarliestPerGroup.Group = m2.Group
AND EarliestPerGroup.EarliestMembershipSignup = m2.Date
join groups g1
on m2.group = g1.id
join users u1
on m2.user = u1.ID
join posts p1
on u1.id = p1.author
Something like this
SELECT p.id, u.nick, g.name
FROM posts p,
users u,
membership m,
groups g
WHERE p.author = u.id
AND m.user = u.id
AND m.group = g.id
ORDER BY m.date ASC
LIMIT 1;
Take care to have good indexes when joining these 4 tables.
I'd recommend moving your date column from the membership table into your groups table since that seems to be where you're tracking that information. The membership table is just an intersection table for the many-to-many users<->groups tables. It should only contain user ID and the group ID columns.
What about this?
SELECT p.id, u.nick, g.name
FROM
users u,
posts p,
groups g
INNER JOIN membership m
ON u.id = m.user
INNER JOIN groups
ON m.group = groups.id
ORDER BY g.timestamp DESC
LIMIT 1;

Get the latest row from another table in MySQL

Let's say I have two tables, news and comments.
news (
id,
subject,
body,
posted
)
comments (
id,
parent, // points to news.id
message,
name,
posted
)
I would like to create one query that grabs the latest x # of news item along with the name and posted date for the latest comment for each news post.
Speed matters in terms of selecting ALL the comments in a subquery is not an option.
I just realized the query does not return results if there are no comments attached to the news table, here's the fix as well as an added column for the total # of posts:
SELECT news.*, comments.name, comments.posted, (SELECT count(id) FROM comments WHERE comments.parent = news.id) AS numComments
FROM news
LEFT JOIN comments
ON news.id = comments.parent
AND comments.id = (SELECT max(id) FROM comments WHERE parent = news.id)
If speed is that important, why not create a recent_comment table that contains the id and parent id of just the most recent comments? Every time a comment is posted on a news post, replace that news id's most recent comment id. Create an index on the news id column of the new table and your joins will be fast.You'd be trading write speed for read speed, but not by a whole lot.
Assuming posted is a unique timestamp, otherwise choose a unique autonumber
select c.id, c.parent, c.message, c.name, c.posted
c.message, c.name,
c.posted -- same as comment_latest.recent
from comments c
join
(
select parent, max(posted) as recent
from comments
group by parent
) as comment_latest
on c.parent = comment_latest.parent
and c.posted = comment_latest.recent
Complete(displays news information):
select
n.id as news_id, n.subject, n.body, n.posted as news_posted_date
c.id as comment_id,
c.message, c.name as commenter_name, c.posted as comment_posted_date
from comments c
join
(
select r.parent, max(r.posted) as recent
from comments r
join
(
select id from news order by id desc limit $last_x_news
) news l
on r.parent = l.id
group by r.parent
) as comment_latest
on c.parent = comment_latest.parent
and c.posted = comment_latest.recent
join news n on c.parent = n.id
NOTE:
The above code is not subquery, it is table-deriving query. It is faster than subquery. This is subquery(slow):
select
id,
subject,
body,
posted as news_posted_date,
(select id from comments where parent = news.id order by posted desc limit 1) as comment_id,
(select message from comments where parent = news.id order by posted desc limit 1) as message,
(select name from comments where parent = news.id order by posted desc limit 1) as name,
(select posted from comments where parent = news.id order by posted desc limit 1) as comment_posted_date,
from news
SELECT news.subject, news.body, comments.name, comments.posted
FROM news
INNER JOIN comments ON
(comments.parent = news.id)
WHERE comments.parent = news.id
AND comments.id = (SELECT MAX(id)
FROM comments
WHERE parent = news.id)
ORDER BY news.id
This gets all the news items, along with the related comment with the highest id value, which in theory should be the latest.
My solution is similar to J but I think he added one line that is unnecessary:
SELECT news.*, comments.name, comments.posted FROM news INNER JOIN comments ON news.id = comments.parent WHERE comments.id = (SELECT max(id) FROM comments WHERE parent = news.id )
Not sure of the speed on an extremely large table though.
Given the constraints brought to light in the comments of my other answer, I have a new idea that may or may not make any sense in practise.
Create a view (or function if it's more appropriate) with the following definition, called recent_comments:
SELECT MAX(id), parent
FROM comments
GROUP BY parent
If you have a clustered index on the parent column, this is probably a reasonably fast query, but even then it will still be a bottleneck.
Using this, the query you need to get your answer is something like,
SELECT news.*, comments.*
FROM news
INNER JOIN recent_comments
ON news.id = recent_comments.parent
INNER JOIN comments
ON comments.id = recent_comments.id
Plus considerations for news posts that don't have any comments yet.
I think the solution provided by #Jan is the best. i.e create the "View" and inner join it with the SQL statement.
It'll definitely reduce the time to pull the data. I tested it and it works 100%.