MySQL: Get latest message from 2 tables that are associated with eachother - mysql

First, thank you so much for your help.
I have 2 tables: a conversation table and a message table, and a third table assoc_message__conversation that associates the messages to a conversation.
I need to get the latest message_id and message sent for each conversation specified, along with the conversation_id it is associated with.
Here is a db-fiddle: https://www.db-fiddle.com/f/kxRQeGUYYgQ7FTwi96hbLp/0
As you can see in this example, there are two conversations with conversation_id of 1 and 2, and there are three messages associated to each conversation. Messages 1, 2, and 3 are associated to conversation 1, and messages 4, 5 and 6 are associated to conversation 2.
I need to be able to specify the conversation_id's in the assoc_message__conversation table (IDs 1 and 2), and retrieve the latest message_id, message and the associated conversation_id sent from the message table for each conversation specified.
So the rows it should pull are:
conversation_id | message_id | message
------------------------------------------------
1 | 3 | "Latest message"
------------------------------------------------
2 | 6 | "Latest message"
------------------------------------------------
Thank you so much for your help!

In older versions of MySQL (< 8.0.2), we can use Derived Tables. In a Derived table, we can get the latest send_datetime value for each conversation_id. Also, it is noteworthy that you can provide your filters for conversation_id in the WHERE clause of this subquery.
We can then use this subquery`s result-set and join back to the main tables appropriately, to get the row corresponding to the latest message in a conversation.
Schema (MySQL v5.7)
View on DB Fiddle
Query #1
SELECT
amc.conversation_id,
m.message_id,
m.message
FROM
assoc_message__conversation AS amc
JOIN message AS m
ON m.message_id = amc.message_id
JOIN
(
SELECT
amc1.conversation_id,
MAX(m1.send_datetime) AS latest_send_datetime
FROM
assoc_message__conversation AS amc1
JOIN message AS m1
ON m1.message_id = amc1.message_id
WHERE amc1.conversation_id IN (1,2) -- Here you provide your input filters
GROUP BY amc1.conversation_id
) AS dt
ON dt.conversation_id = amc.conversation_id AND
dt.latest_send_datetime = m.send_datetime;
Result
| conversation_id | message_id | message |
| --------------- | ---------- | -------------- |
| 1 | 3 | Latest message |
| 2 | 6 | Latest message |
In MySQL 8.0.2 and above, we can use Row_Number() functionality. Within a partition of conversation_id, we will determine Row Number for every message, sorted in descending order of send_datetime. In this subquery, you can provide your filters for conversation_id in the WHERE clause.
We will then use this result-set as a Derived Table, and consider only those rows, where Row Number value is 1 (as it will belong to latest send_datetime).
Schema (MySQL v8.0)
View on DB Fiddle
Query #2
SELECT
dt.conversation_id,
dt.message_id,
dt.message
FROM
(
SELECT
amc.conversation_id,
m.message_id,
m.message,
ROW_NUMBER() OVER (PARTITION BY amc.conversation_id
ORDER BY m.send_datetime DESC) AS row_no
FROM
assoc_message__conversation AS amc
JOIN message AS m
ON m.message_id = amc.message_id
WHERE amc.conversation_id IN (1,2) -- Here you provide your input filters
) AS dt
WHERE dt.row_no = 1;
Result
| conversation_id | message_id | message |
| --------------- | ---------- | -------------- |
| 1 | 3 | Latest message |
| 2 | 6 | Latest message |

Assuming that amc_id increments for each new message, I would recommend a correlated subquery in the where clause:
select amc.*, m.message
from message m join
assoc_message__conversation amc
on amc.message_id = m.message_id
where amc.amc_id = (select max(amc.amc_id)
from assoc_message__conversation amc2
where amc2.conversation_id = amc.conversation_id
);
If you actually need to use send_datetime, then an additional join is necessary:
where m.send_datetime = (select max(m2.send_datetime)
from message m2 join
assoc_message__conversation amc2
on amc2.message_id = m2.message_id
where amc2.conversation_id = amc.conversation_id
)

Related

How to get records ordered by joined table field

I have two tables:
chat:
id
name
chat_messages:
id
chat_id
user_id
message
created_at
I want to get a list of chats names ordered by recent activity. In other words, I want to get a list of chats where the first one is that with the bigger created_at field, and the last one is that with the smaller created_at field.
For example:
chat table:
1 General
2 News
chat_messages:
1 | 1 | 20 | Hello everybody | 2020-10 18:00:00
1 | 1 | 23 | this is a me... | 2020-10 18:00:05
1 | 1 | 15 | another message | 2020-10 18:00:15
1 | 2 | 22 | Anybody there? | 2020-10 17:00:00
1 | 2 | 45 | Hello?????????? | 2020-10 16:00:00
The desired result would be: ['News', 'General']
Any help?
Thanks
Try this:
SELECT DISTINCT `chat`.`name`
FROM `chat` JOIN `chat_message` ON `chat`.`id` = `chat_message`.`chat_id`
ORDER BY `chat_message`.`created_at` DESC
Explanation:
You only want the chat name to be returned. And you also want each value only once (that's the DISTINCT).
You ORDER BY the created_at field, and then the only thing left to do is to JOIN the tables.
Edit: you can try / improve here.
Select last Created_at for every chat:
SELECT m.chat_id,
max(m.created_at) as LastActivity
FROM chat_message as m
group by m.chat_id;
Let's find some infos about chats, by joining the chat table:
SELECT c.name,
info.LastActivity
FROM ( SELECT m.chat_id,
max(m.created_at) as LastActivity
FROM chat_message as m
group by m.chat_id) info -- virtual table "info" from first query
JOIN chat as c ON c.id = info.chat_id; -- add table chat to query
Now we want to add some info about the LastActivity
SELECT c.name,
info.LastActivity,
m2.user_id, -- Print Infos of LastActivityMessage
m2.message
FROM ( SELECT m.chat_id,
max(m.created_at) as LastActivity
FROM chat_message as m
group by m.chat_id) info
JOIN chat as c ON c.id = info.chat_id
JOIN chat_message as m2 ON info.LastActivity = m2.created_at; -- Search for Message at LastActivity
In out last query we see a problem: We do not want to search for a messag with it's date!
We're missing a propper primary-Key in table chat_message.
I'd suggest to calculate a unique id:
create table chat_message
(
id int not null auto_increment primary key, -- this is the message-id which will automatically set
chat_id int not null, -- where did the user post?
user_id int not null, -- which user?
message text,
created_at datetime
);
If we insert our Messages now like this:
INSERT INTO chat_message (chat_id, user_id, message, created_at)
VALUES
(2, 45, "Hello?", "2020-10-01 16:00:00"),
(2 , 22 , "Anybody there?" , "2020-10-01 17:00:00"),
(1, 20, "Hello everybody", "2020-10-01 18:00:00"),
(1, 23, "this is a me...", "2020-10-01 18:00:05"),
(1, 15, "another message", "2020-10-01 18:00:15");
..Ids are generated automatically:
id chat_id user_id message created_at
1 2 45 Hello? 2020-10-01T16:00:00Z
2 2 22 Anybody there? 2020-10-01T17:00:00Z
3 1 20 Hello everybody 2020-10-01T18:00:00Z
4 1 23 this is a me... 2020-10-01T18:00:05Z
5 1 15 another message 2020-10-01T18:00:15Z
This leads to a better query without searching for a single message with it's date:
-- Now we want to add some info about the LastActivity
SELECT c.name,
m2.id,
m2.created_at,
m2.user_id,
m2.message
FROM ( SELECT m.chat_id,
max(m.id) as LastActivity -- select last Id
FROM chat_message as m
group by m.chat_id) info
JOIN chat as c ON c.id = info.chat_id
JOIN chat_message as m2 ON info.LastActivity = m2.id; -- search for message with correct id

MySql multiple GROUP BY alternative

I have two tables: teams and messages
teams
========
id | name
messages
============================
id | team_id | created_date
I need a result set that looks like this:
team name | number of messages | most recent message
For example:
Joe Blow's Group | 23 | 2018-08-29
My initial try at this:
select team.id, team.name, count(messages.team_id) as 'Number of
Messages', messages.created_date as 'Last Message' from messages inner join
teams on messages.team_id = team.id group by team.name
Produced:
Joe Blow's Team | 23 | 2018-02-14
Bob's Team | 12 | 2018-04-15
etc etc
The issue is that the dates are of the oldest message created for that team, not the most recent. If I try to add sort by it only sorts this result set but the wrong dates. If I try multiple 'group by' I get a row for every message.
Does this need a subquery or is there a simpler way to have the created_date be the most recent?
Use MAX function to get the latest created_date.
SELECT group.id,
group.name,
COUNT(messages.group_id) AS 'Number of Messages',
MAX(messages.created_date) AS 'Last Message'
FROM messages
INNER JOIN groups ON messages.group_id = group.id
GROUP BY group.name

How to group by rows but with the most recent date?

In my MySQL database I have a table like this used for storing conversation messages from any people
id int(11) id of the message
from member_id int(11) id of the person the message was sent from
to member_id int(11) id of the person the message was sent to
date sent datetime date of when it was sent
active tinyint(1) if the message is deleted
text longtext the text of the message
from_read tinyint(1) boolean to know if the person who sent it read it
to_read tinyint(1) boolean to know if the person who it got sent to read it
So for example, it could have like:
from_member_id to_member_id date sent
1 2 june 12
1 3 june 13
2 3 june 14
3 1 june 9
So we have a conversation between person 1 and 2, 1 and 3, 2 and 3.
I am trying to get a select statement which will give me the most recent message that the current user is involved with from every conversation that user is in. So if 1 is logged in then I would expect to get 2 rows. The first row in the result set would be the second row above (july 13) because its the most recent, then then the second row in the result set would be the first row above (june 12), which are the most recent from 1's two conversations. The result set also needs to be sorted by date sent, so newer conversations are listed on top.
What I am trying to do is like the texting in android phones, where you see the list of conversations, and the most recent message in each listing.
This is my sql query
SELECT *
FROM (
SELECT *
FROM message
WHERE `from member_id`=1 OR `to member_id`=1
ORDER BY IF(`from member_id`=1, `to member_id`, `from member_id`)
) as t
GROUP BY IF(`from member_id`=1, `to member_id`, `from member_id`)
I just hardcoded 1 for now to be the current user. What I am doing is, sorting them by the id of the other person which I can check using the if statement, then grouping that result so I try to get the recent one from each conversation.
The problem is that when grouping, each group can have more than 1 rows, and it just seems to pick some random row. How can I get it to pick the row that has the most recent date sent value?
Are you looking for something like this?
SELECT m.*
FROM message m JOIN
(
SELECT from_member_id, to_member_id, MAX(date_sent) date_sent
FROM message
WHERE from_member_id = 1
GROUP BY from_member_id, to_member_id
) q
ON m.from_member_id = q.from_member_id
AND m.to_member_id = q.to_member_id
AND m.date_sent = q.date_sent
ORDER BY date_sent DESC
Sample output:
| FROM_MEMBER_ID | TO_MEMBER_ID | DATE_SENT |
----------------------------------------------
| 1 | 3 | 2013-06-13 |
| 1 | 2 | 2013-06-12 |
Here is SQLFiddle demo
UPDATE
SELECT m.*
FROM message m JOIN
(
SELECT LEAST(from_member_id, to_member_id) least_id,
GREATEST(from_member_id, to_member_id) greatest_id,
MAX(date_sent) date_sent
FROM message
WHERE from_member_id = 1
OR to_member_id = 1
GROUP BY LEAST(from_member_id, to_member_id),
GREATEST(from_member_id, to_member_id)
) q
ON LEAST(m.from_member_id, m.to_member_id) = q.least_id
AND GREATEST(m.from_member_id, m.to_member_id) = q.greatest_id
AND m.date_sent = q.date_sent
ORDER BY date_sent DESC
Sample output:
| FROM_MEMBER_ID | TO_MEMBER_ID | DATE_SENT |
----------------------------------------------
| 3 | 1 | 2013-06-14 |
| 1 | 2 | 2013-06-12 |
Here is SQLFiddle demo
SELECT
*
FROM message m INNER JOIN
(
SELECT
from_menber_id,
MAX(date_sent) AS sentdate
FROM message s
GROUP BY from_menber_id
) AS a
ON m.date_sent = a.sentdate AND a.from_menber_id = m.from_menber_id

MySQL query to get first unique values of one column on multiple tables

I have the following SQL query which queries my tickets, ticketThreads, users and threadStatus tables:
SELECT tickets.threadId, ticketThreads.threadSubject, tickets.ticketCreatedDate, ticketThreads.threadCreatedDate, threadStatus.threadStatus, users.name
FROM
tickets
INNER JOIN
ticketThreads
ON
tickets.threadId = ticketThreads.threadId
INNER JOIN
threadStatus
ON
ticketThreads.threadStatus = threadStatus.id
INNER JOIN
users
ON
users.id = ticketThreads.threadUserId
WHERE
tickets.ticketId = ticketThreads.lastMessage
AND
ticketThreads.threadStatus != 3
ORDER BY
tickets.ticketCreatedDate
DESC
The abridged version of what this returns is:
threadId |
----------
1 |
2 |
This works fine, and is what I expect, however to clean up the code and database slightly I need to remove the ticketThreads.lastMessage column.
If I remove the line WHERE tickets.ticketId = ticketThreads.lastMessage then this is an abridged version of what is returned:
threadId |
----------
1 |
2 |
1 |
What I need to do then is edit the query above to enable me to select the highest unique value for each threadId value in the tickets database.
I know about MAX() and GROUP BY but can't figure how to get them into my query above.
The relevant parts of the tables are shown below:
tickets
ticketId | ticketUserId | threadId
-------------------------------
1 | 1 | 1
2 | 1 | 2
3 | 1 | 1
ticketThreads
threadId | lastMessage | threadStatus
-------------------------------
1 | 3 | 4
2 | 2 | 1
I hope all the above is clear and makes sense
So you need the ticket with the highest id per each thread? Your problem is actually very easy variant of greatest record per group problem. No need for any subqueries. Basicaly you have two options, which both should perform much better than your query, the second be faster (please post the actual durations in your db!):
1. Standard compliant query, but slower:
SELECT t1.threadId, ticketThreads.threadSubject, t1.ticketCreatedDate,
ticketThreads.threadCreatedDate, threadStatus.threadStatus, users.name
FROM tickets as t1
LEFT JOIN tickets as t2
ON t1.threadId = t2.threadId AND t1.ticketId < t2.ticketId
JOIN ticketThreads ON t1.threadId = ticketThreads.threadId
JOIN threadStatus ON ticketThreads.threadStatus = threadStatus.id
JOIN users ON users.id = ticketThreads.threadUserId
WHERE t2.threadId is NULL
AND ticketThreads.threadStatus != 3
ORDER BY t1.ticketCreatedDate DESC
This one joins the tickets table two times, which can make it a bit slower for big tables.
2. Faster, but uses MySQL extension to standard SQL:
set #prev_thread := NULL;
SELECT t.threadId, ticketThreads.threadSubject, t.ticketCreatedDate,
ticketThreads.threadCreatedDate, threadStatus.threadStatus, users.name
FROM tickets as t
JOIN ticketThreads ON t.threadId = ticketThreads.threadId
JOIN threadStatus ON ticketThreads.threadStatus = threadStatus.id
JOIN users ON users.id = ticketThreads.threadUserId
WHERE ticketThreads.threadStatus != 3
AND IF(IFNULL(#prev_thread, -1) = #prev_thread := t.threadId, 0, 1)
ORDER BY t.threadId, t.ticketId DESC,
t.ticketCreatedDate DESC
Here, we perform one pass scan on ordered joined data, using auxiliary mysql variable #prev_thread to filter only the first (in the given order) ticket for each thread (the one with highest ticketId).

SQL distinct between different columns

I have a table that contains 2 ids that I want to retrieve in a query, and I want them to be distinct. However I want them to be distinct between columns, so:
| column1 | column2 |
| 2 | 3 | row1
| 2 | 4 | row2
| 3 | 2 | row3
| 3 | 2 | row3
should return row1 and row2. Any help?
EDIT:
Sorry, that was sorta vague and I was in a rush when posting.
So both of these columns are IDs that reference the same table. Let's think of this as a message between users, where the table in question is a message and the IDs are the user IDs (one for sender, one for receiver). So the messages table looks a little like this:
| sender_id | receiver_id |
| 2 | 3 | message1
| 2 | 3 | message2
| 3 | 2 | message3
Each user can message any other as much as they want, so I can have many messages between each user. I want to know which users a given user has sent a message to. So I need a query to find who the user with ID 2 has messaged. So this theoretical query, given user ID 2, should only return user ID 3. I don't actually care about these messages that I'm searching through, just the users associated with them, who are not the user I'm searching against, and I want a list with unique values.
I've tried something like:
SELECT sender_id, receiver_id FROM messages
WHERE sender_id = 2 OR receiver_id = 2
GROUP BY sender_id
GROUP BY receiver_id
and
SELECT DISTINCT sender_id, receiver_id FROM messages
WHERE sender_id = 2 OR receiver_id = 2
but these only return a distinct list for one of the IDs. (ie. it would return user ID 3 twice ). I hope this is enough info to help.
**
UPDATE:
**
this is the query I ended up using:
SELECT DISTINCT other_user_id FROM (
SELECT sender_id AS some_user_id,
receiver_id AS other_user_id FROM messages
UNION
SELECT receiver_id AS some_user_id,
sender_id AS other_user_id FROM messages
)
WHERE some_user_id = whatever_value
Assuming that column1 and column2 are both numeric data types and you are comparing 2 columns you can use the following
SELECT DISTINCT
LEAST( column1 , column2 ) AS leastColumn,
GREATEST( column1 , column2 ) AS greatestColumn
FROM yourTable
I'm not entirely sure why you would wish to do this though, as if the two columns store the same data then this table is part of an n:m relationship and so this could be further normalised. Additionally this would be come horribly inefficient as the dataset grows but for your specific question this fits the bill.
Figured it out. This is the query I ended up using:
SELECT DISTINCT other_user_id FROM (
SELECT sender_id AS some_user_id,
receiver_id AS other_user_id FROM messages
UNION
SELECT receiver_id AS some_user_id,
sender_id AS other_user_id FROM messages
)
WHERE some_user_id = whatever_value