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

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

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: Get latest message from 2 tables that are associated with eachother

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
)

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

SQL query to get historic data from table

I have a table that stores the history of status changes to a person like this:
id | username | date | status
The date field is when the status was updated and the status field contains the new status that the person has since that date.
So the date in my table could be something like this:
1 | serafeim | 2012-03-03 | "NEW"
2 | john | 2012-03-05 | "NEW"
3 | serafeim | 2012-03-13 | "PENDING"
4 | serafeim | 2012-03-15 | "OLD"
5 | john | 2012-03-05 | "PENDING"
etc etc.
Now, I'd like to have a query that for a specific date in the past will retrieve the status that each user had then. For instance, for 2012-04-14 I'd like to get the following results
serafeim | "PENDING"
john | "NEW"
for 2012-03-04 I should get
serafeim | "NEW"
Can anybody think of an SQL query that will do that ? I don't want to do this programatically ! I'm using mysql but I don't think that that's relative to my problem...
Thanks in advance
The following query identifies the latest record for a given username, before the date specified, and joins the history table with that latest record ID to fetch the rest of the details.
SELECT a.*
FROM
history a
JOIN (SELECT username, MAX(id) 'id' FROM history
WHERE date < #inputDate
GROUP BY username
) as b
ON a.id = b.id
Get the first record of the user having the date less or equal to the input date:
declare #userid nvarchar(128)
declare #date datetime
SELECT userid, status
FROM
(
SELECT limit 1 * FROM mytable
WHERE date <= #date
AND userid = #userid
ORDER by date desc
)
Untested! And sorry if any syntax error.
I cannot test right now on a MySql database, but this query should do the job.
The table2 query retrieve the max date in which you registered an event for every user name before the desired date: this should be the last status event for that person.
The join get the status.
select username, status from table
join
(
select username, max(date) as maxdate from table
where date <= '2012-04-14'
group by username ) table2
on table.username = table2.username and table.date = table.2maxdate
Another way could be without join
select username, status from table
where date = (select max(date) as maxdate from table
where date <= '2012-04-14'
group by username )

Query returns additional rows

I have this kind of table for simple chat:
messages table structure
+----+---------+-----------+---------+------+------+
| id | to_user | from_user | message | read | sent |
+----+---------+-----------+---------+------+------+
And i need to get list of each conversation which looks like that
Username ---- Message ---- Date
I am using this query to do it:
SELECT *
FROM `messages`
WHERE `sent`
IN (
SELECT MAX( `sent` )
FROM `messages`
WHERE `from_user` = '1' --id of user who is requesting the list
OR `to_user` = '1' --id of user who is requesting the list
GROUP BY `to_user` , `from_user`
)
LIMIT 0 , 30
And this works almost fine, my problem is that it returns me not the last message of that conversation but last message from each user so let's say user 1 and 2 is talking and i'm getting this list, this is what i get:
+----+---------+-----------+-----------------------+------+---------------------+
| id | to_user | from_user | message | read | sent |
+----+---------+-----------+-----------------------+------+---------------------+
| 3 | 2 | 1 | Message 1 from user 1 | 0 | 2012-01-11 13:20:54 |
| 4 | 1 | 2 | Message 1 from user 2 | 0 | 2012-01-11 13:24:59 |
+----+---------+-----------+-----------------------+------+---------------------+
And i would like to get only last message which is 4, cause sent field is the highest in 4th record so how can i solve it?
EDIT After deleting group by i'm getting only one message even if user was talking with more than one user
Here's how you do it:
SELECT *
FROM (SELECT *
FROM messages
WHERE from_user = ?
OR to_user = ?
ORDER by from_user, to_user, sent DESC
) x
GROUP BY from_user, to_user
ORDER BY sent DESC
LIMIT 1;
In mysql, a group by without aggregating the other columns returns the first row for each group. By selecting form an ordered row set (the inner query) we get the most recent row for each conversation.
SELECT *
FROM `messages`
WHERE `sent`
IN (
SELECT MAX( `sent` )
FROM `messages`
WHERE (`from_user` = '1' OR `to_user` = '1')
)
LIMIT 0 , 30
the groupby is going to conbine them i believe.
You are getting the last message from each user because you have done GROUP BY for both: to_user and from_user.
There is no need to use GROUP BY clause in your query.
Remove the group by clause in your in statement--it's useless in this case. It's returning a sent timestamp for each distinct pairing of to_user and from_user. You really just want the max sent where to_user or from_user equal some value. Lose the group by, and you'll return exactly one record showing the latest message either to or from a user.
It looks like this:
SELECT *
FROM `messages`
WHERE `sent`
IN (
SELECT MAX( `sent` )
FROM `messages`
WHERE `from_user` = '1' --id of user who is requesting the list
OR `to_user` = '1' --id of user who is requesting the list
)
LIMIT 0 , 30