MySql sorted subquery with group by in main query - mysql

I'm developing a simple message system for my website. Here's an image of how the messages table works:
Picture of the MySql table
What I had in mind is to use a subquery and then use GROUP BY to get only the last message for every person that was sent to, in this case, the user 1. This is the first query:
SELECT m.user_id as user_id, u.username as username, u.avatar as avatar, m.message_id as messsage_id, m.message as message, m.date as read_date
FROM users_messages m
INNER JOIN users u
ON u.user_id = m.sender_id
WHERE m.receiver_id = 1
ORDER BY message_id DESC
Some data is from another table, but it isn't relevant. This query returns this result, which is expected:
Image with the results of the first query
I made the messages descriptive so you can see what's going on more clearly. Now, here comes the weird part. As you can see, the messages are sorted by message ID, so the comments are sorted from new to old. All good here. However, when I expecute the following query...
SELECT q.user_id, q.username, q.avatar, q.message, q.read_date
FROM (SELECT u.user_id as user_id, u.username as username, u.avatar as avatar, m.message_id as messsage_id, m.message as message, m.date as read_date
FROM users_messages m
INNER JOIN users u
ON u.user_id = m.sender_id
WHERE m.receiver_id = 1
ORDER BY message_id DESC) AS q
GROUP BY user_id
This should group the messages by user, so only the first one (the newest one in this case) should show up. However, this isn't the case. It ALWAYS returns the first messages for everyone, as seen in the following picture:
Picture with the results that make no sense
I'm really, really confused about this, and I can't find anything that helps me fix it.

The issue is with the GROUP BY, which selects an arbitrary result from the set you give it. I think you should be able to get the result you want by changing your first query:
SELECT u.user_id as user_id, u.username as username, u.avatar as avatar, m.message_id as message_id, m.message as message, m.date as read_date
FROM users_messages m
INNER JOIN users u
ON u.user_id = m.sender_id
WHERE m.receiver_id = 1 AND
m.message_id = (SELECT MAX(message_id)
FROM users_messages m1
WHERE m1.sender_id = u.user_id)

Related

Sort the table by COUNT after displaying values from multiple tables

My goal is: display how often is specific ID repeated as the topic_poster in one table, phpbb_topics, but only if the proper forum_id condition is also met, then also display the corresponding username from another table, phpbb_users.
I have successfully extracted the count of how often is one specific userID occuring as the topic_poster in table phpbb_topics, like that:
SELECT topic_poster, COUNT(topic_poster)
FROM phpbb_topics WHERE forum_id = 156
GROUP BY topic_poster
Thanks to another question on StackOverflow I now also know how to get data from another table to get the username corresponding to the specific userID, like that:
SELECT t.topic_poster, u.user_id, u.username
FROM phpbb_topics t
LEFT JOIN phpbb_users u ON u.user_id = t.topic_poster
I also managed to finally mix the two to get what I want:
SELECT t.topic_poster, COUNT(t.topic_poster), u.user_id, u.username
FROM phpbb_topics t
LEFT JOIN phpbb_users u ON u.user_id = t.topic_poster WHERE t.forum_id = 156
GROUP BY t.topic_poster
However, I do not know how to properly sort in descending or ascending order based on the counter. phpmyAdmin won't let me just click on the column's name to sort by it, and any queries i write with GROUP BY or ORDER BY are reporting errors.
Update:
after putting this in:
SELECT t.topic_poster, COUNT(t.topic_poster), u.user_id, u.username
FROM phpbb_topics t
LEFT JOIN phpbb_users u ON u.user_id = t.topic_poster WHERE t.forum_id = 156
ORDER BY COUNT(topic_poster)
the results display only one row:
topic_poster |COUNT(t.topic_poster) | user_id | username
6 | 254 6 | Opix
Same happens if I use this:
SELECT t.topic_poster, COUNT(t.topic_poster), u.user_id, u.username
FROM phpbb_topics t
LEFT JOIN phpbb_users u ON u.user_id = t.topic_poster WHERE t.forum_id = 156
ORDER BY COUNT(t.topic_poster)
Same happens if I use this:
SELECT t.topic_poster, COUNT(t.topic_poster), u.user_id, u.username
FROM phpbb_topics t
LEFT JOIN phpbb_users u ON u.user_id = t.topic_poster WHERE t.forum_id = 156
ORDER BY topic_poster
If I use this: SELECT t.topic_poster, COUNT(t.topic_poster), u.user_id, u.username FROM phpbb_topics t LEFT JOIN phpbb_users u ON u.user_id = t.topic_poster WHERE t.forum_id = 156 GROUP BY t.topic_poster I get all the results, but I can't sort by the counter.
mySQL extends the group by so you don't have to have one. However, it assumes all values for each column are the same; so it's free to pick what to put in from each column. However, if the values are different, what it picks (1 value) isn't representative of the entire set, so you must use group by when the values are different.
Put a different way: if t.forum_ID = 156 limited to a specific topic_poster, user_Id and username. you'd have no problem. But since t.forum_ID represents many different values in each of those columns, group by is needed or the engine will "somewhat" randomly select a value for each of them. The engine assumes all are the same.
Thus the downfall of the mySQL Group by extension. But, if all the non-aggregrated columns did have the same value... you get a performance gain by allowing the engine to just aggregate and 'pick' a value for each column.
Based on your response, you think you should be getting multiple rows. So that tells me the non-aggregated fields are different so add a group by...
SELECT t.topic_poster, COUNT(t.topic_poster), u.user_id, u.username
FROM phpbb_topics t
LEFT JOIN phpbb_users u ON u.user_id = t.topic_poster
WHERE t.forum_id = 156
GROUP BY t.topic_poster, u.user_id, u.username
ORDER BY COUNT(t.topic_poster)
You could have ties, so you may also want to order by poster or user name after the count...

Left join to get most recent entries

I have the tables users and statuses . I want to select all the users, plus any statuses they might have, but only the most recent status from each user.
Here is the code that doesn't work:
SELECT users.id, alias, gender, login, logout, users.create_date, statustext as statustxt, TIMESTAMPDIFF(YEAR,birthdate,CURDATE()) AS age
FROM users
LEFT JOIN statuses s ON users.id = s.user_id
WHERE s.ID = (
SELECT MAX(s2.ID)
FROM statuses s2
WHERE s2.user_id = s.user_id
)
This gets the users with the most recent statuses, but not the users from the users table as well. Maybe it can be fixed by some small adjustment?
I got the sub query by searching, but I don't understand how that code works. It seems to compare two versions of the same table (For example: WHERE s2.user_id = s.user_id ) . Where can I read about this sort of technique?
Is a sub query required in this case by the way?
If you can find a solution would be great, and some basic explanation of how it works would highly appreciated.
----------EDIT---------------
I took one of the responses (by maresa) and combined with the sub query of my initial code , and this works(!) It has 3 selects and looks a bit over complicated maybe?:
SELECT users.id, alias, gender, login, logout, users.create_date, statustext as statustxt, TIMESTAMPDIFF(YEAR,birthdate,CURDATE()) AS age
FROM users
LEFT JOIN (
SELECT user_id, statustext FROM statuses s
WHERE s.ID = (
SELECT MAX(s2.ID)
FROM statuses s2
WHERE s2.user_id = s.user_id
)
) as s ON users.id = s.user_id
I've encountered similar problem. This post is relevant: http://www.microshell.com/database/sql/optimizing-sql-that-selects-the-maxminetc-from-a-group/.
Regarding your specific query, since you care only the latest status, you want to first get the latest status from each users. Assuming that the latest status has the latest id (based on your sample), the SQL would be below:
SELECT
MAX(ID), statustext, user_id
FROM
statuses
GROUP BY
user_id
What the above query does is, to get the latest status per user_id. Once you get that, you can think of it as if it's a table. Then simply join on this "table" (the query) instead of the real one (statuses table). Therefore, your query would be like below:
SELECT
users.id, alias, gender, login, logout, users.create_date, statustext as statustxt, TIMESTAMPDIFF(YEAR,birthdate,CURDATE()) AS age
FROM
users
LEFT JOIN (
SELECT
MAX(ID), user_id
FROM
statuses
GROUP BY
user_id
) as s ON users.id = s.user_id
LEFT JOIN statuses ON statuses.ID = s.ID -- EDIT: Added this line.
You might use a subselect as the join, and limit to show only 1 row:
SELECT
users.id,
alias,
gender,
login,
logout,
users.create_date,
statustext as statustxt,
TIMESTAMPDIFF(YEAR,birthdate,CURDATE()) AS age
FROM users
LEFT JOIN (
SELECT user_id,statustext
FROM statuses s1
WHERE s1.user_id = users.id
ORDER BY status_date DESC
LIMIT 1
) s ON users.id = s.user_id
You could use a in clause with a tuple
SELECT
users.id
, alias
, gender
, login
, logout
, users.create_date
, statustext as statustxt
, TIMESTAMPDIFF(YEAR,birthdate,CURDATE()) AS age
FROM users
LEFT JOIN statuses s ON users.id = s.user_id
WHERE (s.user_id, s.ID) in (
SELECT user_id, MAX(s2.ID)
FROM statuses s2
group by user_id
)

how to select the last text from message table

I have two tables (messages and user). I want to select the last (msg_id,text) from the messages table for a particular ad_id and need to select the name of the user from the user table.
SELECT u.id
, m.date
, m.ad_id
, max(m.msg_id)as msg_id
, u.first_name
, m.text
, m.u_to_id
, m.u_from_id
FROM user u
JOIN messages m
ON CASE WHEN m.u_from_id ='14' THEN u.id = m.u_to_id
ELSE u.id = m.u_from_id END
AND (m.u_from_id='14' OR m.u_to_id='14')
AND m.ad_id='20'
GROUP BY CONCAT(m.ad_id,u.id)
ORDER by m.msg_id DESC
this query is working but I can't select t the last m.textTable structure
SELECT u.id, m.text
FROM user u
JOIN messages m ON m.msg_id = (SELECT max(msg_id) FROM messages WHERE u_from_id = u.id)
I simplified your query to show the logic relevant to your question. Basically you want to join your messages table on the msg_id that is equal to the inner query of the max msg_id with that user.
After so many experiments added a new column(bargainer) for identify the recipient and this query working fine for me
select m.msg_id,m.text,m.status,m.date,m.bargainer,m.ad_id,u.first_name,u.id from user u JOIN messages m where msg_id in (select max(msg_id) from messages m where m.ad_id=20 and u.id=m.bargainer group by(m.bargainer))group by(m.msg_id) order by msg_id DESC

SQL with 2 INNER JOIN's on the same column not working

I am trying to use the following code to get the 6 users with which the current user has most recently chatted. I have two problems. First of all, if the current user has recieved a message from the other user but has only sent, that other user isnt fetched. Second of all, the ORDER BY clause is causing an error. Im a beginner in SQL so I have no idea what's going on.
Thanks in Advance!
Here's the code:
SELECT users.*
FROM users INNER JOIN
messages fromuser
ON (fromuser.fromid = users.id) INNER JOIN
messages touser
ON (touser.toid = users.id)
WHERE fromuser.toid = :userid OR touser.fromid = :meid
GROUP BY users.id
ORDER BY MAX(messages.datetime)
LIMIT 6;
This should do your job, and it relies less on MySQL extensions than your other answer so far. I estimate that it would perform about the same, but it's surely wordier.
SELECT u.*
FROM (
SELECT DISTINCT otherid
FROM (
SELECT
m.fromid AS otherid,
MAX(m.datetime) as maxts
FROM messages m
WHERE m.toid = :userid
GROUP BY m.fromid
UNION ALL
SELECT
m.toid AS otherid,
MAX(m.datetime) as maxts
FROM messages m
WHERE m.fromid = :userid
GROUP BY m.toid
) um
ORDER BY maxts DESC
LIMIT 6
) otheru
INNER JOIN users u
ON u.id = otheru.otherid
Your logic is doomed to fail, because one users.id cannot be two different values at the same time. I think this query does what you want:
SELECT u.*
FROM messages m INNER JOIN
users u
ON (m.fromid = u.id AND m.toid = :userid) OR
(m.toid = u.id AND m.fromid = :userid )
GROUP BY u.id
ORDER BY MAX(m.datetime) DESC
LIMIT 6;
Notice that it joins to the users table by the id that is not the current user.

Reference outside inner join query

I have the following query for a basic messaging system. It shows the messages that you've sent. However, it is showing the first_name and second_name for the account referenced by sender_id. Instead I want it to output the name columns referenced by receiver_id
ie) SELECT first_name, second_name WHERE accounts.account_id = messages.receiver_id
Do I need to write a seperate query for this or can I make this one output it in a single query? I've been thinking quite hard about this problem!
SELECT first_name, second_name, message_id, title, body, seen, urgent, type, messages.timestamp
FROM messages
INNER JOIN accounts
ON messages.sender_id=accounts.account_id
WHERE sender_id = ?
You can do this with two joins:
SELECT s.first_name as sender_first, s.second_name as sender_last,
r.first_name as rec_first, r.second_name as rec_last,
m.message_id, m.title, m.body, m.seen, m.urgent, m.type, m.timestamp
FROM messages m inner join
accounts s
ON m.sender_id = s.account_id inner join
accounts r
on m.receiver_id = r.account_id
WHERE m.sender_id = ?