Extract only 1 message from all senders - mysql

I am building a messaging system and need to extract only the last message from each sender to a specified recipient. So, if 3 people each sent 5 messages(total of15 messages) to the recipient, I need to get 3 entries; the last message from each sender.
Here is my current SQL:
SELECT
messages.*,
user_accounts.uacc_id,
user_accounts.uacc_username,
user_profiles.upro_image_name
FROM messages
LEFT JOIN user_accounts
ON messages.msg_from_uacc_fk = user_accounts.uacc_id
LEFT JOIN user_profiles
ON user_profiles.upro_uacc_fk = user_accounts.uacc_id
WHERE
messages.msg_to_uacc_fk = ?
ORDER BY
msg_id
DESC
I tried adding 'MAX(1)' to the SELECT as well as 'LIMIT = 1' to after the DESC, but, of course, this just returned a total of 1 message.

It is sometimes hard to guess how the tables are designed but this query below uses a subquery to get the latest message for each user.
SELECT a.*,
c.uacc_id,
c.uacc_username,
d.upro_image_name
FROM messages a
INNER JOIN
(
SELECT msg_from_uacc_fk, MAX(msg_id) max_id
FROM messages
GROUP BY msg_from_uacc_fk
) b ON a.msg_from_uacc_fk = b.msg_from_uacc_fk AND
a.msg_id = b.max_id
INNER JOIN user_accounts c
ON a.msg_from_uacc_fk = c.uacc_id
INNER JOIN user_profiles d
ON d.upro_uacc_fk = c.uacc_id
WHERE a.msg_to_uacc_fk = ?
If this doesn't solve the problem, please add sample records along with your question :)

Can you not simply use a group by?
SELECT u.uacc_username, max(m.msg_id) as LatestMsg
FROM messages m JOIN user_accounts u on m.msg_from_uacc_fk = u.uacc_id
WHERE m.msg_to_uacc_fk = ?
GROUP BY u.uacc_username

Related

Returning the Max value of a half the composite key MYSQL

I am trying to query a table that has a composite key made up of two integers. The relationship is
What I am trying to get as a result is for every conversation that has been started, I want the MAX(threadNum) from the messages table. Currently the query is
SELECT c.conversation_id
FROM conversation as c
INNER JOIN (
SELECT MAX(threadNum), user_from, user_to, message, dateTime, deleted,replied
FROM messages
GROUP BY conversation_id
) as m ON c.conversation_id = m.conversation_Id
WHERE (m.user_to ='$userId' OR m.user_from ='$userId') AND m.deleted = 0
The results that I am expecting for the conversation_Id and the threadNum would be :
35 5
34 4
33 55
one result for every conversation_Id and only the largest threadNum result. Currently I am getting a m.converation_Id is an unknown column. What is wrong with the query structure? And more importantly is there an easier way to do what I am attempting to do?
It looks like you want one row per conversation, along with the latest message in the conversation (that is, the message that has the greatest thread_id).
If so, that's a top-1-per-group problem. You can solve it by filtering with a correlated subquery:
select c.*, m.*
from conversation c
inner join messages m on m.conversation_id = c.conversation_id
where m.thread_num = (
select max(m1.thread_num)
from messages m1
where
m1.conversation_id = m.conversation_id
and m.deleted = 0
and :user_id in (m.user_from, m.user_to)
)
:user_id represents the query parameter for your query (you should use a parameterized query instead of munging the variable into the query string).
Alternatively, if you are running MySQL 8.0, you can use row_number():
select *
from (
select
c.*,
m.*,
row_number() over(partition by c.conversation order by m.thread_num desc) rn
from conversation c
inner join messages m on m.conversation_id = c.conversation_id
where m.deleted = 0 and :user_id in (m.user_from, m.user_to)
) t
where rn = 1

I'm getting more than one result per left row from a LEFT JOIN with a subquery

With this query I'm supposed to get the latest message from the chats table for every agreement I get, as well as all the information including business name, etc.
I kind of solved it using GROUP BY in the subquery, but it is not the way I wanna fix this, because I don't understand why it does act as a RIGHT JOIN, and WHY doesn't it order it in the way I meant in the subquery:
SELECT agreements.id, agreements.`date`, agreements.state, business.name, chat.message
FROM ((agreements JOIN
business_admin
ON agreements.business = business_admin.business AND business_admin.user = 1
) LEFT JOIN
business
ON business.id = agreements.business
) LEFT JOIN
(SELECT agreements_chat.agreement, agreements_chat.message
FROM agreements_chat
WHERE origin = 0
ORDER BY agreements_chat.`date` DESC
) AS chat
ON agreements.id = chat.agreement
I really appreciate your help, thank you so much!
It's not working because the subquery in your left join returns more than one rows, hence the duplication of rows you get.
SELECT agreements.id,
agreements.`date`,
agreements.state,
business.name,
chat.message
FROM agreements
JOIN business_admin
ON agreements.business = business_admin.business AND
business_admin.user = 1
LEFT JOIN
business
ON business.id = agreements.business
LEFT JOIN
agreements_chat chat
ON chat.origin = 0 AND
chat.agreement = agreements.id
LEFT JOIN
(
SELECT agreement, max(`date`) last_date
FROM agreements_chat
GROUP BY agreement
) last_chat
ON chat.agreement = last_chat.agreement AND
chat.`date` = last_chat.last_date
Note that (as per #GordonLinoff comment) you don't need parenthese around your joins.

Left join sql query

I want to get all the data from the users table & the last record associated with him from my connection_history table , it's working only when i don't add at the end of my query
ORDER BY contributions DESC
( When i add it , i have only the record wich come from users and not the last connection_history record)
My question is : how i can get the entires data ordered by contributions DESC
SELECT * FROM users LEFT JOIN connections_history ch ON users.id = ch.guid
AND EXISTS (SELECT 1
FROM connections_history ch1
WHERE ch.guid = ch1.guid
HAVING Max(ch1.date) = ch.date)
The order by should not affect the results that are returned. It only changes the ordering. You are probably getting what you want, just in an unexpected order. For instance, your query interface might be returning a fixed number of rows. Changing the order of the rows could make it look like the result set is different.
I will say that I find = to be more intuitive than EXISTS for this purpose:
SELECT *
FROM users u LEFT JOIN
connections_history ch
ON u.id = ch.guid AND
ch.date = (SELECT Max(ch1.date)
FROM connections_history ch1
WHERE ch.guid = ch1.guid
)
ORDER BY contributions DESC;
The reason is that the = is directly in the ON clause, so it is clear what the relationship between the tables is.
For your casual consideration, a different formatting of the original code. Note in particular the indented AND suggests the clause is part of the LEFT JOIN, which it is.
SELECT * FROM users
LEFT JOIN connections_history ch ON
users.id = ch.guid
AND EXISTS (SELECT 1
FROM connections_history ch1
WHERE ch.guid = ch1.guid
HAVING Max(ch1.date) = ch.date
)
We can use nested queries to first check for max_date for a given user and pass the list of guid to the nested query assuming all the users has at least one record in the connection history table otherwise you could use Left Join instead.
select B.*,X.* from users B JOIN (
select A.* from connection_history A
where A.guid = B.guid and A.date = (
select max(date) from connection_history where guid = B.guid) )X on
X.guid = B.guid
order by B.contributions DESC;

Most recent related entries from another table

I've got a table of users (1,000s) and a table of user messages (100,000s). I want a fast way of getting all users and their most recent message.
What I'm currently using is something like...
SELECT
u.id, u.name,
(
SELECT note FROM msgs
WHERE msgs.uID=u.id
ORDER BY created_date DESC
LIMIT 1
) as note
FROM users u
Right now if I limit that to 20 users, it takes 2.5s ... 200 users takes 45s.
(I already have an INDEX on msgs.uID and msgs.created_date.)
What am I doing wrong? I need a much faster query.
I searched before posting (with no luck), but found this solution in the "related" sidebar just after posting.
SELECT u.id, u.first_name, msgs.note
FROM users u
JOIN (
SELECT MAX(created_date) max_date, user_id
FROM msgs
GROUP BY user_id
) msgs_max ON (msgs_max.user_id = u.id)
JOIN msgs m ON (msgs.created_date = msgs_max.max_date AND u.id = msgs.user_id)
Considerably better, but still ~1.3s on my tables. Can't MySQL do this much faster?
select users.*, msgs2.* from users
inner join
(
select msgs.* from msgs
inner join
(
select max(created_date) as dt, user_id from msgs
group by user_id
) last_dt
on
msgs.user_id = last_dt.user_id
and
msgs.created_date = last_dt.dt
) msgs2
on
users.id = msgs2.user_id
Try this, I am sorry if this may be has some syntax error, or may be also works slow, I write this query directly, without any test.
Just try.
Solution with just two joins
SELECT u.id,u.name,msgs_max.note FROM users u JOIN
(SELECT m1.uID, m1.note FROM msgs m1 LEFT JOIN msgs m2
ON (m1.created_date < m2.created_date
AND m1.uID = m2.uID)
WHERE m2.id IS NULL) msgs_max
ON u.uID=msg_max.uID
Reference: https://stackoverflow.com/a/123481/2180715

Get unread count with join

I have a problem getting this query to work, so the basic idea is:
I have messages table, I want to track whether user has read a message or not. Note: multiple users can receive same message, so simply adding column read to message is not an option
Each message is in a thread (has a column thread_id)
I have another table user_read_message which adds record whenever somebody reads a message (user_id, message_id, read)
I want to get number of unread messages for a user in a specific thread. I was trying something along these lines but I couldn't get it to work:
SELECT m.thread_id, urm.user_id, urm.read
FROM sup_messages as m
LEFT OUTER JOIN user_read_message as urm ON m.id = urm.message_id
WHERE m.thread_id = 76852 AND urm.user_id = 1337;
Which would if it worked selected all messages in thread_id 76852 then joined user_read_message where user_id is 1337 and messages which he hasn't read will simply have null. I would then somehow count where read is 0 or NULL.
ps. If there is better idea how to model this please let me know!
I would do this. Add your WHERE clause related to the user_read_message table into the JOIN to that table. Since this is a LEFT JOIN, all of the fields returned from that table will be NULL if there is no match. Add a field from that table to your WHERE clause that is always populated and then check to see if it is NULL. That would mean there is no match.
SELECT m.thread_id, 1337 AS user_id, COUNT(*) unread_messages
FROM sup_messages as m
LEFT OUTER JOIN user_read_message as urm
ON m.id = urm.message_id
AND urm.user_id = 1337
WHERE m.thread_id = 76852 AND urm.message_id IS NULL;
SELECT COUNT(*)
FROM sup_messages
WHERE sup_messages.thread_id = 76852 AND
sup_messages.id IN (SELECT DISTINCT urm.message_id
FROM urm
WHERE urm.user_id = 1337 AND urm.read = 0)