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
)
Related
I have been searching a lot, but probably I don't know how to ask the question properly.
I'm storing all chat messages between all users in table called 'user_messages' which looks like this:
message_from_user_id | message_to_user_id | message_content | message_date
1 | 2 | "hey" | 07:36
1 | 2 | "how are u?" | 07:37
2 | 1 | "hey, im fine" | 07:38
3 | 1 | "wassup" | 09:21
4 | 2 | "wow" | 11:55
5 | 1 | "example" | 5:34
Now let's say I'm the user with id 1. And I want to display my latest chats with all people (something like messenger).
The result I would like to achieve is:
message_from_user_id | message_to_user_id | message_content | message_date
3 | 1 | "wassup" | 09:21
2 | 1 | "hey, im fine" | 07:38
5 | 1 | "example" | 5:34
So basically I need to select all messages where message_from_user_id = 1 or message_to_user_id = 1 but how can I make that the only one latest result for every chat will be displayed?
Even in this scenario:
message_from_user_id | message_to_user_id | message_content | message_date
1 | 2 | "hey" | 07:36
1 | 2 | "how are u?" | 07:37
2 | 1 | "hey, im fine" | 07:38
I want to get only one result that would be this:
2 | 1 | "hey, im fine" | 07:38
I'm using MySQL and PHP.
One method uses window functions:
select um.*
from (select um.*,
row_number() over (partition by coalesce(nullif(message_from_user_id, 1), message_to_user_id)
order by message_date desc
) as seqnum
from user_messages um
where 1 in (message_from_user_id, message_to_user_id)
) um
where seqnum = 1;
The expression:
coalesce(nullif(message_from_user_id, 1), message_to_user_id)
Is just a more concise way of writing:
(case when message_from_user_id <> 1
then message_to_user_id
else message_from_user_id
end)
I have a table like this:
// messages
+----+----------------+-----------+-------------+-------------+
| id | content | sender_id | receiver_id | date_time |
+----+----------------+-----------+-------------+-------------+
| 1 | whatever1 | 1 | 3 | 1521097240 |
| 2 | whatever2 | 3 | 1 | 1521097241 |
| 3 | whatever3 | 1 | 3 | 1521097242 |
| 4 | whatever4 | 1 | 4 | 1521097243 |
| 5 | whatever5 | 1 | 5 | 1521097244 |
| 6 | whatever6 | 5 | 1 | 1521097245 |
+----+----------------+-----------+-------------+-------------+
Now I need to count the number of messages that user sender_id = 1 to different people. So assuming all those row are in the past day, then the result should be 3. Because sender_id = 1 have sent messages to receiver_ids = 3,4,5. How can I do that count in Mysql?
Here is what I've tried:
SELECT count(1) as sent_messages_num
FROM messages
WHERE sender_id = 1
AND date_time > UNIX_TIMESTAMP(DATE_SUB(now(), INTERVAL 1 DAY))
So all I need it adding a grouping. But not sure how should I do that?
You need to use distinct in COUNT
SELECT count(distinct receiver_id) as sent_messages_num
FROM users
WHERE sender_id = 1
AND date_time > UNIX_TIMESTAMP(DATE_SUB(now(), INTERVAL 1 DAY))
Try using the following query:
SELECT count(*) as sent_messages_num, receiver_id
FROM users
WHERE sender_id = 1
AND date_time > UNIX_TIMESTAMP(DATE_SUB(now(), INTERVAL 1 DAY))
GROUP BY receiver_id
This will give you the number of messages per receiver. After I read the answer again, I think the following might suit better:
SELECT count(DISTINCT receiver_id) as sent_messages_num
FROM users
WHERE sender_id = 1
AND date_time > UNIX_TIMESTAMP(DATE_SUB(now(), INTERVAL 1 DAY))
With my limited knowledge of complex mysql queries I'm trying to retrieve some information from a database.
The story is this; users get an invite to come to our company. On the basis of this one invite, users can get multiple notifications and multiple appointments. I've got three relevant tables:
invites
-------
| id | name | created |
-----------------------------
| 1 | someth1 | 2018-02-03 |
| 2 | someth2 | 2018-02-03 |
| 3 | someth3 | 2018-02-03 |
notifications
-------------
| id | inv_id | message |
--------------------------
| 1 | 101 | hello |
| 2 | 287 | hi |
| 3 | 827 | hey |
appointments
------------
| id | inv_id | start_at |
-----------------------------
| 1 | 101 | 2018-02-03 |
| 2 | 287 | 2018-02-08 |
| 3 | 827 | 2018-02-15 |
I currently have a query, which shows a list of notifications send to users, for all invites done after 1 feb 2018, and which have an appointment no later than '2018-03-10'.
SELECT id, inv_id, message
FROM notifications
WHERE inv_id IN (
SELECT id
FROM invites as invite
WHERE created > '2018-02-01'
AND id IN (
SELECT inv_id
FROM appointments
WHERE invite.id = inv_id
AND start_at < '2018-03-10'
)
)
ORDER BY inv_id ASC;
The result looks something like this:
| id | inv_id | message |
--------------------------
| 1 | 101 | hello |
| 2 | 287 | hi |
| 3 | 827 | hey |
I now want to add the start_at of the first appointment for these notifications
| id | inv_id | message | start_at |
---------------------------------------
| 1 | 101 | hello | 2018-02-03 |
| 2 | 287 | hi | 2018-02-08 |
| 3 | 827 | hey | 2018-02-15 |
But from here I'm kinda lost in how I should do that.
Does anybody know how I can add the start_at of the first appointment which corresponds to the invite for the respective notification? So it should show the start_at of the first appointment for the invite of the notification inv_id?
Try this:
SELECT id, N.inv_id, message,A.start_at
FROM notifications N
JOIN(
SELECT inv_id,MIN(start_at) start_at
FROM appointments
WHERE inv_id IN (
SELECT id
FROM invites as invite
WHERE created > '2018-02-01'
AND id IN (
SELECT inv_id
FROM appointments
WHERE invite.id = inv_id
AND start_at < '2018-03-10'
)
)
GROUP BY inv_id
)A ON N.inv_id = A.inv_id
WHERE inv_id IN (
SELECT id
FROM invites as invite
WHERE created > '2018-02-01'
AND id IN (
SELECT inv_id
FROM appointments
WHERE invite.id = inv_id
AND start_at < '2018-03-10'
)
)
ORDER BY inv_id ASC;
You don't need a lot of subqueries to get the desired result. Just good use of join operations and just one subquery to get the first appointment so here is the query you need:
select n.id, n.inv_id, n.message, a.startat as start_at
from invites i
inner join notifications n
on i.id = n.inv_id
inner join (select inv_id, min(start_at) startat
from appointments
where start_at < '2018-03-10'
group by inv_id) a
on n.inv_id = a.inv_id
where i.created > '2018-02-01';
Notice that for your current sample data your desired result is impossible since in your query you use the notifications.inv_id IN invites.id and there is no equivalence (1,2,3 are different from 101, 287, 827).
Because of that I created a SQLFiddle to show the working query but with those ids "101, 287, 827" as invites. Here it is: http://sqlfiddle.com/#!9/2d5175/2
Also, if you want the notifications even if there is no appointment for it change the join operation between notifications and the subquery from inner join to left join
I have table in which all conversation is stored like this
Suppose I have a table like this (fromUser and toUser is fk from user table)
-----------------------------------------
Id. | FromUser | toUser | message |
| | | |
1 | 1 | 2 | Hi |
2 | 2 | 1 | hello |
3 | 3 | 1 | hi |
4 | 1 | 4 | hello |
-----------------------------------------
Desired output for distinct row for user 1 orderBy last updated date
-----------------------------------------
Id. | FromUser | toUser | message |
| | | |
2 | 2 | 1 | hello |
3 | 3 | 1 | hi |
4 | 1 | 4 | hello |
-----------------------------------------
Desired output for distinct row for user 2 orderBy last date
-----------------------------------------
Id. | FromUser | toUser | message |
| | | |
2 | 2 | 1 | hello |
-----------------------------------------
Desired output for distinct row for user 3 orderBy last date
-----------------------------------------
Id. | FromUser | toUser | message |
| | | |
3 | 3 | 1 | hi |
-----------------------------------------
As well for other users
The query I came up with is a bit complex, so here it is in multiple steps:
1) Get the “Other User” for the message matching the requested user.
SET #id = 1;
SELECT id, FromUser, ToUser, updateDate,
IF(FromUser=#id, ToUser, FromUser) as OtherUser
FROM user_msgs
WHERE #id=FromUser OR #id=ToUser;
2) Wrap the above and get the last updateDate. (see below for user 'id' instead)
SET #id = 1;
SELECT OtherUser, max(updateDate) as updateDate FROM
(
SELECT id, FromUser, ToUser, updateDate,
IF(FromUser=#id, ToUser, FromUser) as OtherUser
FROM user_msgs
WHERE #id=FromUser OR #id=ToUser
) AS i1
GROUP BY OtherUser;
3) FULL QUERY HERE ... Finally wrap again to get the desired records.
SET #id = 1;
SELECT * FROM user_msgs O
WHERE EXISTS
(
SELECT * FROM
(
SELECT OtherUser, min(updateDate) as updateDate FROM
(
SELECT id, FromUser, ToUser, updateDate,
IF(FromUser=#id, ToUser, FromUser) as OtherUser
FROM user_msgs
WHERE #id=FromUser OR #id=ToUser
) AS i1
GROUP BY OtherUser
) i2
WHERE O.updateDate = i2.updateDate AND
(( O.FromUser=#id AND i2.OtherUser = O.ToUser) OR
( O.ToUser=#id AND i2.OtherUser = O.FromUser) )
);
Note: If you don’t have a last date "updateDate" field, and instead are using “id” (I wasn’t sure), then just replace all the “updateDate” field occurrences in the query with “id”.
SQL Fiddle: link
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