Creating a dynamic column on grouped mysql query - mysql

I have the following table in mysql
msg_id | thread_id | read_status
--------------------------------
1 | 1 | read
2 | 1 | read
3 | 2 | unread
4 | 2 | read
5 | 2 | read
6 | 3 | read
7 | 3 | unread
I want a query that will show me all messages grouped by thread_id and if they contain any unread messages I want the a column called read_status to show unread
so the query result would look like this:
thread_id | read_status
-----------------------
1 | read
2 | unread
3 | unread
So far i have:
SELECT
thread_id,
IF(user_read_status = 'U',"unread","read") as message_status FROM messages
GROUP BY thread_id
but this populates the "read_status" with the result of whatever the first message is, rather than if any of the messages are unread...
I have no idea about how to do this, can anyone help?
Thanks in advance

You need aggregation, such as:
select thread_id,
(case when sum(user_read_status = 'U') > 0 then 'unread' else 'read' end) as thread_status
from messages
group by thread_id;
If the message statuses are really "read" and "unread" (as in the sample data), you can take this shortcut:
select thread_id, max(message_status) as thread_status
from messages
group by thread_id;
This only works because you have two statuses and the alphabetical ordering has a meaning.

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.

two separate joins on one query; counting all messages to and from one user

I'm trying to figure out how I can show the count of messages sent a received by each user in a database. I have a table of users, where I want to pull the userid (called id) and the username, and a table of messages, which have an id, a fromid (fkey from users), a toid (fkey from users), and a body (the text of the message). The result would be like this:
id | username | tocount | fromcount
1 | user1 | 2 | 3
2 | user2 | 1 | 1
3 | user3 | 3 | 1
4 | lastuser | 0 | 1
How can I accomplish this? I've tried a number of different join combinations, but I end up getting inaccurate results.
One method uses union all followed by group by:
select userid, sum(fromnum) as fromcnt, sum(tonum) as tocnt
from ((select fromid as userid, 1 as fromnum, 0 as tonum
from messages
) union all
(select toid as userid, 0, 1
from messages
)
) ft
group by userid;

How can I make a dynamic limit selecting?

I have a table like this:
// notifications
+----+--------------+------+---------+------------+
| id | event | seen | id_user | time_stamp |
+----+--------------+------+---------+------------+
| 1 | vote | 1 | 123 | 1464174617 |
| 2 | comment | 1 | 456 | 1464174664 |
| 3 | vote | 1 | 123 | 1464174725 |
| 4 | answer | 1 | 123 | 1464174813 |
| 5 | comment | NULL | 456 | 1464174928 |
| 6 | comment | 1 | 123 | 1464175114 |
| 7 | vote | NULL | 456 | 1464175317 |
| 8 | answer | NULL | 123 | 1464175279 |
| 9 | vote | NULL | 123 | 1464176618 |
+----+--------------+------+---------+------------+
I'm trying to create a recent inbox messages box. I want to fetch both read and unread message for each user based on this condition:
Note: Always there should be at least 2 messages in the end of list which are read (seen = 1) .
The result should be containing 15 rows by default. Unless:
there is more that 13 unread message (seen = NULL) (plus two read messages that have been read and always there).
all messages are less than 15 in total.
Here is some examples: (read message: seen = 1, unread message: seen = NULL)
Info: read message: 1, unread message: 1
Output: 2 rows. (containing one read message and one unread message)
Info: read message: 40, unread message: 1
Output: 15 rows. (containing 14 read messages and 1 unread message)
Info: read message: 12, unread message: 20
Output: 22 rows. (containing 2 read messages and 20 unread message)
Info: read message: 0, unread message: 16
Output: 16 rows. (containing 0 read messages and 16 unread message)
Info: read message: 25, unread message: 15
Output: 17 rows. (containing 2 read messages and 15 unread message)
Info: read message: 4, unread message: 8
Output: 12 rows. (containing 4 read messages and 8 unread message)
Info: read message: 745, unread message: 4
Output: 15 rows. (containing 11 read messages and 4 unread message)
As you see, I'm trying to fetch all unread messages plus at least two read messages. Also the number of rows should be 15 by default. How can I do that?
Here is my query:
SELECT id, event seen, time_stamp
FROM notifications
WHERE id_user = :id AND
LIMIT 15 OR IF (seen IS NULL > 15) THEN seen AND
SELECT id, event seen, time_stamp FROM notification
WHERE id_user = :id AND seen IS NOT NULL LIMIT 2
ENDIF
One method comes close:
SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id
ORDER BY (seen IS NULL) desc, time_stamp desc
LIMIT 15;
However, this does not get all unseen messages. So, here is sort of hack to do this:
(SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id AND seen IS NULL
) UNION
(SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id
ORDER BY (seen IS NULL) desc, time_stamp desc
LIMIT 15
)
ORDER BY (seen IS NULL) desc, time_stamp desc;
The first subquery gets all unseen messages. The second gets fifteen messages. The UNION removes duplicates, but no other limit is applied. I believe this does what you want.
There is no possibility to use varying values for LIMIT in MySQL.
The workaround would be to compute the value for the LIMIT and save it into a variable, and then use the prepared statements

SQL LIMIT expression

Users:
UserID | UserNick
3 | Joe
23 | Peter
4 | Mary
Messages:
FromUserID | theMSG
3 | Hi
3 | What' up?
23 | asdfg
23 | OK...
4 | Hi, this is Mary
I have a query that gives the following result:
UserID | Message
1 | Hello
1 | How are ya?
2 | yadda yadda
5 | Cool.
5 | I didn't know that.
I now want to limit the result. Not by the number of rows I get back, by the number of different users from whom I want to see the messages.
"Give me three messages of the first 2 users"
UserID | Message
1 | Hello
1 | How are ya?
2 | yadda yadda
But if I write LIMIT 2, it will give me only
UserID | Message
1 | Hello
1 | How are ya?
How do I achieve what I want? What's the keyword I need?
I think you need to use a nested query like this:
SELECT * FROM messages
WHERE UserID IN
(SELECT DISTINCT UserID FROM messages ORDER BY UserID LIMIT 2)
but without knowing the table structure it is difficult to say more.
The idea is:
get the users you want in nested query
get their messages with outer query
You could use the following query. It gives you options to select first n unique users or last n unique users. Use ASC for first or DESC for last n users.
SELECT *
FROM messages
WHERE UserID IN (
SELECT DISTINCT UserID
FROM messages
ORDER BY UserID ASC/DESC
LIMIT 2
)
LIMIT 2
Inner Limit defines the number of unique users you want to select.
Outer Limit definesthe number of messages you wish to see from users
If I understand your question, you can get those results with:
SELECT * FROM table WHERE UserID IN(1, 2)
or
SELECT * FROM table WHERE UserID BETWEEN 1 AND 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 |
+----+----------+------------+---------+