mysql queries in order -> limit,sort,group. How? - mysql

oh, that's driving me crazy. I have messages table, simplified example:
id[int] - sender[int] - receiver[int] - conv_id[bigint] - received[int] - stamp[int]
1 2 1 5 1 timestamp+1
2 2 1 5 1 timestamp+2
3 3 1 6 1 timestamp+3
4 4 1 7 1 timestamp+4
5 5 1 8 1 timestamp+5
6 5 1 8 1 timestamp+6
now I'm interesting a results grouped by receiver, limited to 3 senders and sorted by stamp DESC. How to do that?
I already have this, but it's taking all messages and i believe that's not the best way to not loose performance, even though I'm really not the guru of mysql:
SELECT id, sender, receiver, conv_id FROM
(SELECT m.id, m.sender, m.receiver,m.conv_id
FROM messages AS m WHERE m.receiver = 1 AND m.received = 1
ORDER BY m.stamp DESC) as messages_tmp
WHERE receiver = 1 GROUP BY conv_id ORDER BY NULL LIMIT 0,3
This should return these results in that order:
id = 6, id = 4, id = 3
The thing is I'm already doing to queries of this, for m.received = 0 and than if not enough of the results, for m.received = 1. So far my database isn't too big, but if it's gets bigger I'm afraid it can be slow. I'm thinking about possibility to limit results of the subquery, but have no idea how to that and be sure I'll get enough results after GROUP.
Thanks.

SELECT id, sender, receiver,conv_id
FROM messages WHERE receiver = 1 AND received = 1
GROUP BY conv_id
ORDER BY id DESC LIMIT 3

Related

MySQL Query to find users still inside room

Below is my database table, where I will have Check In and Check Out entry records for attending the conference room.
id registration_id roomno day type
1 101 1 2 In
2 103 1 2 In
3 101 1 2 Out
4 105 1 2 In
5 103 1 2 Out
6 101 1 2 In
7 103 1 2 In
8 101 1 2 Out
9 105 1 2 In
10 103 1 2 Out
Now, I want to select those records, which are still attending the conference. Condition is like their last record should be type = In. There can be multiple In/Out entries for each user during a day.
Please let me know the quickest possible MySQL query.
Thanks
Answer which I ended up using:
select * from `registrations_inouts` t
group by t.registration_id
having max(id) = max(case when type = 'In' then id end)
order by rand() limit 1;
Here is one method using not exists:
select *
from t
where t.type = 'In' and
not exists (select 1
from t t2
where t2.registration_id = t.registration_id and t2.type = 'Out'
);
Another method uses conditional aggregation:
select t.registration_id
from t
group by t.registration_id
having max(id) = max(case when type = 'In' then id end);
Note: both of these assume that the ids are assigned sequentially in time, so larger ids are later in time.

MySQL Winning Streak for every Player

I have a table with winner and loser statistics from a game:
id winner_id loser_id
1 1 2
2 1 2
3 3 4
4 4 3
5 1 2
6 2 1
7 3 4
8 3 2
9 3 5
10 3 6
11 2 3
12 3 6
13 2 3
I want a result table where i can find the highest winning streak of every player in the game. A streak of a player is broken, when he lost a game (player_id = loser_id). It should look like:
player_id win_streak
1 3
2 2
3 4
4 1
5 0
6 0
I tried many queries with user defined variables etc. but i can't find a solution. Thanks!
SQL Fiddle : http://sqlfiddle.com/#!9/3da5f/1
Is this the same as Alex's approach; I'm not quite sure, except that it seems to have one distinct advantage.... ;-)
SELECT player_id, MAX(CASE WHEN result = 'winner' THEN running ELSE 0 END) streak
FROM
( SELECT *
, IF(player_id = #prev_player,IF(result=#prev_result,#i:=#i+1,#i:=1),#i:=1) running
, #prev_result := result
, #prev_player:=player_id
FROM
( SELECT id, 'winner' result, winner_id player_id FROM my_table
UNION
SELECT id, 'loser', loser_id FROM my_table
) x
,
( SELECT #i:=1,#prev_result = '',#prev_player:='' ) vars
ORDER
BY x.player_id
, x.id
) a
GROUP
BY player_id;
I guess you should better to do that on php (or any other language you use) side.
But just to give you some idea and as experiment and example for some unique cases (hope it could be useful somewhere)
Here is my approach:
http://sqlfiddle.com/#!9/57cc65/1
SELECT r.winner_id,
(SELECT MAX(IF(winner_id=r.winner_id,IF(#i IS NULL, #i:=1,#i:=#i+1), IF(loser_id = r.winner_id, #i:=0,0)))
FROM Results r1
WHERE r1.winner_id = r.winner_id
OR r1.loser_id = r.winner_id
GROUP BY IF(winner_id=r.winner_id, winner_id,loser_id)) win_streak
FROM ( SELECT winner_id
FROM Results
GROUP BY winner_id
) r
It returns not all ids now but only who had ever win. So to make it better, probably you have user table. If so it would simplify a query. If you have no user table you need to union all somehow users who had never win.
You are welcome if any questions.

select last three commenter for each post (Greatest n per group)

MySQL
SELECT DISTINCT comments.commenter_id FROM comments WHERE ((oid IN (421,425)
AND otype = 'post') (oid IN (331) AND otype = 'photo')) ORDER BY
post_id,type,comment_id LIMIT 3
What i wanted to do is select last three distinct commenters for each individual post or photo with respective ids.
i.e max 3 commenters for each o_id and o_type combinations
But the above instead of yielding me last three distinct commenters yields me total three.
Where i am going wrong ? can anyone help me out ?
IF LIMIT IS 2
ID | oid | otype | commenter_id
1 1 post 1
2 1 post 1
3 1 post 2
4 1 post 3
5 2 post 1
6 1 photo 2
7 2 post 3
OUTPUT SHOULD BE
commenter_id| o_type | o_id
3 post 1
2 post 1
3 post 2
1 post 2
2 photo 1
SOLVED - GREATESET N PER GROUP
That was easy though! :P
This question helped me lot.
SELECT DISTINCT t.commenter_id,t.o_id,t.otype
from
(
SELECT c.*,
#row_number:=if(#post_id = oid, #row_number + 1, 1) AS row_number,
#oid:=oid AS varval
FROM comments c
join (select #row_number := 0, #oid:= NULL) as var
ON
((oid IN (425) AND otype = 'post') OR (oid IN (331) AND otype = 'photo'))
order by comment_id DESC
) t
where t.row_number <=2

Grouping notifications by type in MySql

I have the following table structure for notifications table.
id user_id post_id type status date seconduser_id
1 1 23 1 0 somedate 4
2 2 25 2 0 somedate 3
3 3 26 1 0 somedate 4
4 4 28 2 1 somedate 5
5 5 21 2 0 somedate 4
---
---
and so on
Here type = 1 means a like and type = 2 means a comment. status = 0 means seconduser_id hasn't seen the notification yet. seconduser_id is the notification recipient.
Is it possible to get notifications for 1 user (example seconduser_id = 4), with notification grouped by type, showing count and the latest user_id for each type (in one query)?
The implementation of this would be something like User3 and 10 other people liked your post.
Edit: So far I have something that pulls all notification for user 4 and then groups in php. I don't think this is efficient and so am looking for better solutions.
The basic answer is an aggregation query. The complication is getting the latest user id for each type. THere is one method, using substirng_index()/group_concat():
select type, count(*),
substring_index(group_concat(user_id order by id desc), ',', 1) as latest_user
from notifications n
where secondaryuser_id = 4 and status = 0
group by type;
I am not sure if you also want to filter by status.
Edit (added by OP):
Using the above code and grouping by both post_id and type. Because you want to say User1 and 10 others liked your post. Which means for each grouped notification, post_id has to be unique.
SELECT *, substring_index(group_concat(user_id order by id desc), ',', 1) as latest_user, COUNT(post_id) AS total
FROM notifications n
WHERE seconduser_id = 4 and status = 0
GROUP BY post_id, type
Try this:
SELECT MAX(user_id) AS LatestUser, type, COUNT(type) AS total
FROM notifications
WHERE seconduser_id = 4
GROUP BY type

Get the last 2 rows of a table while grouping one of the column. MySQL

Consider Facebook. Facebook displays the latest 2 comments of any status. I want to do something similar.
I have a table with e.g. status_id, comment_id, comment and timestamp.
Now I want to fetch the latest 2 comments for each status_id.
Currently I am first doing a GROUP_CONCAT of all columns, group by status_id and then taking the SUBSTRING_INDEX with -2.
This fetches the latest 2 comments, however the GROUP_CONCAT of all the records for a status_id is an overhead.
SELECT SUBSTRING_INDEX(GROUP_CONCAT('~', comment_id,
'~', comment,
'~', timestamp)
SEPARATOR '|~|'),
'|~|', -2)
FROM commenttable
GROUP BY status_id;
Can you help me with better approach?
My table looks like this -
status_id comment_id comment timestamp
1 1 xyz1 3 hour
1 2 xyz2 2 hour
1 3 xyz3 1 hour
2 4 xyz4 2 hour
2 6 xyz6 1 hour
3 5 xyz5 1 hour
So I want the output as -
1 2 xyz2 2 hour
1 3 xyz3 1 hour
2 4 xyz4 2 hour
2 6 xyz6 1 hour
3 5 xyz5 1 hour
Here is a great answer I came across here:
select status_id, comment_id, comment, timestamp
from commenttable
where (
select count(*) from commenttable as f
where f.status_id = commenttable.status_id
and f.timestamp < commenttable.timestamp
) <= 2;
This is not very efficient (O(n^2)) but it's a lot more efficient than concatenating strings and using substrings to isolate your desired result. Some would say that reverting to string operations instead of native database indexing robs you of the benefits of using a database in the first place.
After some struggle I found this solution -
The following gives me the row_id -
SELECT a.status_id,
a.comments_id,
COUNT(*) AS row_num
FROM comments a
JOIN comments b
ON a.status_id = b.status_id AND a.comments_id >= b.comments_id
GROUP BY a.status_id , a.comments_id
ORDER BY row_num DESC
The gives me the total rows -
SELECT com.status_id, COUNT(*) total
FROM comments com
GROUP BY com.status_id
In the where clause of the main select -
row_num = total OR row_num = total - 1
This gives the latest 2 rows. You can modify the where clause to fetch more than 2 latest rows.