Get users who replied the first message of the conversation within 24hours - mysql

I need to find the users who replied the first message of the conversation (one to one conversation) within 24hours. I have a messages table where all data are stored.
Table: messages
id | sender_id | recipient_id | content | Created_at
1 | 1001 | 256 | Hi | 2017-03-20 22:37:30
2 | 256 | 1001 | Hello | 2017-03-21 20:29:10
3 | 1001 | 256 | XYZ | 2017-03-21 22:02:00
4 | 256 | 1001 | ??? | 2017-03-21 23:01:01
5 | 1002 | 500 | Hi there | 2017-03-22 10:10:10
6 | 1002 | 500 | Can you meet?| 2017-03-22 10:15:32
7 | 500 | 1002 | Yes | 2017-03-22 10:20:30
8 | 1003 | 600 | Hello world | 2017-03-23 01:00:00
9 | 1004 | 700 | Hi | 2017-03-23 08:10:10
10 | 700 | 1004 | hello | 2017-03-26 22:00:00
Expected result:
users
256
500
Example: Conversation between user 1001 and 256.
id | sender_id | recipient_id | content | Created_at
1 | 1001 | 256 | Hi | 2017-03-20 22:37:30
2 | 256 | 1001 | Hello | 2017-03-21 20:29:10
3 | 1001 | 256 | XYZ | 2017-03-21 22:02:00
4 | 256 | 1001 | ??? | 2017-03-21 23:01:01
Here 2 | 256 | 1001 | Hello | 2017-03-21 20:29:10 is the first replied message of the conversation and its replied within 24 hours.

I've tested this out and it works. It's much the same as the other answers though.
select messages.sender_id as users from (
select t.id1, t.id2, t.start, messages.sender_id as initiator,
messages.recipient_id as replier from (
select greatest(sender_id, recipient_id) as id1,
least(sender_id, recipient_id) as id2, min(Created_at) as start
from messages group by id1, id2
) as t left join messages on messages.Created_at = t.start
and ((messages.sender_id = t.id1 and messages.receiver_id = t.id2)
or (messages.sender_id = t.id2 and messages.receiver_id = t.id1))
) as t inner join messages on messages.sender_id = t.replier
and messages.recipient_id = t.initiator
and messages.Created_at < date_add(t.start, interval 1 day)
group by users;
The innermost query finds conversations by grouping messages by the two users involved, and finds the start of that conversation by taking the minimum Created_at.
The middle query finds the initiator and replier by looking up the first message in the conversation.
The outside query finds messages from the replier to the initiator (which are therefore in that conversation) within one day of the start of it, and groups by users so that they each appear only once (even if involved in multiple conversations).

Alright.
First, we need to define what a conversation is: a pair of (sender_id, recipient_id) exchanging messages. Determining the first message in a conversation is a bit tricky. We could do this:
SELECT sender_id, recipient_id, min(created_at) FROM messages
GROUP BY sender_id, recipient_id
However, this will give us the first two messages of each conversation instead. We still don't know who started it and who replied without looking at the date, but the data we get is all we need to answer the question. And it is likely to be fast, since I will assume an index on (sender_id, recipient_id, created_at).
Now, I see two ways to solve this. First one:
SELECT least(sender_id,recipient_id),
greatest(sender_id,recipient_id),
max(created_at) <= DATE_ADD( min(created_at), INTERVAL 1 DAY )
FROM (
SELECT sender_id, recipient_id, min(created_at) FROM messages
GROUP BY sender_id, recipient_id
) foo
GROUP BY least(sender_id,recipient_id),
greatest(sender_id,recipient_id)
HAVING count(*)=2;
least() and greatest() allow to create one id for each conversation from the sender and receiver ids. max() and min() will return the first message and its reply, since we have only 2 rows per conversation. And the having will remove messages without reply.
We could also use a temp table:
CREATE TEMPORARY TABLE foo (
sender_id INT NOT NULL,
recipient_id INT NOT NULL,
createdèat DATETIME NOT NULL
);
INSERT INTO foo
SELECT sender_id, recipient_id, min(created_at) FROM messages
GROUP BY sender_id, recipient_id
ALTER TABLE foo ADD PRIMARY KEY (sender_id,recipient_id);
SELECT ... substract a.created_at and b.created_at to get your 24h limit
FROM foo a
JOIN foo b ON ( a.sender_id=b.recipient_id
AND a.recipient_id=b.sender_id
AND a.created_at < b.created_at)
By joining the temp table to itself, we put together the first message and its reply in a single query, and we can compare their dates.

Taking a swing without testing, as I think the desired result is still unclear.
First, find the "first messages" of a conversation:
select m1.id
,m.sender_id
,m.recipient_id
,m.Created_at
from messages m1
inner join (
select m.sender_id
,m.recipient_id
,Min(m.Created_at) as first_message
from messages m
group by m.sender_id
,m.recipient_id
) m2
on m1.sender_id = m2.sender_id
and m1.m.recipient_id = m2m.recipient_id
and m1.Created_at = m2.first_message
If these are properly "first messages", then find any replies in 24 hours
select distinct m3.sender_id
from messages m3
inner join (
<the above first message select statement>
) fm
on m3.sender_id = fm.recipient_id
and m3.recipient_id = fm.sender_id
and m3.Created_at < DATEADD (HH , 24 , fm.Created_at)
where m3.Created_at > fm.Created_at

this return the last message in 24 hours between to users
select
cnv.id ,
cnv.sender_id,
cnv.recipient_id,
cnv.content,
cnv.Created_at
from
(
-- first create a table with costum id of conversaton
select
-- ex: 1001-256
concat(greatest(sender_id, recipient_id),'-',least(sender_id, recipient_id) ) as 'cnv_id', -- costum column for joining
id ,
sender_id,
recipient_id,
content,
Created_at
from message
) cnv
INNER JOIN
(
-- second extract the last date of every one to one coversation conversation
-- result ex : 1001-256 | 2017-03-21 23:01:01
SELECT
concat(greatest(sender_id, recipient_id),'-',least(sender_id, recipient_id) ) as 'cnv_id', -- costum column for joining
max(Created_at) 'max_date'
group by cnv_id
) max_cnv ON cnv.cnv_id = max_cnv.cnv_id -- join the two result by the custom cnv_id
WHERE
-- here we extract only the record that there Created_at is > 24 hours from the max date
-- you can make this condition as you want but i think this will work
(max_cnv.max_date - cnv.Created_at)/1000/60 >= 24;

Related

Querying conversations from messages table

I have the messages table formatted as follows:
+------------------------------------------------------------------+
| id|sender_id| recipient_id | message_text |created_at |
+------------------------------------------------------------------+
| 1 | 2 | 10 | "test1" |"2017-04-10 09:05:45" |
| 2 | 10 | 2 | "test2" |"2017-04-10 09:05:47" |
| 3 | 2 | 4 | "test3" |"2017-04-10 09:05:49" |
| 4 | 10 | 4 | "test4" |"2017-04-10 09:05:51" |
| 5 | 4 | 2 | "test5" |"2017-04-10 09:05:53" |
| 6 | 2 | 10 | "test6" |"2017-04-10 09:05:58" |
+------------------------------------------------------------------+
What I'm trying to do is get all the "conversations" of a logged in user (say user with id 2), along with the last messages for that conversation.
I managed to pull out the id's of users user2 has messages with using this query:
select distinct users.id
from messages, users where
(recipient_id = 2 and users.id = sender_id)
or
(sender_id = 2 and users.id = recipient_id);
What this yields is
4
10
as user2 has either sent and/or received messages from these two people (test1, test2, test6 for 10, and test3, test5 for 4).
What I can't do is modify this query so it also yields the last message sent to or received by the yielded id - for example
4 | test5
10 | test6
If I understand your requirement correctly, you want to obtain the most recent message date for every conversation involving a certain user. In this case, we can aggregate over conversations for a given user and retain the most recent message date.
SELECT m1.*
FROM messages m1
INNER JOIN
(
SELECT LEAST(sender_id, recipient_id) AS first,
GREATEST(sender_id, recipient_id) AS second,
MAX(created_at) AS recent_date
FROM messages
WHERE sender_id = 2 OR recipient_id = 2
GROUP BY LEAST(sender_id, recipient_id),
GREATEST(sender_id, recipient_id)
) m2
ON LEAST(m1.sender_id, m1.recipient_id) = m2.first AND
GREATEST(m1.sender_id, m1.recipient_id) = m2.second AND
m1.created_at = m2.recent_date
Output:
Explanation:
The challenge in this query is to find a way to group conversations between two users together. I used the LEAST/GREATEST trick, which is a way that we can treat a 2 -> 4 and 4 -> 2 conversation as being logically the same thing. Then, using GROUP BY, we can identify the most recent conversation date for that pair of conversing users. So the subquery in my answer above finds, for each pair of users, without regard of any order, that pair along with its most recent conversation date. We then join this result back to the messages table to bring in the actual latest message text.
Demo here:
Rextester
Use an order_by created_at desc statement at the end of your query to get the most recent messages.

MySQL grouping by 2 columns

I'm trying to return back all the messages for a user_id ( 1 ) in the table sorted by created_at desc, but have it grouped by sender_id or recipient_id depending on whichever created_at is newer.
messages
sender_id | recipient_id | text | created_at
1 | 2 | hey | 2017-03-26 04:00:00
1 | 2 | tees | 2017-03-26 00:00:00
2 | 1 | rrr | 2017-03-27 00:00:00
3 | 1 | edd | 2017-03-27 00:00:00
1 | 3 | cc3 | 2017-02-27 00:00:00
Ideally it would return
2 | 1 | rrr | 2017-03-27 00:00:00
1 | 3 | cc3 | 2017-02-27 00:00:00
The query I have so far is -
select *
from messages
where (
sender_id = 1
or recipient_id = 1
)
group by least(sender_id, recipient_id)
order by created_at desc
but it seems it is doing the order by before the group by.
Any help would be appreciative.
GROUP BY is intended for aggregation (sum, count, etc...), the fact that it orders is little more than an official side effect (that is being deprecated, and not guaranteed behavior in future versions). ORDER BY is done after GROUP BY, it sorts the final results, and can take multiple expressions. Your interchangeable use of the terms makes it difficult to understand exactly what you are looking for, but going by sample desired results this is probably it:
ORDER BY LEAST(sender_id, recipient_id), created_at DESC
However, since all rows in your sample result have the same "LEAST" value, it could be this:
ORDER BY LEAST(sender_id, recipient_id), created_at DESC
If you are attempting to get the most recent post by user 1 where they are a sender and the most recent post by user 1 where they are a recipient.
I would do a union with the first query getting the most recent post as a sender and the second getting the most recent post as a recipient.
select *
from messages
join
(
select
sender_id,
max(created_at) as max1
from messages
where
sender_id = 1
group by
sender_id
) t1
on messages.created_at = t1.max1
union
select *
from messages
join
(
select
recipient_id,
max(created_at) as max2
from messages
where
recipient_id = 1
group by
recipient_id
) t2
on messages.created_at = t2.max2

SQL get last messages from/to certain user

I'm trying to make SQL query from table,
- get last messages from all pairs of users with user 36 as sender or recipient, and join them with users table to get names. I've managed to create something like this, but still want to ask if there is more simple | efficient solution.
Mysql version - 5.5.31
table: messages
fields:
sender_user_id, recipient_user_id, date, text
query:
SELECT
*
FROM
(SELECT
(CASE sender_user_id > recipient_user_id
WHEN true THEN CONCAT(recipient_user_id, '|', sender_user_id)
WHEN false THEN CONCAT(sender_user_id, '|', recipient_user_id)
END) as hash,
sender_user_id,
recipient_user_id,
date,
text,
u.first_name
FROM
fed_messages
LEFT JOIN fed_users as u ON ((fed_messages.sender_user_id = 36 AND fed_messages.recipient_user_id = u.id) OR (fed_messages.recipient_user_id = 36 AND fed_messages.sender_user_id = u.id))
WHERE
sender_user_id = 36 OR recipient_user_id = 36
ORDER BY date DESC) as main
GROUP BY hash;
Thanks.
Upd. Data from sample messages table:
mysql> SELECT id, sender_user_id, recipient_user_id, text, date FROM federation.fed_messages WHERE id > 257;
-----+----------------+-------------------+-----------------+---------------------+
| id | sender_user_id | recipient_user_id | text | date |
+-----+----------------+-------------------+-----------------+---------------------+
| 258 | 40 | 36 | and one more | 2013-06-06 10:57:17 |
| 259 | 36 | 38 | another message | 2013-06-06 11:03:49 |
| 260 | 38 | 36 | some mes | 2013-06-06 12:29:33 |
| 261 | 38 | 36 | message | 2013-06-06 12:29:53 |
| 262 | 36 | 38 | message | 2013-06-06 12:47:26 |
| 263 | 36 | 40 | some message | 2013-06-10 16:22:46 |
The result should be with ids - 262, 263
I'm using SQL Server 2008, you don't say which database you are using.
From the information you have supplied your query seems overly complex for the output you require. Here's a simple query to get all the messages involving user 36:
SELECT
sender.msg_user_name AS sender_user_name
,recipient.msg_user_name AS recipient_user_name
,msg_date
,msg_text
FROM
dbo.Fed_Messages
INNER JOIN dbo.Fed_User AS sender
ON sender.msg_user_id = sender_user_id
INNER JOIN dbo.Fed_User AS recipient
ON recipient.msg_user_id = recipient_user_id
WHERE
sender_user_id = 36
OR recipient_user_id = 36
ORDER BY
msg_date DESC
I've had to change some field names as in SQL Server some of the names you have chosen are reserved words.
SQL Fiddle: http://sqlfiddle.com/#!3/b8e88/1
EDIT:
Now you've added some more information, and shown there is an id field on the message table, you could use something like this (note: I have SQL Server so you will probably have to change the query for MySQL):
SELECT sender.msg_user_name AS sender_user_name
,recipient.msg_user_name AS recipient_user_name
,msg_date
,msg_text
FROM dbo.Fed_Messages
INNER JOIN dbo.Fed_User AS sender ON sender.msg_user_id = sender_user_id
INNER JOIN dbo.Fed_User AS recipient ON recipient.msg_user_id = recipient_user_id
INNER JOIN ( SELECT MAX(id) AS most_recent_message_id
FROM dbo.Fed_Messages
GROUP BY CASE WHEN sender_user_id > recipient_user_id
THEN recipient_user_id
ELSE sender_user_id
END -- low_id
,CASE WHEN sender_user_id < recipient_user_id
THEN recipient_user_id
ELSE sender_user_id
END -- high_id
) T ON T.most_recent_message_id = dbo.Fed_Messages.id
WHERE sender_user_id = 36
OR recipient_user_id = 36
ORDER BY msg_date DESC
The SELECT in the FROM part of the query finds the most recent message (based on the id, I'm assuming it's an auto incrementing number) for each ordered pair of sender/recipient user id's. The result of that is rejoined to the Fed_Messages table to ensure we get the names for sender/receiver correct.
Updated SQL Fiddle: http://sqlfiddle.com/#!3/1f07a/2

How to make a query to GROUP BY x DESC

The following SELECT statement
select *
from messages
where receiverID = '5'
group BY senderID
order by id DESC
database:
id | senderID | receiverID | message
1 | 245 | 5 | test 1
2 | 89 | 5 | test 2
3 | 79 | 5 | test 3
4 | 245 | 5 | test 4
5 | 245 | 5 | test 5
For senderID=245 I expected to return the row with id=5 , but it dosent it returns row with id=1, but i want the last row. How to achieve that ?
returns:
id | senderID | receiverID | message
1 | 245 | 5 | test 1
2 | 89 | 5 | test 2
3 | 79 | 5 | test 3
Ohh I made it :D
so this is the code that worked,for anyone with similar question
SELECT * FROM ( SELECT * FROM messages WHERE
receiverID = '5' ORDER BY id DESC) AS m GROUP BY senderID ORDER BY id DESC
This is not possible. You have to do something like:
[...] WHERE `id` = (SELECT MAX(`id`) FROM `messages` WHERE `receiverID` = '5')
Personally I'd consider a subquery, something along the lines of this should do the job for you
SELECT messagesOrdered.*
FROM (
SELECT *
FROM messages
WHERE receiverID = '5'
ORDER BY id DESC
) AS messagesOrdered
GROUP BY senderID
You may wish to check what keys you have set up depending on how large the table is.
The problem with using MAX is that if you use MAX on the id field then it will get the number you are looking for, however using MAX on another field does not get the data that matches that id. Using the subquery method, the inner query is doing the sorting and then the GROUP on the outside will group based on the order of rows in the inner query.
SELECT * FROM messages m
JOIN
( SELECT senderID, MAX(id) AS last
FROM messages
WHERE receiverID = '5'
GROUP BY senderID ) mg
ON m.id = mg.last
Not sure I understand your question completely, but it sounds to me like you want:
select max(id),
senderId,
max(receiverId),
max(message)
from messages
where receiverID = '5'
group BY senderID
order by id DESC
Note that you need to include message into your aggregate as well, otherwise you'll get unpredicatable results (other DBMS wouldn't allow leaving out the max(message) but MySQL will simply return a random row from the group).
Here it goes mine :)
select m1.* from messages m1
left join messages m2
on m1.senderid = m2.senderid and m1.id < m2.id
where m2.id is null and receiverID = '5'
Given your example this would return:
+----+----------+------------+---------+
| ID | SENDERID | RECEIVERID | MESSAGE |
+----+----------+------------+---------+
| 2 | 89 | 5 | test 2 |
| 3 | 79 | 5 | test 3 |
| 5 | 245 | 5 | test 5 |
+----+----------+------------+---------+

Dependent MySQL SELECT

My problem is the following:
My tables are MESSAGE and MESSAGE_COMMENT,
MESSAGE (id,content)
MESSAGE_COMMENT (id, message_id, content)
I need to select all messages and max 3 comments for each message, like in this example:
type | id | content
M 15 "this is a message with no comments"
M 16 "this is another message with many comments"
R 16 "comment1"
R 16 "comment2"
R 16 "comment3"
M 17 "this is another message with no comments"
"id" is MESSAGE.id when it's a message and COMMENT.message_id when it's a comment.
I hope I have clearly explained my problem..
SELECT *
FROM (
SELECT m.id,
COALESCE(
(
SELECT id
FROM message_comment mc
WHERE mc.message_id = m.id
ORDER BY
mc.message_id DESC, id DESC
LIMIT 2, 1
), 0) AS mid
FROM message m
) mo
LEFT JOIN
message_comment mcd
ON mcd.message_id >= mo.id
AND mcd.message_id <= mo.id
AND mcd.id >= mid
Create an index on message_comment (message_id, id) for this to work fast.
See this article in my blog for more detailed explanation of how this works:
Advanced row sampling
That's all because PHP is parsed in your server (side) and the HTML generated by it go to client browser and get renderized...
I'm not a fan of unions, but sometimes they have their places... :)
SELECT type, id, content FROM (
SELECT 'M' AS type, id, 0 AS reply_id, content FROM MESSAGE
UNION
SELECT 'R' AS type, message_id AS id, id AS reply_id, content FROM MESSAGE_COMMENT) a
ORDER BY id, reply_id
returns
+------+----+--------------------------------------------+
| type | id | content |
+------+----+--------------------------------------------+
| M | 15 | this is a message with no comments |
| M | 16 | this is another message with many comments |
| R | 16 | comment1 |
| R | 16 | comment2 |
| R | 16 | comment3 |
| M | 17 | this is another message with no comments |
+------+----+--------------------------------------------+
NB: The second SELECT in the UNION could easily be reworked with an INNER JOIN to the MESSAGE table if orphaned message_comments were a concern.