-1

Currently I have the following:

select *
from (SELECT 'MesRec' as Type, a.Message, a.UserFrom as UserID, u.Name, u.TeamID, t.TeamName, a.DateSent
FROM Messages a
LEFT OUTER JOIN Messages b
    ON a.UserFrom = b.UserFrom AND a.DateSent < b.DateSent
JOIN Users u on u.UserID = a.UserFrom
INNER JOIN Teams t on u.TeamID = t.TeamID
WHERE a.UserTo = 1 AND b.DateSent IS NULL
UNION ALL
SELECT 'MesSen' as Type, a.Message, a.UserTo as UserID, u.Name, u.TeamID, t.TeamName, a.DateSent
FROM Messages a
LEFT OUTER JOIN Messages b
    ON a.UserTo = b.UserTo AND a.DateSent < b.DateSent
JOIN Users u on u.UserID = a.UserTo
INNER JOIN Teams t on u.TeamID = t.TeamID
WHERE a.UserFrom = 1 AND b.DateSent IS NULL
) t
GROUP BY UserID

Which is getting all messages for User 1 from the message table and grouping them together by the last message in that group then if it's a sent message Type is MesSen if it's received it's MesRes, which is perfect.

However I also want to be able to count the "ReadDate" column and if ReadDate is null, to count how many unread messages there are. I've added sum(case when a.DateRead is null then 1 else 0 end) as Qty as follows:

select *
from (SELECT 'MesRec' as Type, a.Message, a.UserFrom as UserID, u.Name, u.TeamID, t.TeamName, a.DateSent, sum(case when a.DateRead is null then 1 else 0 end) as Qty
FROM Messages a
LEFT OUTER JOIN Messages b
    ON a.UserFrom = b.UserFrom AND a.DateSent < b.DateSent
JOIN Users u on u.UserID = a.UserFrom
INNER JOIN Teams t on u.TeamID = t.TeamID
WHERE a.UserTo = 1 AND b.DateSent IS NULL
UNION ALL
SELECT 'MesSen' as Type, a.Message, a.UserTo as UserID, u.Name, u.TeamID, t.TeamName, a.DateSent, 0 as Qty
FROM Messages a
LEFT OUTER JOIN Messages b
    ON a.UserTo = b.UserTo AND a.DateSent < b.DateSent
JOIN Users u on u.UserID = a.UserTo
INNER JOIN Teams t on u.TeamID = t.TeamID
WHERE a.UserFrom = 1 AND b.DateSent IS NULL
) t
GROUP BY UserID
ORDER BY DateSent DESC

Which is adding the Qty count of unread messages, however it's adding the unread quantity to the wrong row and no longer getting the most recent messages.

Edit:

Example Data without unread count:

Type | Message | UserID | Name | TeamID | TeamName | DateSent
MesRec | Last mess | 3 | Jimmy | 3 | Team 3 | 2022-08-28 19:06:14
MesRec | Test 1 | 4 | Alan | 4 | Team 4 | 2022-08-28 12:50:04
MesSen | Lorem | 5 | Sam | 5 | Team 5 | 2022-08-27 19:25:09

Example Data:

Type | Message | UserID | Name | TeamID | TeamName | DateSent | Qty
MesSen | Test... | 3 | Jimmy | 3 | Team 3 | 2022-08-28 14:40:33 | 0
MesRec | Test 1 | 4 | Alan | 4 | Team 4 | 2022-08-28 12:50:04 | 1
MesSen | Lorem | 5 | Sam | 5 | Team 5 | 2022-08-27 19:25:09 | 0

As you can see when the unread count is on, it is missing the last message received by user 3, and returning it as a message sent, however it's marking the message from user 4 as unread.

EDIT:

I've updated the query to the following:

select Type, Message, UserID, Name, TeamID TeamName, DateSent, sum(case when DateRead is null then 1 else 0 end) as Qty
from (SELECT 'MesRec' as Type, a.Message, a.UserFrom as UserID, u.Name, u.TeamID, t.TeamName, a.DateSent, a.DateRead
FROM Messages a
LEFT OUTER JOIN Messages b
    ON a.UserFrom = b.UserFrom AND a.DateSent < b.DateSent
JOIN Users u on u.UserID = a.UserFrom
INNER JOIN Teams t on u.TeamID = t.TeamID
WHERE a.UserTo = 1 AND b.DateSent IS NULL
UNION ALL
SELECT 'MesSen' as Type, a.Message, a.UserTo as UserID, u.Name, u.TeamID, t.TeamName, a.DateSent, NOW() as DateRead
FROM Messages a
LEFT OUTER JOIN Messages b
    ON a.UserTo = b.UserTo AND a.DateSent < b.DateSent
JOIN Users u on u.UserID = a.UserTo
INNER JOIN Teams t on u.TeamID = t.TeamID
WHERE a.UserFrom = 1 AND b.DateSent IS NULL
) t
GROUP BY UserID
ORDER BY DateSent DESC

Which appears to be returning the values as expected now, I addedNOW() to the MesSen type so that it always returns a date, as it doesn't affect sent messages and have done the count on the main select.

Your Common Sense
  • 156,878
  • 40
  • 214
  • 345
BN83
  • 902
  • 3
  • 9
  • 29
  • I must admit it took me a moment to understand that you are applying anti-joins here. I find `NOT IN` and `NOT EXISTS` way more readable, but that's personal preference I guess. Anayway, you select all messages user #1 sent and the receiver didn't get any message by anyone afterwards. And you select all messages user #1 received where the sender didn't send any message to anyone afterwards. Is this what you want? Don't you rather want just the latest sent/received message per user #1 correspondence? – Thorsten Kettner Aug 29 '22 at 11:15
  • 1
    `SELECT * ... GROUP BY userid` is invalid. It would only be valid, if there were just one row per userid, but then again the `GROUP BY` clause would be completely superfluous. The DBMS should have raised an error. As it didn't, you must be working in MySQL's cheat mode. Don't do that! Make sure to always have `SET sql_mode = 'ONLY_FULL_GROUP_BY';` when working in MySQL. – Thorsten Kettner Aug 29 '22 at 11:19
  • @ThorstenKettner is not grouping by the assigned value of UserID in the parent query? – BN83 Aug 29 '22 at 11:21
  • 1
    Your subquery may return two rows for userid #2, one where user #1 sent them a message, one where user #1 received one. If you group these two rows by userid (#2), then you must tell the DBMS which values to show. For instance there are two different types. Which do you want to see? The maximum value `MAX(type)`? The minimum value `MIN(type)`? You cannot `select *` you must tell the DBMS for every column which value you want. As mentioned: Never use MySQL in cheat mode. Otherwise you may end up with queries that seem to work, but are not reliable. – Thorsten Kettner Aug 29 '22 at 11:25
  • 1
    Which MySQL version are you using? From the disabled ONLY_FULL_GROUP_BY mode I fear that you are using some old version. Before MySQL 8, some queries were very difficult to write, so better avoid those old versions. and use an up-to-date one – Thorsten Kettner Aug 29 '22 at 11:27
  • @ThorstenKettner 'MesSen' will always be checking `WHERE a.UserFrom = 1` and 'MesRec' will always be checking `WHERE a.UserTo = 1` (where number 1 will be replaced by the logged in User's ID. So it will always be looking for all messages sent by a user, or all messages received by a user and then showing the latest? – BN83 Aug 29 '22 at 11:27
  • Please don't insert "EDIT"s/"UPDATE"s, just make your post the best presentation as of edit time. – philipxy Aug 29 '22 at 11:29
  • "So it will always be [...] showing the latest?": No, not really. If user #1 received the last messages from user #2 yesterday, you check whether user #2 sent any message afterwards. So, if user#2 sent a message to user #3 today, you won't select user #2. – Thorsten Kettner Aug 29 '22 at 11:30
  • Does this answer your question? [Error related to only\_full\_group\_by when executing a query in MySql](https://stackoverflow.com/questions/34115174/error-related-to-only-full-group-by-when-executing-a-query-in-mysql) – philipxy Aug 29 '22 at 11:30
  • Debug questions require a [mre]--cut & paste & runnable code including initialization; desired & actual output (including verbatim error messages); tags & versions; clear specification & explanation. For SQL include DDL & tabular initialization code. For debug that includes the least code you can give that is code that you show is OK extended by code that you show is not OK. [ask] [Help] When you get a result you don't expect, pause your overall goal, chop to the 1st subexpression with unexpected result & say what you expected & why, justified by documentation. (Debugging fundamental.) – philipxy Aug 29 '22 at 11:31
  • 1
    What count do you actaully want to show? So far you are only looking for the last sent and last received message per correspondence (or at least you are trying to do so). So the unread count is either 0 or 1 or 2? Older messages don't matter? Say, user #2 wrote five messages in the last hour and user #1 has not read them yet. Then you want to show 1 (or 2 in case user #2 also did not read user #1's last message)? – Thorsten Kettner Aug 29 '22 at 11:58
  • @ThorstenKettner Ah, you're right. This is just getting the last message so the count is pointless. This query is edited from a previous query that DID return how many unready messages there were, but it wasn't getting the correct message content. – BN83 Aug 29 '22 at 12:09
  • I've written an answer with a query that might help you. And as mentioned, always `SET sql_mode = 'ONLY_FULL_GROUP_BY';` when working with MySQL in order to have the DBMS help you with `GROUP BY` queries. You need that help. – Thorsten Kettner Aug 29 '22 at 15:25

1 Answers1

1

UNION ALL is not the best approach for this task. A FULL OUTER JOIN of sent and received messages might be a good approach, but MySQL does not support full outer joins.

In below query I just pick all messages involving user #1, aggregate them by the other user and use conditional aggregation (CASE expression inside the aggregation function) to get the information I want. As this gives me for each conversation the partner and the last message datetime, I can join the message table again to get that last message.

You may want to replace SELECT * with the columns you really want to see.

select *
from
(
  select
    case when userfrom = 1 then userto else userfrom end as other_user_id,
    count(*) as total_count,
    count(case when userfrom = 1 then 1 end) as sent_count,
    count(case when userto = 1 then 1 end) as received_count,
    count(case when userfrom = 1 and readdate is null then 1 end) as sent_unread,
    count(case when userto = 1 and readdate is null then 1 end) as received_unread,
    max(case when userfrom = 1 then datesent end) as last_sent_date,
    max(case when userto = 1 then datesent end) as last_received_date,
    max(datesent) as last_msg_date
  from messages
  where 1 in (userfrom, userto)
  group by case when userfrom = 1 then userto else userfrom end
) aggregated
join users u on u.userid = aggregated.other_user_id
join teams t on t.teamid = u.teamid
left join messages m on 1 in (m.userfrom, m.userto)
                     and aggregated.other_user_id in (m.userfrom, m.userto)
                     and m.datesent = aggregated.last_msg_date
order by aggregated.other_user_id;
Thorsten Kettner
  • 89,309
  • 7
  • 49
  • 73
  • That seems amazing and concise, I'll take the time to try and figure out exactly what is going on in that query to learn for future purposes. Thanks! – BN83 Aug 30 '22 at 09:58