MySQL Distinct / Group by, breaking my head with this one - mysql

So, what I'm trying to do is to retrieve a list of all "initial" messages a person sees in their messaging window
This is the table structure
thread_id | sender | receiver | message | date | sender_deleted | sender_received | read
xRdaQ | bTP5n | lCBNA | hello! | date | 0 | 0 |
xRdaQ | lCBNA | bTP5n | hey! | date | 0 | 0 |
1T4xR | bTP5n | An03R | hhi | date | 0 | 0 |
The queries I tried so far:
select * from messages where sender = 'bTP5n'
union select * from messages where receiver = 'bTP5n'
group by conversation_id
And I still get the two rows with the same thread_id
The same with this one query:
select * from messages where sender = 'bTP5n'
union select * from messages where receiver = 'bTP5n'
group by conversation_id order by date desc
Both of them are failing to return what I want, which is all unique thread_id where the sender or the receiver is equal to "bTP5n"
Disclaimer: Dummy data was used for this question

If you are using group by in second union query then it is only apply to the second query , if you want to apply in all the result then you have to write group by outside of all the results.
Try below query:
select * from
(select * from messages where sender = 'bTP5n'
union
select * from messages where receiver = 'bTP5n'
)
as a group by conversation_id order by date desc

The GROUP BY clause is required as soon as statistical calculation functions are used with raw data. It's not your case in your examples

Related

MySQL GROUPBY show newest row

I have a messaging system I am working on that receives messages from my Facebook Business Page and stores the information in my database. The information provided in the callback is a Sender ID, Recipient ID, and message data. I am wanting to group all messages between the sender and recipient together and return only the newest result (by row ID number) at the top of the list.
For example :
ID | Sender ID | Recipient ID | is_read
1 | 67890 | 12345 | 1
2 | 23232 | 12345 | 0
3 | 12345 | 67890 | 1
4 | 67890 | 12345 | 0
5 | 12345 | 23232 | 1
6 | 55555 | 12345 | 1
I don't want to show any results with Sender ID "12345".
The result I need should look something like this
Result | Row ID | Sender ID
1 | 4 | 67890
2 | 2 | 23232
3 | 6 | 55555
Here is my current query. Should return an Array with the newest message first no matter the senderid order. Currently, I get random results.
$sql = "SELECT id, senderid, sender_name, is_read FROM (SELECT id, senderid, sender_name, is_read FROM FB WHERE senderid != '".$fb_page_id."' GROUP BY senderid) AS f ORDER BY is_read ASC LIMIT ".$page_num * $perpage.", ".$perpage;
This has to be something simple.... just can't figure it out... lol.
If you just need the sender and its latest id in the resultset, we can just use aggregation here:
select max(id) as last_id, sender_id
from fb
where sender_id != 12345
group by sender_id
order by last_id desc
If, on the other hand, you need the entire latest row per sender, you can use window functions:
select *
from (
select fb.*, row_number() over(partition by sender_id order by id desc) rn
from fb
where sender_id != 12345
) f
where rn = 1
order by id desc
You can add the limit clause after the order by if that's needed.
In MySQL < 8.0, where window functions are not supported, we can use a correlated subquery instead:
select *
from fb f
where sender_id != 12345 and id = (
select max(f1.id) from fb f1 where f1.sender_id = f.sender_id
)
order by id desc

MySQL: How to get leaderboard position, for each event in a series

I have the following data structure (simplified):
Users:
ID
Name
Events:
ID
Date
Results:
ID
User ID (foreign key)
Event ID (foreign key)
Points: (int)
I would like to know (ideally the most efficient way):
How to get a user's position in a 'league' compared to other users. And - - If possible using one query (or sub queries), how to break this down by event, e.g. the user's position after the 1st event, 2nd event, 3rd event etc.
I can get the leaderboard with:
select users.name, SUM(results.points) as points
from results
inner join users on results.user_id = users.id
group by users.id
order by points DESC
However, I'd like to know a user's position without having to return the entire table if possible.
Edit: I have supplied some sample data here.
Ideal output:
| User ID | Rank |
| 3 | 1 |
| 1 | 2 |
| 2 | 3 |
and something similar to (not exactly like this, it's flexible, just something that shows the user's rank from each event)
| User ID | After Event | Rank |
| 1 | 1 | 1 |
| 1 | 2 | 1 |
| 1 | 3 | 2 |
| 2 | 1 | 2 |
| 2 | 2 | 2 |
| 2 | 3 | 1 |
| 3 | 1 | 3 |
| 3 | 2 | 3 |
| 3 | 3 | 3 |
MySQL 8.0+ supports window functions so the use of dense_rank() comes in handy.
MySQL under 8.0 solution
Since your version is 5.7 you could imitate this like below:
select
t.id,
CASE WHEN #prevRank = points THEN #currRank
WHEN #prevRank := points THEN #currRank := #currRank + 1
END AS rank
from (
select users.id, SUM(results.points) as points
from results
inner join users on results.user_id = users.id
group by users.id
order by points DESC
) t
cross join (SELECT #currRank := 0, #prevRank := NULL) r
If you need data for particular user then add a WHERE condition to filter out everyone else in an outer query:
select *
from (
<< above query here >>
) t
where id = ? -- your id here
MySQL 8.0+ solution
rank is a reserved keyword so backticks are required when naming a column. We're using dense_rank window function which will assign ranks based od descending sorting of points acquired:
select id, dense_rank() over (order by points desc) as `rank`
from (
select users.id, SUM(results.points) as points
from results
inner join users on results.user_id = users.id
group by users.id
) t
order by `rank`
SET #rowno = 0;
select UserID, max(points), #rowno:=#rowno+1 as rank from
(
select users.id as UserID ,users.name as users_name,events.name, SUM(results.points) as points
from results
inner join users on results.user_id = users.id
inner join events on results.event_id= events.id
group by users.id,events.name,users.name
order by points DESC
) as T
group by UserID
order by max(points) desc

Number of unread messages sum

I want to retrieve messages and number of unread message (0) for a sender and dest in a conversation.
+---------------------------------------------------------------+
| messages |
+---------------------------------------------------------------+
| message_id | id_sender | id_dest | subject | message | read |
+---------------------------------------------------------------+
| 1 | 25 | 50 | Hi | message | 0 |
| 2 | 25 | 50 | Hi2 |message2 | 1 |
| 3 | 25 | 50 | Hi3 |message3 | 0 |
+---------------------------------------------------------------+
In this case the result must be 2. I try with
SELECT *
FROM
(SELECT message,sum(read = 0) as nm_messages
FROM messages
WHERE ( id_sender = id1 AND id_dest = id2 ) or
( id_dest = id1 AND id_sender = id2 )
ORDER BY message_id DESC
LIMIT 10) AS ttbl
ORDER BY message_id ASC
The messages part is ok but when
I add
sum(read = 0) as nm_messages
return only the firsth message if possible for both mysql postgresql
Thanks!
I have used PostgreSQL 9.4.11, compiled by Visual C++ build 1800, 64-bit.
With distinct on you can eliminate same rows with their unique ids. in this case i have used id_sender.
SELECT DISTINCT ON ( expression [, ...] ) keeps only the first row of each set of rows where the given expressions evaluate to equal
more information look at this link:
distinct on
Below sql query will return only the first message and the number of unread messages (0):
SELECT distinct on (id_Sender)
message,
count(case when read=0 then 1 end) over() as nm_messages
FROM messages
group by id_Sender,message,message_id
order by id_Sender,message_id
message | nm_message
message | 2
You should use the sum with if condition. Should be like this:
SELECT *
FROM
(SELECT GROUP_CONCAT(message),sum(IF(read = 0,1,0)) as nm_messages
FROM messages
WHERE ( id_sender = id1 AND id_dest = id2 ) or
( id_dest = id1 AND id_sender = id2 )
GROUP BY id_sender, id_dest
LIMIT 10) AS ttbl
ORDER BY message_id ASC
When the condition is true (read = 1), then it will sum up 1, otherwise 0.
I just saw that there was no grouping in the query. I added that. Also if you use an aggregate function, it doesnt make sense to do that only for one field (read), and not for others (message). So i put group_concact around message. That makes more sense to me?!

How can I filter an SQL query with a GROUP BY clause

I'm trying to formulate an SQL query for a messaging system that will return a list of all threads which have a status of 'OPEN' and whose last message was not posted by a user in a certain group.
The tables involved in the query are:
threads
-------
threadId
timestamp
subject
status
clientId
messages
--------
messageId
userId
messagebody
timestamp
threadId
users
-----
userId
username
groupId
The current query is:
SELECT threadId, timestamp, subject, status, clientId
FROM threads
WHERE status='OPEN'
ORDER BY timestamp DESC
which works fine; but I now have a new requirement - if the last message in the thread was posted by a user with a groupId of 1 then it should NOT be in the result set.
Including the information I need is simple enough:
SELECT threadId, timestamp, subject, status, clientId
FROM threads
INNER JOIN messages USING(threadId)
INNER JOIN users USING(userId)
WHERE status='OPEN'
GROUP BY threadId
ORDER BY timestamp DESC
but at this point I get stuck, because I can't figure out how to get the query to filter out threads based on the messages.userId with the highest timestamp within a particular group. I've looked at HAVING and all the aggregate functions and nothing seems to fit the bill.
Edit
I may not have been clear enough, so I'll try to illustrate with an example.
Here's some example data:
threads
threadId | timestamp | subject | status | clientId
--------------------------------------------------
1 | 1 | One | OPEN | 1
2 | 10 | Two | OPEN | 4
3 | 26 | Three | OPEN | 19
4 | 198 | Four | OPEN | 100
messages
messageId | userId | messagebody | timestamp | threadId
-------------------------------------------------------
1 | 3 | Hello, World| 1 | 1
2 | 1 | Hello | 3 | 1
3 | 1 | ^&%&6 | 10 | 2
4 | 2 | Test | 12 | 2
5 | 4 | Hi mum | 26 | 3
6 | 1 | More tests | 100 | 4
users
userId | username | groupId
---------------------------
1 | Gareth | 1
2 | JimBob | 2
3 | BillyBob | 2
4 | Bod | 3
In this example dataset threads 1 and 4 should be eliminated from the query because user Gareth (a member of group 1) is the last one to post to them (messageId 2 in threadId 1, and messageId 5 in threadId 4). So the result set should only have the thread data (threadId, timestamp, subject, status and clientId) for threads 2 and 3.
I've created an SQLfiddle for this test data.
Can anyone point me in the right direction here?
Sounds like you want this:
SELECT threadId, timestamp, subject, status, clientId
FROM threads t
INNER JOIN messages m
ON t.threadId = m.threadId
INNER JOIN users u
ON m.userId = u.userId
and u.groupId != 1 --placed the groupId filter here.
WHERE status='OPEN'
GROUP BY threadId
ORDER BY timestamp DESC
Edit, this appears to give you what you need:
SELECT t.threadId,
t.timestamp,
t.subject,
t.status,
t.clientId
FROM threads t
INNER JOIN messages m
ON t.threadId = m.threadId
INNER JOIN users u
ON m.userId = u.userId
WHERE status='OPEN'
AND NOT EXISTS (select t1.threadid
FROM threads t1
INNER JOIN messages m
ON t1.threadId = m.threadId
INNER JOIN users u
ON m.userId = u.userId
where u.groupid = 1
and t.threadid = t1.threadid)
ORDER BY timestamp DESC
see SQL Fiddle with Demo
Does adding LIMIT 1 to your query solve your problem?
SELECT threadId, timestamp, subject, status, clientId
FROM threads
INNER JOIN messages USING(threadId)
INNER JOIN users USING(userId)
WHERE status='OPEN'
GROUP BY threadId
ORDER BY timestamp DESC
LIMIT 1
Or changing the where clause to
WHERE status='OPEN' and groupID<>1
I think this is what you want?
select * from threads
where threadId not in
(
select messages.threadId
from messages
inner join (select threadId, MAX(timestamp) maxtime from messages group by threadId) t
on messages.threadId = t.threadId
and messages.timestamp = t.maxtime
where messages.userId=1
)

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