Reputation: 1911
I am trying to implement a message system quite similar to facebook . The message table is :
+--------+----------+--------+-----+----------+
| msg_id | msg_from | msg_to | msg | msg_time |
+--------+----------+--------+-----+----------+
Here msg_from
and msg_to
contain user ids and the msg_time
contains the timestamp of the message . A user's user id can appear in both the to and from column and multiple times for another user . How should I write a SQL query which selects the most recent sent message between two users ? (The message can come from either one) 1 to 2 or 2 to 1 .
Upvotes: 2
Views: 1912
Reputation: 753695
For any complex query like this, use TDQD — Test-Driven Query Design. Devise the answer step-by-step, with the size of the steps controlled by your experience and how well you understand the problem.
Throughout this, I assume that the user IDs are integers; I'm using the values 1000 and 2000.
SELECT MAX(msg_time) AS msg_time
FROM message
WHERE ((msg_to = 1000 AND msg_from = 2000) OR
(msg_to = 2000 AND msg_from = 1000)
)
SELECT m.*
FROM message AS m
JOIN (SELECT MAX(msg_time) AS msg_time
FROM message
WHERE ((msg_to = 1000 AND msg_from = 2000) OR
(msg_to = 2000 AND msg_from = 1000)
)
) AS t
ON t.msg_time = m.msg_time
WHERE ((m.msg_to = 1000 AND m.msg_from = 2000) OR
(m.msg_to = 2000 AND m.msg_from = 1000)
)
If there happen to be two (or more) messages between these characters with the same latest timestamp, then they'll all be selected; there is at present no basis for choosing between the collisions. If you think that's a problem, you can arrange to find the MAX(msg_id)
using the query above (as a sub-query):
SELECT m2.*
FROM message AS m2
JOIN (SELECT MAX(m.msg_id) AS msg_id
FROM message AS m
JOIN (SELECT MAX(msg_time) AS msg_time
FROM message
WHERE ((msg_to = 1000 AND msg_from = 2000) OR
(msg_to = 2000 AND msg_from = 1000)
)
) AS t
ON t.msg_time = m.msg_time
WHERE ((m.msg_to = 1000 AND m.msg_from = 2000) OR
(m.msg_to = 2000 AND m.msg_from = 1000)
)
) AS i
ON i.msg_id = m2.msg_id
Warning: Code not formally tested with any DBMS.
Upvotes: 1
Reputation: 39393
Since John Woo clarified that it is not directional, here's my new answer:
select *
from msgsList
where (least(msg_from, msg_to), greatest(msg_from, msg_to), msg_time)
in
(
select
least(msg_from, msg_to) as x, greatest(msg_from, msg_to) as y,
max(msg_time) as msg_time
from msgsList
group by x, y
);
Output:
| MSG_ID | MSG_FROM | MSG_TO | MSG | MSG_TIME |
------------------------------------------------------------------------
| 1 | 1 | 2 | hello | January, 23 2010 17:00:00-0800 |
| 5 | 1 | 3 | me too | January, 23 2012 00:15:00-0800 |
| 6 | 3 | 2 | hello | January, 23 2012 01:12:12-0800 |
For this input:
create table msgsList
(
msg_id int,
msg_from int,
msg_to int,
msg varchar(10),
msg_time datetime
);
insert into msgslist VALUES
(1, 1, 2, 'hello', '2010-01-23 17:00:00'), -- shown
(2, 2, 1, 'world', '2010-01-23 16:00:00'),
(3, 3, 1, 'i am alive', '2011-01-23 00:00:00'),
(4, 3, 1, 'really', '2011-01-22 23:15:00'),
(5, 1, 3, 'me too', '2012-01-23 00:15:00'), -- shown
(6, 3, 2, 'hello', '2012-01-23 01:12:12'); -- shown
If ANSI SQL is your cup of tea, here's the way to do it: http://sqlfiddle.com/#!2/0a575/19
select *
from msgsList z
where exists
(
select null
from msgsList
where
least(z.msg_from, z.msg_to) = least(msg_from, msg_to)
and greatest(z.msg_from, z.msg_to) = greatest(msg_from, msg_to)
group by least(msg_from, msg_to), greatest(msg_from, msg_to)
having max(msg_time) = z.msg_time
) ;
Upvotes: 4
Reputation: 39393
Could it be this simple? http://www.sqlfiddle.com/#!2/50f9f/1
set @User1 := 'John';
set @User2 := 'Paul';
select *
from
(
select *
from messages
where msg_from = @User1 and msg_to = @User2
order by msg_time desc
limit 1
) as x
union
select *
from
(
select *
from messages
where msg_from = @User2 and msg_to = @User1
order by msg_time desc
limit 1
) as x
order by msg_time desc
Output:
| MSG_ID | MSG_FROM | MSG_TO | MSG | MSG_TIME |
----------------------------------------------------------------------------
| 2 | Paul | John | Hey Johnny! | August, 20 2012 00:00:00-0700 |
| 1 | John | Paul | Hey Paulie! | August, 19 2012 00:00:00-0700 |
Could be a lot simpler if only MySQL supported windowing function: http://www.sqlfiddle.com/#!1/e4781/8
with recent_message as
(
select *, rank() over(partition by msg_from, msg_to order by msg_time desc) as r
from messages
)
select *
from recent_message
where r = 1
and
(
(msg_from = 'John' and msg_to = 'Paul')
or
(msg_from = 'Paul' and msg_to = 'John')
)
order by msg_time desc;
Upvotes: 2
Reputation: 1574
After giving it some thought, I came up with this:
SELECT min_user AS min(msg_from, msg_to), max_user AS max(msg_from, msg_to),
max(msg_date) FROM msg GROUP BY min_user, max_user
I'm still not quite sure how to get the additional data from the message, but I'll give it some thought.
Upvotes: 0