Selecting first row that matches a criteria in Groupby - mysql

Given the following messages table where channel is a particular chat session, and user1 and user2 are the users in the chat:
+---------+-------+-------+------------+
| channel | user1 | user2 | message |
+---------+-------+-------+------------+
| 5 | 15 | 8 | Hello |
| 5 | 15 | 8 | I'm John |
| 5 | 8 | 15 | Hi John |
| 6 | 9 | 15 | yo |
| 6 | 15 | 9 | heyo |
| 6 | 9 | 15 | you here? |
| 8 | 15 | 10 | Hi |
| 8 | 15 | 10 | you there? |
+---------+-------+-------+------------+
I'd like to group by the channel and select the first response row (the first row where the second person said something). If the second person never responded as in channel 8, then they don't need to show up in the output.
So my expected output would be this:
+---------+-------+-------+---------+
| channel | user1 | user2 | message |
+---------+-------+-------+---------+
| 5 | 8 | 15 | Hi John |
| 6 | 15 | 9 | heyo |
+---------+-------+-------+---------+
Note that there is a timestamp column, just forgot to include it. Any help would be appreciated, been searching all over for a solution an have yet to come up with any. Thanks.

Assuming that you have a timestamp column, you can get the user2 for the first message as:
select m.*
from messages m
where not exists (select 1
from messages m2
where m2.channel = m.channel and
m2.timestamp < m.timestamp
);
So, if you want the first message from this, you can use the group_concat()/substring_index()` trick:
select m.channel, m.user1, m.user2,
substring_index(group_concat(m2.messages order by m2.timestemp separator '|'), '|', 1)
from messages m join
(select m.*
from messages m
where not exists (select 1
from messages m2
where m2.channel = m.channel and
m2.timestamp < m.timestamp
)
) mfirst
on m.channel = mfirst.channel and
m.user1 = mfirst.user2
group by m.channel, m.user1, m.user2;

Not entirely convinced myself. Feel free to improve.
The limit 1 relies on the table being read top down every time.
And I suspect all the different selects can be done more elegantly.
But at least it gives the required result for the sample data :)
SELECT channelchat.channel,
(SELECT user2
FROM chat firstline
WHERE firstline.channel = channelchat.channel
LIMIT 1) seconduser,
(SELECT user1
FROM chat firstline
WHERE firstline.channel = channelchat.channel
LIMIT 1) firstuser,
(SELECT message
FROM chat secondline
WHERE secondline.channel = channelchat.channel
AND secondline.user1 = seconduser
LIMIT 1) response
FROM chat channelchat
GROUP BY channelchat.channel
HAVING response IS NOT NULL
sqlfiddle

Related

Get only last messages from database mysql

I have a table like the next one:
|--------|-----------|-------------|-------------|----------------|
| id | userToId | userFromId | message | date |
|--------|-----------|-------------|-------------|----------------|
| 1 | 1 | 2 | Hi |06/30/2018/18:00|
|--------|-----------|-------------|-------------|----------------|
| 2 | 1 | 2 | how r u |06/30/2018/18:01|
|--------|-----------|-------------|-------------|----------------|
| 3 | 3 | 5 | Hi |06/30/2018/17:00|
|--------|-----------|-------------|-------------|----------------|
| 4 | 1 | 5 | Hi |06/30/2018/19:00|
|--------|-----------|-------------|-------------|----------------|
| 5 | 7 | 1 | Hi |06/30/2018/19:00|
|--------|-----------|-------------|-------------|----------------|
And, I want to get only the last messages (send or received) related to a particular user provided his user ID. So, for example, if the user ID is 1 then the output should look like this:
|--------|-----------|-------------|-------------|----------------|
| id | userToId | userFromId | message | date |
|--------|-----------|-------------|-------------|----------------|
| 2 | 1 | 2 | how r u |06/30/2018/18:01|
|--------|-----------|-------------|-------------|----------------|
| 4 | 1 | 5 | Hi |06/30/2018/19:00|
|--------|-----------|-------------|-------------|----------------|
| 5 | 7 | 1 | Hi |06/30/2018/19:00|
|--------|-----------|-------------|-------------|----------------|
This output means (for user with id = 1) that the latest messages received from others users were:
"how r u" -> from user with id = 2
"Hi" -> from user with id = 5
And the latest messages send to other users were:
"Hi" -> to user with id = 7
https://www.db-fiddle.com/f/wLEHJ1EgNT9zjecYZEnCcb/0
SELECT t.*
FROM my_table t
LEFT JOIN my_table temp
ON t.userToId = temp.userToId
AND t.userFromId = temp.userFromId
AND t.date < temp.date
WHERE (t.userToId = 1
OR t.userFromId = 1)
AND temp.id IS NULL
First, I suggest to use normalized date formats (read Date Types) on the database and convert it on the query if you want to show in another format. Now, for your particular case and the datetime format you use on the table, I will go with the next query:
SELECT
m.*
FROM
msgs AS m
INNER JOIN
( SELECT
userToId,
userFromId,
MAX(STR_TO_DATE(`date`, '%m/%d/%Y/%H:%i')) AS maxDate
FROM
msgs
GROUP BY
userToId, userFromId
) AS l ON l.userToId = m.userToId
AND l.userFromId = m.userFromId
AND l.maxDate = STR_TO_DATE(m.`date`, '%m/%d/%Y/%H:%i')
WHERE
m.userToId = <your_user_id> OR m.userFromId = <your_user_id>;
You can check online example with some data here:
DB-Fiddle

How can I count unanswered messages in the last year?

I'm making a messaging system. Messages are two different kinds.
The first messages have a title and NULL for related column.
The second messages are related to one of the first messages which don't have title and they have the id of parent message for related column. (which are known as response/reply/answer)
Here is my table structure:
// messages
+----+----------+------------------+-----------+-------------+-------------+---------+
| id | title | content | sender_id | receiver_id | date_time | related |
+----+----------+------------------+-----------+-------------+-------------+---------+
| 1 | titel1 | whatever1 | 1 | 3 | 1521097240 | NULL |
| 2 | | whatever2 | 3 | 1 | 1521097241 | 1 |
| 3 | | whatever3 | 1 | 3 | 1521097242 | 1 |
| 4 | title2 | whatever4 | 1 | 4 | 1521097243 | NULL |
| 5 | title3 | whatever5 | 1 | 5 | 1521097244 | NULL |
| 6 | | whatever7 | 4 | 1 | 1521097246 | 4 |
| 7 | title4 | whatever8 | 1 | 4 | 1521097246 | NULL |
+----+----------+------------------+-----------+-------------+-------------+---------+
/*
related column: it is NULL for the first message and the id of the parent for othesrs.
Now I need to count the number of messages that user A sent and hasn't receive a response for that in the last year.
For example, the number of messages user user_id = 1 has sent which hasn't gotten a response for is 1. Because he has sent a message to user user_id = 5 and he hasn't responded yet.
How can I count that number?
SELECT count(1)
FROM messages
WHERE sender_id = 1
AND date_time > UNIX_TIMESTAMP(DATE_SUB(now(), INTERVAL 1 YEAR))
My query counts all sent messages. How can I count only the unanswered ones?
Let me assume that you really mean first messages sent by "A". If so, your sample query needs to filter on related is NULL. To filter on the non-responses, you can use LEFT JOIN/WHERE or NOT EXISTS:
SELECT count(*)
FROM messages m LEFT JOIN
messages m2
ON m2.related = m.id
WHERE m.sender_id = 1 AND
m.related IS NULL AND
m.date_time > UNIX_TIMESTAMP(DATE_SUB(now(), INTERVAL 1 YEAR)) AND
m2.id IS NULL; -- response does not exist
Use NOT EXISTS
SELECT count(1)
FROM messages m1
WHERE sender_id = 1 AND
related IS NULL AND
date_time > UNIX_TIMESTAMP(DATE_SUB(now(), INTERVAL 1 YEAR)) AND
NOT EXISTS (
SELECT 1
FROM messages m2
WHERE m1.id = m2.related
)

How to get last record for group?

I have a table called tbl_chat and tbl_post.
The tbl_chat is given as follows.
|--------------------------------------------------------------------------------|
| chat_id | message | from_user | to_user | post_id |send_date |
|--------------------------------------------------------------------------------|
| 1 | Hi | 23 | A | 35 | 2016-04-01 17:35|
| 2 | Test | 24 | A | 35 | 2016-04-02 01:35|
| 3 | Thut | A | 23 | 35 | 2016-04-02 03:35|
| 4 | test | A | 24 | 35 | 2016-04-02 12:35|
| 5 | Hi | 23 | A | 35 | 2016-04-03 17:35|
|--------------------------------------------------------------------------------|
Now, in the chat table we can see three users are interacting with each other. The Admin (A), user with id = 23 and user = 24.
So there is basically two chat thread.
One between A and 23
Another between A and 24.
I want a query which will show the two chat threads, with the last chat message. Just like in case of facebook chat list showing all the chat-threads with mentioning the last chat.
I am writing a query like this.
SELECT * FROM tbl_chat, tbl_post
WHERE tbl_post.post_id = tbl_chat.post_id
AND tbl_post.post_id = '39'
GROUP BY tbl_chat.chat_from
ORDER BY date DESC
The query has a problem. It is first retrieving all the chats and grouping it w.r.t. chat_from and then ORDERING it Descending-wise.
So first it's creating the group, and then ordering the group.
Also, the first query produces three group, taking the reply message from Admin as a separate group. Since GROUP BY chat_from.
How can I solve this issue?
EDIT:-
I would be grateful if someone can build the query in Active-Records of Codeigniter.
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(chat_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,message VARCHAR(20) NOT NULL
,from_user VARCHAR(12)
,to_user VARCHAR(12)
,post_id INT NOT NULL
,send_date DATETIME NOT NULL
);
INSERT INTO my_table VALUES
(1,'Hi' ,'23','A' ,35,'2016-04-01 17:35:00'),
(2,'Test','24','A' ,35,'2016-04-02 01:35:00'),
(3,'Thut','A' ,'23',35,'2016-04-02 03:35:00'),
(4,'test','A' ,'24',35,'2016-04-02 12:35:00'),
(5,'Hi' ,'23','A' ,35,'2016-04-03 17:35:00');
SELECT a.*
FROM my_table a
JOIN
( SELECT LEAST(from_user,to_user) user1
, GREATEST(from_user,to_user) user2
, MAX(send_date) send_date
FROM my_table
GROUP
BY user1
, user2
) b
ON b.user1 = LEAST(a.from_user,a.to_user)
AND b.user2 = GREATEST(a.from_user,a.to_user)
AND b.send_date = a.send_date;
+---------+---------+-----------+---------+---------+---------------------+
| chat_id | message | from_user | to_user | post_id | send_date |
+---------+---------+-----------+---------+---------+---------------------+
| 4 | test | A | 24 | 35 | 2016-04-02 12:35:00 |
| 5 | Hi | 23 | A | 35 | 2016-04-03 17:35:00 |
+---------+---------+-----------+---------+---------+---------------------+
You can use NOT EXISTS() :
SELECT * FROM tbl_chat
INNER JOIN tbl_post
ON tbl_post.post_id = tbl_chat.post_id
WHERE NOT EXISTS(SELECT 1 FROM tbl_chat s
WHERE tbl_chat.from_user IN(s.from_user,s.to_user)
AND tbl_chat.to_user IN(s.from_user,s.to_user)
AND tbl_chat.date < s.date)
Although date field looks like DATE type, which is curious - how would you find the difference between two messages at the same day?

MySQL Select use IN and GROUP BY

I have two tables messages and users I want to find out which users received the messages however the query is only returning one message.
My Schemas are as follow
Messages
msg_id | msg_content | recipients |
-----------------------------------
1 | Hello world | 1,2,3,4,5
2 | Test | 1,3,5
3 | Welcome | 1,2,4
Users
uid | fname | lname |
---------------------------
1 | John |Doe |
2 | Jane |Doe |
3 | Mark |Someone |
4 | Mary |lady |
5 | Anthony |Doe |
So I would love to see my results simply as
msg_id | msg_content | recipients |
-----------------------------------
1 | Hello world | John,Jane,Mark,Mary,Anthony
2 | Test | John,Mark,Anthony
3 | Welcome | John,Jane,Mary
So I am doing my query as so
SELECT msg_id,msg_content,fname AS recepients FROM messages a
LEFT JOIN users ON uid IN(a.recipients)
When I run that query I only get one recipient. Please advice. Thanks.
I think you have to use a alternative way for create tables
Messages
msg_id | msg_content |
----------------------
1 | Hello world |
2 | Test |
3 | Welcome |
Users
uid | fname | lname |
---------------------------
1 | John |Doe |
2 | Jane |Doe |
3 | Mark |Someone |
4 | Mary |lady |
5 | Anthony |Doe |
users_has_messages
uhm_id | uid | msg_id |
---------------------------
1 | 1 | 1 |
2 | 2 | 1 |
3 | 3 | 1 |
4 | 2 | 2 |
5 | 1 | 3 |
Then you can use your code
Okay, so this schema isn't the best (using comma separated lists of IDs is not a great idea, and the performance of any joins will get pretty bad pretty quick). Best bet is to have a third table mapping uid's to msg_id's as mentioned by #Thilina.
That said, this query will do probably what you're after:
SELECT msg_id,msg_content,GROUP_CONCAT(fname) AS recepients FROM messages a
LEFT JOIN users ON FIND_IN_SET(uid, a.recipients)
GROUP BY msg_id
I tried this in Oracle 12c and it is working fine.
So basically what I did is
- Separate the userid from recipient field and used this a columns.
- Join with USERS table to get user fnames
- Used LISTAGG function to aggregate it back.
For MySql we need to find the corresponding functions to Separate the IDs between commas, Convert it to rows and Aggregate. But the inherent logic would be same.
with users (user_id,fname) as (
select 1 ,'John' from dual union
select 2 ,'Jane' from dual union
select 3 ,'Mark' from dual union
select 4 ,'Mary' from dual union
select 5 ,'Anthony' from dual
),
messages(msg_id, msg_content,recipients) as(
select 1,'Hello world','1,2,3,4,5' from dual union
select 2 , 'Test' ,'1,3,5' from dual union
select 3,' Welcome','1,2,4' from dual
),
flat as(
select msg_id,msg_content,
REGEXP_SUBSTR (recipients, '[^,]+', 1, COLUMN_VALUE) as user_id
from messages,
TABLE(
CAST(
MULTISET(
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT(recipients ,',' ) + 1
) AS SYS.ODCINUMBERLIST
)
)
),
unames as
( select f.msg_id,f.msg_content,u.fname from flat f inner join users u
on f.user_id = u.user_id
order by f.msg_id
)
SELECT msg_id,msg_content,LISTAGG(fname, ',') WITHIN GROUP (ORDER BY fname) as recipients
from unames
group by msg_id,msg_content

Mysql Queue Start/End time

I have one problem that I can't resolve.
I have 2 tables.
Table 1:
ID | Time
1 | 08:12:54
2 | 08:15:40
3 | 09:30:01
4 | 10:15:15
5 | 10:56:12
6 | 11:00:03
Table 2:
ID | Name| Previous | Current
1 | Queue | null | 11
2 | Queue | 11 | 19
3 | Queue | 19 | 11
3 | List | null | 11
4 | Queue | 11 | 16
4 | List | null | 11
5 | Queue | null | 15
6 | Queue | 15 | 19
The result wanted:
NumberQueue | Start | End
11 | 08:12:54 | 08:15:40
19 | 08:15:40 | 09:30:01
11 | 09:30:01 | 10:15:15
15 | 10:56:12 | 11:00:03
...
...
The previous and the current fields, have the number of the Queue and I want to know for each Queue, the start date and the end date, knowing that the previous has the previous Queue, and the current has the new Queue.
I want one query that can present this result. Help me. :(
Regards.
SELECT t1outer.ID, t1outer.Time AS start, (
SELECT Time FROM Table1 AS t1inner
WHERE t1inner.ID > t1outer.ID
ORDER BY ID ASC LIMIT 1
) AS end, Table2.Previous, Table2.Current
FROM Table1 AS t1outer
LEFT JOIN Table2 USING (ID);
This select statement should provide the information you need:
SELECT Current AS Number, t1out.Time AS Start, (
SELECT Time FROM Table1 AS t1in
WHERE t1in.ID > t1out.ID
ORDER BY ID ASC LIMIT 1
) AS End FROM Table2
LEFT JOIN Table1 AS t1out USING (ID)
WHERE Table2.Name = 'Queue';