Get only last messages from database mysql - 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

Related

Group by one field and display multiple fields (MySQL)

So i have these three tables :
Booking :
+----+---------+--------+---------------+-------------+
| id | idRoom | idUser | startDateTime | endDateTime |
+----+---------+--------+---------------+-------------+
| 4 | 3 | 1 | 07/06/2020 | 07/07/2020 |
| 5 | 3 | 2 | 07/06/2021 | 07/06/2021 |
+----+---------+--------+---------------+-------------+
Room :
+----+--------------+
| id | description |
+----+--------------+
| 3 | Room 1 |
+----+--------------+
User :
+----+----------+
| id | userName |
+----+----------+
| 1 | User 1 |
| 2 | User 2 |
+----+----------+
And want to select all the bookings (listed in table one) while displaying the User and the Room fields infos and group by the Room object.
I am using the JOIN clause along with the GROUP BY clause as follows :
select distinct r, b, u
from Booking b
join Room r on b.idRoom=r.id
join User u on b.idUser=u.id
where r.id=3
group by r, b, u
order by r
But it is not rendering the desired result.
Anyone suggests a working SQL query ?
EDIT (Desired Result ) :
+-------+--------+-----------+
| Rooms | Users | Bookings |
+-------+--------+-----------+
| 3 | 1 | 4 |
| | 2 | 5 |
+-------+--------+-----------+
As you wants to group by your query output by room, you can start your query from the room table. But not sure how you can get the Json formatted output. You can achieve the following output and rest part you should manage in the frontend.
Possible output-
Rooms Users Bookings
3 1 4
3 2 5
Query for the above output -
SELECT R.id AS Rooms,
B.idUser AS Users,
B.ID AS Bookings
FROM Room R
INNER JOIN Booking B ON R.Id = B.idRoom
For more details of a User, you can join the User table Now.

Django ORM left join with nested condition?

I am trying to build an ORM query in django. First please look at the database schema.
Meeting Table ( meeting_meeting )
+----+------+------+
| id | host | name |
+----+------+------+
| 1 | 1 | M1 |
| 2 | 8 | M2 |
| 3 | 1 | M3 |
| 4 | 1 | M4 |
+----+------+------+
Participans Table ( meeting_participants )
+----+------------+---------+
| id | meeting_id | user_id |
+----+------------+---------+
| 1 | 1 | 8 |
| 1 | 3 | 8 |
+----+------------+---------+
All I am trying to do is to generate a list of meeting that someone either created or joined. For example user 8 created only one meeting (M2) but joined on two meetings ( M1 & M3 ). So the query will return M1,M2 and M3 as Meeting QueryDict.
I have already done the query with pure sql.
SELECT DISTINCT meeting_meeting.id, meeting_meeting.* FROM meeting_meeting
LEFT JOIN meeting_participants on meeting_participants.meeting_id = meeting_meeting.id
and ( meeting_meeting.host_id = 8 OR meeting_participants.user_id = 8 )
I am just not sure how to do that with ORM in Django. I did find some reference about prefetch_related and select related but I can't seem to put the pieces together.
Use:
user_id = 8
Meeting.object.filter(Q(host_id=user_id)|Q(participants__user_id=user_id))
Reference: Q objects

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
)

SQL order by match to specific row

I have a example table below. I am trying to create a SQL query that gets all user_ids besides user_id of the current user and then orders by number of matches to the row with the current user_id
For example, if the user has a user_id of '1', I want to get all of the user_ids corresponding with the rows of id 2-8, and then order the user_ids from most matches to the row of the current user to least matches with the row of the current user
Let's say var current_user = 1
Something like this:
SELECT user_id
FROM assets
WHERE user_id <> `current_user` and
ORDER BY most matches to `current_user`"
The output should get 7,8,3,9,2
I would appreciate anyone's input on how I can effectively achieve this.
Table assets
+----------+---------+-------+--------+-------+
| id | user_id | cars | houses | boats |
+----------+---------+-------+--------+-------+
| 1 | 1 | 3 | 2 | 3 |
| 2 | 8 | 3 | 2 | 5 |
| 3 | 3 | 3 | 2 | 2 |
| 4 | 2 | 5 | 1 | 5 |
| 5 | 9 | 5 | 7 | 3 |
| 8 | 7 | 3 | 2 | 3 |
+----------+---------+-------+--------+-------+
I think you can just do this:
select a.*
from assets a cross join
assets a1
where a1.user_id = 1 and a.user_id <> a1.user_id
order by ( (a.cars = a1.cars) + (a.houses = a1.houses) + (a.boats = a1.boats) ) desc;
In MySQL, a boolean expression is treated as an integer in a numeric context, with 1 for true and 0 for false.
If you want to be fancier, you could order by the total difference:
order by ( abs(a.cars - a1.cars) + abs(a.houses - a1.houses) + abs(a.boats - a1.boats) );
This is called Manhattan distance, and you would be implementing a version of a nearest neighbor model.

Selecting first row that matches a criteria in Groupby

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