SELECT the last message of conversation - MySQL - mysql

I have query which looks like:
SELECT * FROM
( SELECT DISTINCT CASE
WHEN user1_id = 1
THEN user2_id
ELSE user1_id
END userID,conversationId
FROM conversations
WHERE 1 IN (user2_id,user1_id))dt
INNER JOIN users on dt.userID = users.id
It returns conversationId and information about user from users table. I would like to also add the last message (the one with biggest messageId) from message table on base of conversationId. The last thing would be to sort all the results by messageId
I tried to use another INNER JOIN which looked like :
INNER JOIN message on dt.conversationId = message.conversationId
Its adding messages to the result but I would like to get only the last one (the one with highest messageId as mentioned). I guess I would have to implement MAX somehow but I dont have idea how. The same thing with sorting all result by messageId so results with the biggest messageId would be first.
Thanks for all suggestions.

You can get the highest messageId for the conversation in a corelated subquery and use it for your join condition:
INNER JOIN message m
on m.conversationId = dt.conversationId
and m.messageId = (
SELECT MAX(m1.messageId)
FROM message m1
WHERE m1.conversationId = dt.conversationId
)

So the solution for eveything was following query
SELECT * FROM
( SELECT DISTINCT
CASE
WHEN user1_id = 1
THEN user2_id
ELSE user1_id
END userID,conversationId
FROM conversations
WHERE 1 IN (user2_id,user1_id))dt
INNER JOIN users on dt.userID = users.id
INNER JOIN message m on m.conversationId = dt.conversationId and m.messageId = (SELECT MAX(m1.messageId)
FROM message m1 WHERE m1.conversationId = dt.conversationId)
ORDER by m.messageId DESC

Related

How to show username from another table on the basis of max id in sqql

I am trapped in a sql query, I know it may be common but not getting any proper solution.
From my table messages, I have successfully fetch max id by joining from and to columns, now what I am trying to do is, I want to pull the name of that max id from another table naming users,
Here is my working query to find max id,
select m.*
from messages m
where m.id in (select max(m.id) as max_id
from messages m
where m.`from` = 7
or m.`to` = 7
group by least(m.`to`, m.`from`), greatest(m.`to`, m.`from`))
I have tried something like this but it matches name with from column of messages table.
select messages.*, users.name
from messages
left join users on messages.`from` = users.id
where messages.id in (select max(id) as max_id
from messages
where `from` = 7
or `to` = 7
group by least(`to`, `from`), greatest(`to`, `from`))
I want that name will be shown according to max id that I am getting.
columns of messages table : id , from, to, created_at
columns of users table : id, name, email, created_at
Please help me out,
One method is to join, but only to the column that is not 7:
select m.*, u.name
from messages m join
users u
on u.id in (m.`from`, m.`to`) and u.id <> 7
where m.id in (select max(m.id) as max_id
from messages m
where 7 in (m.`from`, m.`to`)
group by least(m.`to`, m.`from`), greatest(m.`to`, m.`from`)
);

Sql statement with expression in group by

I have a question about one sql statement for my mysql db. I have a table with the following columns :-
sender_id | receiver_id | content | dateAndTime
As you understand I would like to implement sending messages between users. I want to select the last message(doesn't matter sent or received) with every user. Something like the messages in facebook. I guess that I should use expression in group by if it is possible but I would like to see your opinion how I should do it. Thanks!
For This kind of purpose, your field dateAndTime is must be date-time type.
SELECT * FROM TABLE ORDER BY dateAndTime DESC
You can retrieve latest data using this descending order.
Try this one
SELECT C.ID, T.content, T.dateAndTime FROM T JOIN
(
SELECT ID, MAX(dateAndTime) 'last' FROM
(
SELECT sender_id 'ID', content, dateAndTime FROM T
UNION
SELECT receiver_id 'ID', content, dateAndTime FROM T
) B
GROUP BY ID
) C
WHERE T.dateAndTime = C.last AND (T.sender_id = C.ID OR T.receiver_id = C.ID)
Assuming you have a 'users' table, and your stated table named 'mytable' you could:
select users.id,mytable.content, MAX(mytable.dateAndTime) as date
from users join mytable
on users.id = mytable.sender_id
or users.id = mytable.receiver_id
group by users.id, mytable.content, date
The max function will keep only the latest date from each group generated by the group by
EDIT: try this instead
select users.id, mytable.content, mytable.dateAndTime
from users join mytable
on users.id = mytable.sender_id
or users.id = mytable.receiver_id
where mytable.dateAndTime = (select max(t.dateAndTime) from mytable as t where t.receiver_id = users.id or t.sender_id = users.id)

Join mysql tables twice on 2 columns = 1 column

I have a database that contains messages. The messages are stored in one table, the user information is stored in another. In the message table, there is an author_id column which represents the user_id of the author from the user table, there are all the message columns, and there is a to_address which represents a concatenation of "u_" + user_id from the user table. Is there any that I can join these two tables, so that it display the username instead of ID in BOTH the author_id AND to_address.
I've tried
SELECT username, ..., username
FROM msgs
INNER JOIN users
ON user_id=author_id AND concat("u_",user_id)=to_address;
with obvious error
I've tried using subqueries such as
SELECT
( SELECT username
FROM users
INNER JOIN msgs
ON user_id=author_id
) AS "From",
( SELECT username
FROM users
INNER JOIN msgs
ON CONCAT("u_",user_id)=to_address
) AS "To",
( SELECT timestamp(message_time) FROM msgs
) AS "Sent",
( SELECT message_subject FROM msgs
) AS "Subject",
( SELECT message_text AS "Message" FROM msgs
) AS "Message"
and got "Subquery returns more than 1 row". Is there any way that I can do this successfully?
It sounds like you want something like this:
SELECT
from_user.username AS "From",
to_user.username AS "To",
timestamp(msgs.message_time) AS "Sent",
msgs.message_subject AS "Subject",
msgs.message_text AS "Message"
FROM msgs
INNER JOIN users AS from_user
ON msgs.author_id = from_user.user_id
INNER JOIN users AS to_user
ON msgs.to_address = CONCAT("u_", to_user.user_id);
Basically, you join the users table to the msgs table twice, giving each instance of the table a different name and a different join condition. Then you can pick a specific column out of a specific instance of the users table.
I think you want to do something like
SELECT msgs.*,
authors.whatever,
addresses.to_address
FROM msgs
JOIN users AS authors ON msgs.author_id = authors.id
JOIN users AS addresses ON msgs.address_id = addresses.id
My query is perhaps imprecise but you can probably see what I'm doing here.
As an aside, I would recommend not abbreviating msgs and using singular table names.
You need two joins as you want to get two separate users:
select f.username, t.username
from msgs m
inner join users f on f.user_id = m.author_id
inner join users t on concat("u_", t.user_id) = m.to_address
This will return the username associated with both the "author_id" and the "to_address", using correlated subqueries, instead of using JOIN. (Using a JOIN is the usual approach, but an approach using a correlated subquery gives you some additional flexibility.
SELECT (SELECT u.username
FROM users u
ON u.user_id = CONCAT("u_",u.user_id) = m.to_address
ORDER BY u.username LIMIT 1
) AS to_username
, (SELECT a.username
FROM users a
ON a.user_id = m.author_id
ORDER BY a.username LIMIT 1
) AS author_username
, m.*
FROM msgs m
NOTE: this differs a bit from an INNER JOIN in that this will return a row from msg when a matching username is not found for the to_address or the author_id.)
NOTE: this assumes that user_id is unique in the users table.
NOTE: if the username column is NOT NULL in the users table, then you can emulate the INNER JOIN, and NOT return a row if a matching username is not found for the author_id or to_address by adding
HAVING to_username IS NOT NULL
AND author_username IS NOT NULL

MySQL latest unique user chats not working

I'm trying to get latest created datetime for unique user_id. I've got below query but it does not seem to be working....It does not get the latest created time.
SELECT * FROM `chats`
WHERE receiver_id = 1
GROUP BY user_id
ORDER BY created DESC
Is there a reason why?
UPDATE:
Actually I found my answer myself. Please look below. I had to use INNER JOIN for nested searched and filtered result table then find using where clause of that result then left join on that table to get the data I needed!
SELECT c.*, users.username
FROM chats c
INNER JOIN(
SELECT MAX(created) AS Date, user_id, receiver_id, chat, type, id
FROM chats
WHERE receiver_id = 1
GROUP BY user_id ) cc ON cc.Date = c.created AND cc.user_id = c.user_id
LEFT JOIN users ON users.id = c.user_id
WHERE c.receiver_id = 1
You should use a MAX aggregate function -
SELECT user_id, MAX(created) latest_datetime FROM chats
GROUP BY user_id;
Try this query, it will show all users and latest created datetime.

MySQL JOIN queries - Messaging system

I have the following tables for a messaging system and I was wondering how I would go about querying the DB for how many conversations have new messages.
My tables are as follows
Conversation
------------
id
subject
Messages
--------
id
conversation_id
user_id (sender)
message
timestamp (time sent)
Participants
------------
conversation_id
user_id
last_read (time stamp of last view user viewed conversation)
I'm trying to do the following query but it returns no results:
SELECT COUNT(m.conversation_id) AS count
FROM (messages_message m)
INNER JOIN messages_participants p ON p.conversation_id = m.conversation_id
WHERE `m`.`timestamp` > 'p.last_read'
AND `p`.`user_id` = '5'
GROUP BY m.conversation_id
LIMIT 1
Also, I probably will have to run this on every page load - any tips of making it as fast as possible?
Cheers
EDIT
I've got another somewhat related question if anybody would be so kind as to help out.
I'm trying to retrieve the subject, last message in conversation, timestamp of last convo and number of new messages. I believe I have a working query but it looks a bit badly put together. What sort of improvements can I do to this?
SELECT SQL_CALC_FOUND_ROWS c.*, last_msg.*, new_msgs.count as new_msgs_count
FROM ( messages_conversation c )
INNER JOIN messages_participants p ON p.user_id = '5'
INNER JOIN ( SELECT m.*
FROM (messages_message m)
ORDER BY m.timestamp DESC
LIMIT 1) last_msg
ON c.id = last_msg.conversation_id
LEFT JOIN ( SELECT COUNT(m.id) AS count, m.conversation_id, m.timestamp
FROM (messages_message m) ) new_msgs
ON c.id = new_msgs.conversation_id AND new_msgs.timestamp > p.last_read
LIMIT 0,10
Should I determine if the conversations is unread by doing an IF statement in MySQL or should I convert and compare timestamps on PHP?
Thanks again,
RS7
'p.last_read' as quoted above is a string constant - remove the quotes from this and see whether that changes anything, RS7. If user_id is an integer than remove the quotes from '5' as well.
As far as performance goes, ensure you have indexes on all the relevant columns. messages_participants.user_id and messages_message.timestamp being two important columns to index.
Yes, you have problem in your query.
Firstly, you should have noticed that you count the column you are grouping, so the count result will be 1.
Secondly, you are comparing the timestamp to a string : m.timestamp > 'p.last_read'.
Finally, avoid using LIMIT when you know your query will return one row (be self-confident :p).
Try:
SELECT
COUNT(m.conversation_id) AS count
FROM
messages_message m
INNER JOIN
messages_participants p ON p.conversation_id = m.conversation_id
WHERE
m.timestamp > p.last_read
AND p.user_id = 5
if you want to increase the query running time you can create a new index in message_participants (conversation_id, user_id) to index the conversations per users and then change your query with:
SELECT
COUNT(m.conversation_id) AS count
FROM
messages_message m
INNER JOIN
messages_participants p ON p.conversation_id = m.conversation_id AND p.user_id = 5
WHERE
m.timestamp > p.last_read
So that your DB engine can now filter the JOIN by simply looking at the index table. You could go deeper in this thought by indexing the timestampe too : (timestamp, conversation_id, user_id) and put the where condition in the join condition.
Whatever you choose, always put the most selective field first, to increase selectivity.
EDIT
First, let's comment your query:
SELECT
SQL_CALC_FOUND_ROWS c.*,
last_msg.*,
new_msgs.count as new_msgs_count
FROM
messages_conversation c
INNER JOIN
messages_participants p ON p.user_id = 5 -- Join with every conversations of user 5; if id is an integer, avoid writing '5' (string converted to an integer).
INNER JOIN
( -- Select every message : you could already select here messages from user 5
SELECT
*
FROM
messages_message m
ORDER BY -- this is not the goal of ORDER BY. Use MAX to obtain to latest timestamp.
m.timestamp DESC
LIMIT 1
) last_msg ON c.id = last_msg.conversation_id -- this query return one row and you want to have the latest timestamp for each conversation.
LEFT JOIN
(
SELECT
COUNT(m.id) AS count,
m.conversation_id,
m.timestamp
FROM
messages_message m
) new_msgs ON c.id = new_msgs.conversation_id AND new_msgs.timestamp > p.last_read
LIMIT 0,10
Let's rephrase your query:
select the number of new messages of a conversation subject, its last message and timestamp for user #id.
Do it step by step:
Selecting last message, timestamp in conversation for each user:
SELECT -- select the latest timestamp with its message
max(timestamp),
message
FROM
messages_message
GROUP BY
user_id
Aggregates functions (MAX, MIN, SUM, ...) work on the current group. Read this like "for each groups, calculate the aggregate functions, then select what I need where my conditions are true". So it will result in one row per group.
So this last query selects the last message and timestamp of every user in the messages_message table. As you can see, it is easy to select this value for a specific user adding the WHERE clause:
SELECT
MAX(timestamp),
message
FROM
messages_message
WHERE
user_id = #id
GROUP BY
user_id
Number of messages per conversation: for each conversation, count the number of messages
SELECT
COUNT(m.id) -- assuming id column is unique, otherwise count distinct value.
FROM
messages_conversation c
INNER JOIN -- The current user participated to the conversation
messages_participant p ON p.conversation_id = c.id AND p.user_id = #id
OUTER JOIN -- Messages of the conversation where the current user participated, newer than last read its time
messages_message m ON m.conversation_id = c.id AND m.timestamp > p.last_read = #id
GROUP BY
c.id -- for each conversation
INNER JOIN won't return rows for conversations where the current user did not participated.
Then OUTER JOIN will join with NULL columns if the condition is false, so that COUNT will return 0 - there is not new messages.
Putting it all together.
Select the last message and timestamp in conversation where the current user participated and the number of new messages in each conversation.
Which is a JOIN between the two last queries.
SELECT
last_msg.conversation_id,
last_msg.message,
last_msg.max_timestamp,
new_msgs.nb
FROM
(
SELECT
MAX(timestamp) AS max_timestamp,
message,
conversation_id
FROM
messages_message
WHERE
user_id = #id
GROUP BY
user_id
) last_msg
JOIN
(
SELECT
c.id AS conversation_id
COUNT(m.id) AS nb
FROM
messages_conversation c
INNER JOIN
messages_participant p ON p.conversation_id = c.id AND p.user_id = #id
OUTER JOIN
messages_message m ON m.conversation_id = c.id AND m.timestamp > p.last_read = #id
GROUP BY
C.id
) new_msgs ON new_msgs.conversation_id = last_msg.conversation_id
-- put here and only here a order by if necessary :)