Query: How to obtain the most recent message per one user conversations? - mysql

I have a conversations table that is very simple but important (created_by is the code of the user who sent the message). Then, I have a conversations_messages table (sent_by is the code of the user who sent the message, message is my message text and created_at is the timestamp). Finally I have a conversations_users table that has the user_id (code of the user who received the message) and created_by (code of the user who sent the message).
I am trying to implement the functionality of inbox, I want to show the last message per conversation. user_id in conversations_users is the user who received the message and the user who is consulting his inbox.
I tried this query but I don't know how to deal with the created_at timestamp (in table conversations_messages) in order to get the most recent message per conversation.
select CM.id, CM.message, CM.created_at, C.id as 'conversation_id' from conversations_users CU
join conversations C on CU.conversation_id = C.id join conversations_messages CM on C.id = CM.conversation_id where CU.user_id = 1 and CM.created_at = '2015-03-10 14:18:02'
I have to replace '2015-03-10 14:18:02' for some most recent created_at timestamp thats my problem.
My tables are:
conversations_users: id, conversation_id, user_id, created_by
conversations: id, created_by
conversations_messages: id, conversation_id, sent_by, message, created_at
Thanks!

SELECT cm.*
FROM conversations_messages cm
JOIN conversations c ON cm.conversation_id = c.id
JOIN conversations_users cu ON cu.conversation_id = c.id
WHERE cu.user_id = 1
AND NOT EXISTS ( SELECT 'a'
FROM conversations_messages cm2
WHERE cm2.conversation_id = cm.conversation_id
AND cm2.created_at > cm.created_at
)
GROUP BY c.id

Here's one approach joining the table back to itself using the max aggregate, grouping by each conversation:
select CM.id, CM.message, CM.created_at, C.id as 'conversation_id'
from conversations_users CU
join conversations C on CU.conversation_id = C.id
join conversations_messages CM on C.id = CM.conversation_id
join (
select max(created_at) maxcreated_at, conversation_id
from conversations
group by conversation_id
) T on T.maxcreated_at = CM.created_at
and T.conversation_id = CM.conversation_id
where CU.user_id = 1

Related

Adding a extra subquery to MySQL query

So i have 2 tables, users and messages
users table
uid, username
messages table
mid, senderid, receiverid, message, timestamp
The query i have at the moment is retrieving the uid, username and last timestamp of each chat conversation
SELECT DISTINCT
IF (messages.senderid = '5e9b95786a71f8.25790415', messages.receiverid, messages.senderid) as uid,
(SELECT username FROM users WHERE uid = IF (messages.senderid = '5e9b95786a71f8.25790415', messages.receiverid, messages.senderid)) as username,
MAX(messages.timestamp) as last_timestamp
FROM messages JOIN users ON users.uid = messages.senderid WHERE messages.senderid = '5e9b95786a71f8.25790415' OR messages.receiverid = '5e9b95786a71f8.25790415'
GROUP BY 1,2
which outputs below
uid | username | last_timestamp
1 | Developer | 1601826378
2 | BetaTester | 1601826301
What i need to add to this query is the message field which is based on the last_timestamp i have in the output.
How can the query be updated to include the extra message field?
Try this.
select A.uid, C.username, B.timestamp, B.message from (SELECT DISTINCT
IF (messages.senderid = '5e9b95786a71f8.25790415', messages.receiverid, messages.senderid) as uid,
MAX(messages.mid) as max_mid
FROM messages WHERE messages.senderid = '5e9b95786a71f8.25790415' OR messages.receiverid = '5e9b95786a71f8.25790415'
GROUP BY 1) A join messages B on A.max_mid = B.mid join users C on A.uid = C.uid
I'm a little confuse, since you already got last_timestamp why don't just left join message table again.
left JOIN messages m2 on last_timestamp = m2.timestamp and mid = m2.mid
and just select message column
select m2.message

MYSQL Many-to-many select (chats, latest message, username) in one statement

I'm trying to select a list of the user's chats in one statement, together with each chats latest message and the massage's sender's name.
The abstract problem is:
Ever chat can have multiple participating users.
A message is sent to a "chat", not a specific user.
These are the tables I came up with
TABLE users (id, username)
TABLE chats (id)
TABLE messages (id, message, chat_id, user_id)
And lastly a many-to-many relation table between chats and participants
TABLE chats_users (chat_id,user_id)
I want to list all the chats a certain user participates in, together with the latest message and it's sender's username.
How would I go about that in one statement?
Im running Mysql 5.0.9 and php 7.
This query:
select max(m.id) id
from chats_users cu
inner join users u on cu.user_id = u.id
inner join messages m on m.chat_id = cu.chat_id
where cu.chat_id in (select chat_id from chats_users where user_id = ?)
group by cu.chat_id
returns the last messages of all the chats that a certain user participates.
Use it in this query:
select m.chat_id, m.message, u.username
from messages m inner join users u
on u.id = m.user_id
where m.id in (
select max(m.id) id
from chats_users cu
inner join users u on cu.user_id = u.id
inner join messages m on m.chat_id = cu.chat_id
where cu.chat_id in (select chat_id from chats_users where user_id = ?)
group by cu.chat_id
)
to get the details of these messages.

Can't integrate INNER JOIN in my query

Hello I'm using the query below to select the latest message from each unique conversation. Everything works perfectly except when I try to integrate an INNER JOIN to try to link the user's user_id with their username in the users table.
I've tried pretty much every combination to integrate the INNER JOIN so I decided to separate it from the working query until I get help.
I made a Fiddle http://sqlfiddle.com/#!9/a9bbc/1 I just want user_id on the right to print the user's username
SELECT message_id,
msg,
user_id
FROM messages
JOIN (SELECT user_id,
Max(dat) m
FROM ((SELECT message_id,
recipient_id user_id,
dat
FROM messages
WHERE owner_id = 1)
UNION
(SELECT message_id,
owner_id user_id,
dat
FROM messages
WHERE recipient_id = 1)) t1
GROUP BY user_id) t2
ON ( ( owner_id = 1
AND recipient_id = user_id )
OR ( owner_id = user_id
AND recipient_id = 1 ) )
AND ( dat = m )
ORDER BY dat DESC
.
users.username INNER JOIN users ON messages.user_id = users.user_id;
If messages.message_id is an AUTO_INCREMENT PRIMARY KEY column, this will probably be the fastest way to receive the desired result:
select m.message_id, m.msg, u.username,
case sub.other_user_id
when m.owner_id then 'received from'
when m.recipient_id then 'sent to'
end as direction
from (
select other_user_id, max(message_id) as message_id
from (
select recipient_id as other_user_id, max(message_id) as message_id
from messages
where owner_id = #uid
group by recipient_id
union all
select owner_id as other_user_id, max(message_id) as message_id
from messages
where recipient_id = #uid
group by owner_id
) sub
group by other_user_id
) sub
join messages m on m.message_id = sub.message_id
join users u on u.user_id = sub.other_user_id
order by sub.message_id desc
I've also added the column direction. This way you will know if the message has been sent or received. The result would be like:
| message_id | msg | username | direction |
|------------|------------------------------------------------------------------|----------|---------------|
| 9 | You should also see this | User3 | sent to |
| 8 | you should Now see this instead even with the owner_id flipped.. | User2 | received from |
http://sqlfiddle.com/#!9/a9bbc/34
Note that you can still add any column from the users and messages tables in the SELECT clause.
To get this query work fast you will need indexes on messages(owner_id, recipient_id) and messages(recipient_id, owner_id).
I think this is correct... finally. The not exists filters out responses after the start.. the inner query grabs the most recent conversation. This should match the expected output.
SELECT dT.maxid AS message_id
,(SELECT msg FROM messages M WHERE M.message_id = dT.maxid) AS message_id
,(SELECT username FROM users U WHERE U.user_id = dT.owner_id) AS user_id
FROM (
SELECT MAX(message_id) as maxid
,owner_id
,recipient_id
FROM messages M
GROUP BY owner_id, recipient_id
) AS dT
WHERE NOT EXISTS(SELECT *
FROM messages M2
WHERE M2.recipient_id = dT.owner_id
AND M2.owner_id = dT.recipient_id
AND M2.message_id > dT.maxid)
ORDER BY dT.maxid
Produces output:
message_id message_id user_id
8 you should Now see this instead even with the User2
owner_id flipped.. The users_id in the query is 1
and it gets the conversation with user_id 2
9 You should also see this User1
fiddle
When you have joined any tables or subqueries, you really really really need to include table name or table aliases with all column references. I changed the maximum dat to maxdat below so I could use m for messages.
SELECT
m.message_id
, m.msg
, m.user_id
FROM messages m
JOIN (
SELECT
user_id
, MAX(dat) maxdat
FROM (
(SELECT
message_id
, recipient_id user_id
, dat
FROM messages
WHERE owner_id = 1)
UNION
(SELECT
message_id
, owner_id user_id
, dat
FROM messages
WHERE recipient_id = 1)
) t1
GROUP BY
user_id
) t2 ON (
(m.owner_id = 1 AND m.recipient_id = t2.user_id)
OR (m.owner_id = t2.user_id AND m.recipient_id = 1)
)
AND (m.dat = t2.maxdat)
ORDER BY
m.dat DESC
You need to join your final result with users table and get the value of users.username based on user_id. Try this:
SELECT messages.message_id,
messages.msg,
users.user_id,
users.username
FROM users, messages
JOIN (SELECT user_id,
Max(dat) m
FROM ((SELECT message_id,
users.username,
recipient_id user_id,
dat
FROM messages
INNER JOIN users ON messages.recipient_id = users.user_id
WHERE owner_id = 1)
UNION
(SELECT message_id,
users.username,
owner_id user_id,
dat
FROM messages
INNER JOIN users ON messages.owner_id = users.user_id
WHERE recipient_id = 1)) t1
GROUP BY user_id) t2
ON ( ( owner_id = 1
AND recipient_id = user_id )
OR ( owner_id = user_id
AND recipient_id = 1 ) )
AND ( dat = m )
WHERE users.user_id = messages.user_id
ORDER BY dat DESC
If I understand correctly, you want the most recent messages where the send or recipient is "1" and associated user names.
That suggests:
select m.*, ur.username as recipient_name, us.username as sender_name
from messages m join
users ur
on m.recipient_id = ur.user_id join
users us
on m.sender_id = us.user_id
where m.date = (select max(m2.dat)
from messages m2
where 1 in (m2.recipient_id, m2.sender_id)
) and
1 in (m.recipient_id, m.sender_id);
EDIT:
Based on your SQL Fiddle the above understanding is not correct. You want to consider pairs of recipients, so I think you want:
select m.*, ur.username as recipient_name, us.username as sender_name
from messages m left join
users ur
on m.recipient_id = ur.user_id left join
users us
on m.owner_id = us.user_id
where m.dat = (select max(m2.dat)
from messages m2
where (m2.recipient_id, m2.owner_id) = (m.recipient_id, m.owner_id) or
(m2.recipient_id, m2.owner_id) = (m.owner_id, m.recipient_id)
) and
1 in (m.recipient_id, m.owner_id);
Here is the SQL Fiddle.
If the ordering of the pair is import (so (1, 2) is different from (2, 1) remove the second condition in the innermost where clause.

Find records which are not exists in other table

I've this table structure:
-request
request_id
user_id
-user
user_id
company_id
-company
company_id
I want to select all those records from requests table where user_id=? and no such records where the company id of to users is same.
This is usually achieved using LEFT JOIN:
SELECT r.*
FROM request r
JOIN user u ON r.user_id = u.user_id
LEFT JOIN u1 ON u1.user_id != u.user_id AND u1.company_id = u.company_id
LEFT JOIN request r1 ON r1.user_id = u1.user_id
WHERE r1.user_id IS NULL
By "where" we say that we don't want "users with same company, who has at least 1 request"

Complex MySQL search query

I have below query running on my users and chats table to join and I'm trying to group by on my chats table for user_id OR receiver_id not just user_id like you can see below in SQL query inside INNER JOIN.
SELECT c.*, users.username, users.firstname, users.lastname
FROM chats c
INNER JOIN( SELECT MAX(created) AS Date, user_id, receiver_id, chat, type, id
FROM chats
WHERE receiver_id = 286 OR user_id = 286
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 = 286 OR c.user_id = 286
I tried GROUP BY user_id OR receiver_id but I can't seem to get information for id 286 for either receiver_id or user_id.
Is there a way we can achieve it and group by so that we can look into both columns and come up with results from both?
Do I need to do two queries and join them together instead?
If you are looking for most recent chat record, why don't you just sort the results by created and get the first row like this:
SELECT c.*, users.username, users.firstname, users.lastname
FROM chats c, users u
WHERE (c.receiver_id = 286 OR c.user_id = 286) AND
u.id = c.user_id
ORDER BY created DESC
LIMIT 1