SQL - How to retrieve 5 most recent comments for each user - mysql

I have a table called 'user_text'
| id | user_id | date | text |
|----|---------|---------------------|----------------------|
| 1 | 4 | 07/01/2019 10:04:11 | This is a test |
| 2 | 9 | 19/11/2018 09:43:00 | Here's another test |
| ...| | | |
What I need to do is to select the 5 most recent (field 'date') entries for each user_id
I've searched a lot about it and it seems that somehow I need a subquery but I can't find the right combination.

In MySQL 5.x, one option uses a correlated subquery:
select u.*
from user_text u
where (
select count(*)
from user_text u1
where u1.user_id = u.user_id and u1.date >= u.date
) <= 5

You can use row_number():
select t.*
from (select t.*, row_number() over (partition by user_id order by date desc) as seqnum
from t
) t
where seqnum <= 5;

Related

Get last 3 rows from SQL table without duplicates of a row

Lets say we have a table that looks like this:
+---------------+----------------+-------------------+
| ID | random_string | time |
+---------------+----------------+-------------------+
| 2 | K2K3KD9AJ |2022-07-21 20:41:15|
| 1 | SJQJ8JD0W |2022-07-17 23:46:13|
| 1 | JSDOAJD8 |2022-07-11 02:52:21|
| 3 | KPWJOFPSS |2022-07-11 02:51:57|
| 1 | DA8HWD8HHD |2022-07-11 02:51:49|
------------------------------------------------------
I want to select the last 3 entries into the table, however they must all have separate ID's.
Expected Result:
+---------------+----------------+-------------------+
| ID | random_string | time |
+---------------+----------------+-------------------+
| 2 | K2K3KD9AJ |2022-07-21 20:41:15|
| 1 | SJQJ8JD0W |2022-07-17 23:46:13|
| 3 | KPWJOFPSS |2022-07-11 02:51:57|
------------------------------------------------------
I have already tried:
SELECT DISTINCT id FROM table ORDER BY time DESC LIMIT 3;
And:
SELECT MIN(id) as id FROM table GROUP BY time DESC LIMIT 3;
If you're not on MySQL 8, then I have two suggestions.
Using EXISTS:
SELECT m1.ID,
m1.random_string,
m1.time
FROM mytable m1
WHERE EXISTS
(SELECT ID
FROM mytable AS m2
GROUP BY ID
HAVING m1.ID=m2.ID
AND m1.time= MAX(time)
)
Using JOIN:
SELECT m1.ID,
m1.random_string,
m1.time
FROM mytable m1
JOIN
(SELECT ID, MAX(time) AS mxtime
FROM mytable
GROUP BY ID) AS m2
ON m1.ID=m2.ID
AND m1.time=m2.mxtime
I've not test in large data so don't know which will perform better (speed) however this should return the same result:
Here's a fiddle
Of course, this is considering that there will be no duplicate of exact same ID and time value; which seems to be very unlikely but still it's possible.
Using MySql 8 an easy solution is to assign a row number using a window:
select Id, random_string, time
from (
select *, Row_Number() over(partition by id order by time desc) rn
from t
)t
where rn = 1
order by time desc
limit 3;
See Demo

First Unique Sql row

I have a MySql table of users order and it has columns such as:
user_id | timestamp | is_order_Parent | Status |
1 | 10-02-2020 | N | C |
2 | 11-02-2010 | Y | D |
3 | 11-02-2020 | N | C |
1 | 12-02-2010 | N | C |
1 | 15-02-2020 | N | C |
2 | 15-02-2010 | N | C |
I want to count number of new custmer per day defined as: a customer who orders non-parent order and his order status is C AND WHEN COUNTING A USER ONCE IN A DAY WE DONT COUNT HIM FOR OTHER DAYS
An ideal resulted table will be:
Timestamp: Day | Distinct values of User ID
10-02-2020 | 1
11-02-2010 | 1
12-02-2010 | 0 <--- already counted user_id = 1 above, so no need to count it here
15-02-2010 | 1
table name is cscart_orders
If you are running MySQL 8.0, you can do this with window functions an aggregation:
select timestamp, sum(timestamp = timestamp0) new_users
from (
select
t.*,
min(case when is_order_parent = 'N' and status = 'C' then timestamp end) over(partition by user_id) timestamp0
from mytable t
) t
group by timestamp
The window min() computes the timestamp when each user became a "new user". Then, the outer query aggregates by date, and counts how many new users were found on that date.
A nice thing about this approach is that it does not require enumerating the dates separately.
You can use two levels of aggregation:
select first_timestamp, count(*)
from (select t.user_id, min(timestamp) as first_timestamp
from t
where is_order_parent = 'N' and status = 'C'
group by t.user_id
) t
group by first_timestamp;

How to retrieve one random row for each category?

I would like to retrieve one random row for each user_group. How can I do this ?
Here find 2 tables, user and user_group.
user :
id
firstname
user_group_id
user_group :
id
label
Data
users
1 | Thor | 1
2 | Iron man | 2
3 | Hulk | 3
4 | Groot | 3
user_groups
1 | admin
2 | support
3 | user
Results expected
1 | Thor | admin
2 | Iron man | support
4 | Groot | user (or 3 | Hulk | user)
The answer using GROUP BY will conflict with the default SQL mode in MySQL 5.7 and later, which makes it an error to reference columns in the select-list that are neither in the GROUP BY, nor in an aggregate function.
The solution in MySQL 8.0 is to use window functions:
SELECT r.id, r.firstname, r.user_group_id, g.label
FROM (
SELECT id, firstname, user_group_id,
ROW_NUMBER() OVER (PARTITION BY user_group_id ORDER BY RAND()) AS rownum
FROM users
) AS r
JOIN user_groups AS g ON (r.user_group_id = g.id)
WHERE r.rownum = 1
Re your comment:
SELECT r.id, r.firstname, r.user_group_id, r.count, g.label
FROM (
SELECT id, firstname, user_group_id,
ROW_NUMBER() OVER (PARTITION BY user_group_id ORDER BY RAND()) AS rownum,
COUNT(*) OVER (PARTITION BY user_group_id) AS count
FROM users
) AS r
JOIN user_groups AS g ON (r.user_group_id = g.id)
WHERE r.rownum = 1
Result:
+------+-----------+---------------+-------+---------+
| id | firstname | user_group_id | count | label |
+------+-----------+---------------+-------+---------+
| 1 | Thor | 1 | 1 | admin |
| 2 | Iron Man | 2 | 1 | support |
| 3 | Hulk | 3 | 2 | user |
+------+-----------+---------------+-------+---------+
You can use aggregate functions over windows. Read https://dev.mysql.com/doc/refman/8.0/en/window-functions-usage.html for details on this.
A solution that can work with MySQL 5.7 and later
You can create a temporary table from users using ORDER BY rand() to order your users randomly.
Then you can user GROUP BY with ANY_VALUE() in that temporary table to get the id of a random user from that group and just JOIN with your tables to get the other data you want.
You can see a working example here:
https://www.db-fiddle.com/f/jtjPDxFVyzh4zGjXNbLAiL/5
CREATE TEMPORARY TABLE temp_tbl
SELECT * FROM `users` ORDER BY rand() ;
SELECT u.id,u.firstname,g.label FROM
(
SELECT ANY_VALUE(id) AS id FROM temp_tbl GROUP BY user_group_id
) t
INNER JOIN `users` u ON t.id=u.id
INNER JOIN `user_groups` g ON g.id=u.user_group_id

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

Get second highest values from a table

I have a table like this:
+----+---------+------------+
| id | conn_id | read_date |
+----+---------+------------+
| 1 | 1 | 2010-02-21 |
| 2 | 1 | 2011-02-21 |
| 3 | 2 | 2011-02-21 |
| 4 | 2 | 2013-02-21 |
| 5 | 2 | 2014-02-21 |
+----+---------+------------+
I want the second highest read_date for particular 'conn_id's i.e. I want a group by on conn_id. Please help me figure this out.
Here's a solution for a particular conn_id :
select max (read_date) from my_table
where conn_id=1
and read_date<(
select max (read_date) from my_table
where conn_id=1
)
If you want to get it for all conn_id using group by, do this:
select t.conn_id, (select max(i.read_date) from my_table i
where i.conn_id=t.conn_id and i.read_date<max(t.read_date))
from my_table t group by conn_id;
Following answer should work in MSSQL :
select id,conn_id,read_date from (
select *,ROW_NUMBER() over(Partition by conn_id order by read_date desc) as RN
from my_table
)
where RN =2
There is an intresting article on use of rank functions in MySQL here : ROW_NUMBER() in MySQL
If your table design as ID - date matching (ie a big id always a big date), you can group by id, otherwise do the following:
$sql_max = '(select conn_id, max(read_date) max_date from tab group by 1) as tab_max';
$sql_max2 = "(select tab.conn_id,max(tab.read_date) max_date2 from tab, $sql_max
where tab.conn_id = tab_max.conn_id and tab.read_date < tab_max.max_date
group by 1) as tab_max2";
$sql = "select tab.* from tab, $sql_max2
where tab.conn_id = tab_max2.conn_id and tab.read_date = tab_max2.max_date2";