Select last message from all conversations (MySQL) - mysql

I need to select all the last messages for each conversation for user with given id.
In case of last message was send to given id, it have to be last message from sender.
Here is the test case without creationDate using messageID:
+-----------+------------+----------+------+
| messageID | fromUserID | toUserID | text |
+-----------+------------+----------+------+
| 1 | 1 | 2 | 'aa' |
| 2 | 1 | 3 | 'ab' |
| 3 | 2 | 1 | 'ac' |
| 4 | 2 | 1 | 'ad' |
| 5 | 3 | 2 | 'ae' |
+-----------+------------+----------+------+
The result for userID=1 have to be messages with text 'ab' and 'ad'.
For now I have this query with all of the last messages of every user to each other, but does not remove, according to my test case, message with id=1 (have to be only with id=2 and id=4).
SELECT
UM.messageID,
UM.fromUserID, UM.toUserID,
UM.text, UM.flags, UM.creationDate
FROM UserMessage AS UM
INNER JOIN
(
SELECT
MAX(messageID) AS maxMessageID
FROM UserMessage
GROUP BY fromUserID, toUserID
) IUM
ON UM.messageID = IUM.maxMessageID
WHERE UM.fromUserID = 1 OR UM.toUserID = 1
ORDER BY UM.messageID DESC

A simple method is
select um.*
from usermessage um
where um.messageid = (select min(um2.messageid)
from usermessage um2
where (um2.fromuserid, touserid) in ( (um.fromuserid, um.touserid), (um.touserid, um.fromuserid) )
);
Or, in MySQL 8+:
select um.*
from (select um.*,
row_number() over (partition by least(um.fromuserid, um.touserid), greatest(um.fromuserid, um.touserid) order by um.messageid desc) as seqnum
from usermessage um
) um
where seqnum = 1;

Related

MySQL Get all users who performed same actions as current user

I have to do a query and I can't figure it out. I have an actions table ( user_id , action, created_at ), and I need to retrieve all users who performed the same actions as current_user ( in exact order).
ex.
current_user delete 2022/03/19 13:40
current_user add_post 2022/03/19 13:45
current_user write_comment 2022/03/22 13:48
Query result:
user_5 delete 2021/03/15 14:50
user_5 add_post 2021/05/15 13:50
user_5 write_comment 2022/06/06 14:30
user_6 delete 2021/03/15 14:50
user_6 add_post 2021/05/15 13:50
user_6 write_comment 2022/06/06 14:30
( all users with same actions )
You don't stipulate that the exact matching has to be in groups of 3. The following query will identify exact action sequences of 1, 2, 3 or even more then 3:
CREATE TABLE actions(
user_id VARCHAR(50) NOT NULL
,action VARCHAR(50) NOT NULL
,created_at DATE NOT NULL
);
INSERT INTO actions(user_id,action,created_at)
VALUES
('current_user','delete','2022/03/19 13:40')
, ('current_user','add_post','2022/03/19 13:45')
, ('current_user','write_comment','2022/03/22 13:48')
, ('other_user','delete','2022/02/19 13:40')
, ('other_user','add_post','2022/02/19 13:45')
, ('other_user','write_comment','2022/02/22 13:48')
, ('diff_user','delete','2022/02/19 12:40')
, ('diff_user','add_post','2022/02/19 12:42')
, ('diff_user','other_action','2022/02/19 12:45')
, ('diff_user','write_comment','2022/02/22 12:48')
;
It appears (through a comment) that you can use the lag() so I suggest using row_number() to match the action sequences of current user to other users, as follows:
SELECT ou.*
FROM (
SELECT *
, row_number() OVER (
PARTITION BY user_id ORDER BY created_at
) AS rn
FROM actions
WHERE user_id <> 'current_user'
) AS OU
INNER JOIN (
SELECT *
, row_number() OVER (
ORDER BY created_at
) AS rn
FROM actions
WHERE user_id = 'current_user'
) AS CU ON ou.rn = cu.rn
AND ou.action = cu.action
result
+------------+---------------+---------------------+----+
| user_id | action | created_at | rn |
+------------+---------------+---------------------+----+
| diff_user | delete | 2022-02-19 12:40:00 | 1 |
| diff_user | add_post | 2022-02-19 12:42:00 | 2 |
| other_user | delete | 2022-02-19 13:40:00 | 1 |
| other_user | add_post | 2022-02-19 13:45:00 | 2 |
| other_user | write_comment | 2022-02-22 13:48:00 | 3 |
+------------+---------------+---------------------+----+
Now if you really did want to limit this to sequence match of just 3, then you could subsequently count(*) over(partition by user_id) then filter for when that calculation is 3:
SELECT *
FROM (
SELECT ou.*
, count(*) OVER (PARTITION BY ou.user_id) AS cn
FROM (
SELECT *
, row_number() OVER (
PARTITION BY user_id ORDER BY created_at
) AS rn
FROM actions
WHERE user_id <> 'current_user'
) AS OU
INNER JOIN (
SELECT *
, row_number() OVER (
ORDER BY created_at
) AS rn
FROM actions
WHERE user_id = 'current_user'
) AS CU ON ou.rn = cu.rn
AND ou.action = cu.action
) d
WHERE cn = 3
result
+------------+---------------+---------------------+----+----+
| user_id | action | created_at | rn | cn |
+------------+---------------+---------------------+----+----+
| other_user | write_comment | 2022-02-22 13:48:00 | 1 | 3 |
| other_user | add_post | 2022-02-19 13:45:00 | 2 | 3 |
| other_user | delete | 2022-02-19 13:40:00 | 3 | 3 |
+------------+---------------+---------------------+----+----+
for reference db<>fiddle (nb: using postgres as MySQL 8 wasn't available at the time)
btw: you probably also need to introduce the concept of "session" into this logic but that is left for you to consider.

sql sorting inside group by with two column

My table:
id | request | subject | date
1 | 5 | 1 | 576677
2 | 2 | 3 | 576698
3 | 5 | 1 | 576999
4 | 2 | 3 | 586999
Need to select unique records by two columns(request,subject) with showing last inserted id's.
My query:
SELECT *,MAX(id)
FROM `tbl`
GROUP BY CONCAT(`request_id`, `subject_id`)
HAVING (COUNT(`request_id`)>1 order by MAX(id) desc
But results:
id | request | subject | date
2 | 2 | 3 | 576698
1 | 5 | 1 | 576677
How to get records with id's 3 and 4 ?
Try this:
SELECT MIN(id) id, request, subject, MAX(`date`) `date`
FROM `tbl`
GROUP BY request, subject;
See it run on SQL Fiddle.
You can try this.
SELECT T.*
FROM T
INNER JOIN
(
SELECT MAX(`ID`) as ID,`request`,`subject`
FROM T
GROUP BY `request`,`subject`
HAVING COUNT(`ID`) > 1
)AS T1 ON T.ID = T1.ID
SQLFiddle
Thanks for all. My result is
SELECT MAX(id), id, request, subject, date
FROM `tbl`
GROUP BY request, subject having count(request)>1 order by MAX(id) desc

Count (Select * Join) mysql query

I'm stuck on this for hours, I'm trying to COUNT how many subscribers are there in Group A, Group B, Group C for this particular query:
SELECT rh.id_subscriber, rh.bill_month, rh.bill_year,
(
SELECT tbl_gen_info.gen_data_03
FROM tbl_subscriber
LEFT JOIN tbl_gen_info ON tbl_subscriber.bill_area_code = tbl_gen_info.gen_data_01
WHERE rh.id_subscriber = tbl_subscriber.id_subscriber
) AS group_area
FROM tbl_reading_head AS rh
WHERE rh.id_soa_head IS NULL
AND rh.read_status <> 'Beginning'
AND rh.rec_status = 'active'
ORDER BY rh.id_subscriber
The sub-query gets the Group area gen_data_03 from tbl_gen_info
Tables contain this information:
tbl_gen_info
--------------------------------------------
| gen_category | gen_data_01 | gen_data_03 |
--------------------------------------------
| Area Code | Camacho St. | Group A |
--------------------------------------------
tbl_subscriber
----------------------------------
| id_subscriber | bill_area_code |
----------------------------------
| 1 | Camacho St. |
----------------------------------
tbl_reading_head
----------------------------------------------------------------------
| id_subscriber | id_soa_head | read_status | bill_month | bill_year |
----------------------------------------------------------------------
| 1 | NULL | Metered | 10 | 2017 |
----------------------------------------------------------------------
Notice that each id_subscriber has two (2) rows (one for electric, one for water). After grouping by id_subscriber:
GROUP BY rh.id_subscriber
I got this:
I tried adding COUNT before the sub-query making it:
COUNT(SELECT tbl_gen_info.gen_data_03 ...) AS group_area
but that doesn't work.
Use a subquery:
SELECT rh.group_area, COUNT(*)
FROM (SELECT rh.id_subscriber, rh.bill_month, rh.bill_year,
(SELECT tbl_gen_info.gen_data_03
FROM tbl_subscriber LEFT JOIN
tbl_gen_info
ON tbl_subscriber.bill_area_code = tbl_gen_info.gen_data_01
WHERE rh.id_subscriber = tbl_subscriber.id_subscriber
) as group_area
FROM tbl_reading_head rh
WHERE rh.id_soa_head IS NULL AND
rh.read_status <> 'Beginning' AND
rh.rec_status = 'active'
) rh
GROUP BY rh.group_area;

Get second highest values from a table

I have a table like this:
+----+---------+------------+
| id | conn_id | read_date |
+----+---------+------------+
| 1 | 1 | 2010-02-21 |
| 2 | 1 | 2011-02-21 |
| 3 | 2 | 2011-02-21 |
| 4 | 2 | 2013-02-21 |
| 5 | 2 | 2014-02-21 |
+----+---------+------------+
I want the second highest read_date for particular 'conn_id's i.e. I want a group by on conn_id. Please help me figure this out.
Here's a solution for a particular conn_id :
select max (read_date) from my_table
where conn_id=1
and read_date<(
select max (read_date) from my_table
where conn_id=1
)
If you want to get it for all conn_id using group by, do this:
select t.conn_id, (select max(i.read_date) from my_table i
where i.conn_id=t.conn_id and i.read_date<max(t.read_date))
from my_table t group by conn_id;
Following answer should work in MSSQL :
select id,conn_id,read_date from (
select *,ROW_NUMBER() over(Partition by conn_id order by read_date desc) as RN
from my_table
)
where RN =2
There is an intresting article on use of rank functions in MySQL here : ROW_NUMBER() in MySQL
If your table design as ID - date matching (ie a big id always a big date), you can group by id, otherwise do the following:
$sql_max = '(select conn_id, max(read_date) max_date from tab group by 1) as tab_max';
$sql_max2 = "(select tab.conn_id,max(tab.read_date) max_date2 from tab, $sql_max
where tab.conn_id = tab_max.conn_id and tab.read_date < tab_max.max_date
group by 1) as tab_max2";
$sql = "select tab.* from tab, $sql_max2
where tab.conn_id = tab_max2.conn_id and tab.read_date = tab_max2.max_date2";

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 |
+----+----------+------------+---------+