MySQL select top ten records with no duplicate uid - mysql

I have the following table (user_record) with millions of rows like this:
no uid s
================
1 a 999
2 b 899
3 c 1234
4 a 1322
5 b 933
-----------------
The uid can be duplicate .What I need is to show the top ten records(need inclued uid and s) with no duplicate uid order by s (desc). I can do this by two steps in the following SQL statements:
SELECT distinct(uid) FROM user_record ORDER BY s DESC LIMIT 10
SELECT uid,s FROM user_record WHERE uid IN(Just Results)
I just wana know is there a bit more efficient way in one statement?
Any help is greatly appreciated.
ps:I also have following the SQL statement:
select * from(select uid,s from user_record order by s desc) as tb group by tb.uid order by tb.s desc limit 10
but it's slow

The simpliest would be by using MAX() to get the highest s for every uid and sorted it based on the highest s.
SELECT uid, MAX(s) max_s
FROM TableName
GROUP BY uid
ORDER BY max_s DESC
LIMIT 10
SQLFiddle Demo
The disadvantage of the query above is that it doesn't handles duplicates if for instance there are multiple uid that have the same s and turn out to be the highest value. If you want to get the highest value s with duplicate, you can do by calculating it on the subquery and joining the result on the original table.
SELECT a.*
FROM tableName a
INNER JOIN
(
SELECT DISTINCT s
FROM TableName
ORDER BY s DESC
LIMIT 10
) b ON a.s = b.s
ORDER BY s DESC

Related

MySQL select where not in another returned data from sql statement

I have this problem where I want to first select 8 elements from a mysql database ordering by id DESC.
Then I want to select another group of results (8 items), this time order by date DESC but the results here I want to ensure that they are not already on the fisrt query the one for ordering by id.
The data is in the same table just with different columns like id,name,date,.
So far I have tried writing different queries to get the data but the data contains some similar items of which that is what I don't want.
Here are the queries I have written;
this returns 8 items sorted by id DESC
SELECT name FROM person order by id DESC LIMIT 8;
this returns 8 items also but sorted by date DESC
SELECT name FROM person order by date DESC LIMIT 8;
the returned data contain duplicate items!
You could use a nested query, first select the first 8 id's, then select the first 8 records ordered by date, excluding those id's:
SELECT name FROM person
WHERE id NOT IN
(SELECT id FROM person order by id DESC LIMIT 8) AS exc
ORDER BY date DESC LIMIT 8
The first query should return the primary key for the table. If name is the key then so be it, but probably that id field is the better choice.
Then we can write the query like this:
SELECT p.name
FROM Person p
WHERE NOT EXISTS (
SELECT 1
FROM (SELECT id FROM Person ORDER BY id DESC LIMIT 8) p0
WHERE p0.id = p.id
)
ORDER BY p.date DESC
LIMIT 8;
We could also use an exclusion join which is usually slower, but in this case reduces one level of nesting so it might do better:
SELECT p.name
FROM Person p
LEFT JOIN (
SELECT id
FROM Person
ORDER BY id DESC
LIMIT 8
) p0 ON p0.id = p.id
WHERE p0.id is null
ORDER BY p.date DESC
LIMIT 8;
One other thing to keep in mind is MySQL is strict about what kinds of subquery can use the LIMIT keyword. Specifically, you need it to be a derived table. I know the exclusion join option should qualify, but I'm less sure of the NOT EXISTS() option.
Why not generate both resultsets with a single query? We can combine window functions, order by, and limit to generate a resultset containing the top 8 rows per id and the top 8 rows per date, while avoiding duplicates:
select *
from (
select p.*,
row_number() over(order by id desc) rn_id,
row_number() over(order by date desc) rn_dt
from person p
) p
order by case when rn_id <= 8 then rn_id else 9 end, rn_dt
limit 16
In the subquery, the window functions enumerate records by descending id and date. The outer query performs a conditional sort that puts the top 8 id first, and orders the rest of the records by descending date. All that is left to do is retain the top 16 results from the query. You don't need to worry about duplicates since the table is scanned only once.
Here is a small test case:
id
date
1
2022-11-11
2
2022-11-09
3
2022-11-05
4
2022-11-06
5
2022-11-07
6
2022-11-08
7
2022-11-10
For this sample data, and given a target of 3 + 3 records (instead of 8 + 8 in our code), the query returns:
id
date
rn_id
rn_dt
7
2022-11-10
1
2
6
2022-11-08
2
4
5
2022-11-07
3
5
1
2022-11-11
7
1
2
2022-11-09
6
3
4
2022-11-06
4
6
Typically, id 7, which has both the greatest id the second latest date, shows up in the first part of the resultset (the top 3 rows are sorted by descending id), but is not repeated in the second part.
Demo on DB fiddle

want to get total count of records in a table using COUNT(*) in MySQL

I have a mysql query which will return all the details from table along with i need max_row count i.e total no of rows in a table using COUNT(*) in a single select query without using cross join.
Note: MySQL version is earlier version of 8
Query :
SELECT * FROM tablename ORDER BY column name DESC LIMIT 0,10;
The total count of a table is simple, when you want to add it to every row.
SELECT
*
,(SELECT COUNT(*) FROM tablename ) count1
FROM tablename
ORDER BY column name
DESC LIMIT 0,10;,

MySQL grouping with detail

I have a table that looks like this...
user_id, match_id, points_won
1 14 10
1 8 12
1 12 80
2 8 10
3 14 20
3 2 25
I want to write a MYSQL script that pulls back the most points a user has won in a single match and includes the match_id in the results - in other words...
user_id, match_id, max_points_won
1 12 80
2 8 10
3 2 25
Of course if I didn't need the match_id I could just do...
select user_id, max(points_won)
from table
group by user_id
But as soon as I add match_id to the "select" and "group by" I have a row for every match, and if I only add the match_id to the "select" (and not the "group by") then it won't correctly relate to the points_won.
Ideally I don't want to do the following either because it doesn't feel particularly safe (e.g. if the user has won the same amount of points on multiple matches)...
SELECT t.user_id, max(t.points_won) max_points_won
, (select t2.match_id
from table t2
where t2.user_id = t.user_id
and t2.points_won = max_points_won) as 'match_of_points_maximum'
FROM table t
GROUP BY t.user_id
Are there any more elegant options for this problem?
This is harder than it needs to be in MySQL. One method is a bit of a hack but it works in most circumstances. That is the group_concat()/substring_index() trick:
select user_id, max(points_won),
substring_index(group_concat(match_id order by points_won desc), ',', 1)
from table
group by user_id;
The group_concat() concatenates together all the match_ids, ordered by the points descending. The substring_index() then takes the first one.
Two important caveats:
The resulting expression has a type of string, regardless of the internal type.
The group_concat() uses an internal buffer, whose length -- by default -- is 1,024 characters. This default length can be changed.
You can use the query:
select user_id, max(points_won)
from table
group by user_id
as a derived table. Joining this to the original table gets you what you want:
select t1.user_id, t1.match_id, t2.max_points_won
from table as t1
join (
select user_id, max(points_won) as max_points_won
from table
group by user_id
) as t2 on t1.user_id = t2.user_id and t1.points_won = t2.max_points_won
I think you can optimize your query by add limit 1 in the inner query.
SELECT t.user_id, max(t.points_won) max_points_won
, (select t2.match_id
from table t2
where t2.user_id = t.user_id
and t2.points_won = max_points_won limit 1) as 'match_of_points_maximum'
FROM table t
GROUP BY t.user_id
EDIT : only for postgresql, sql-server, oracle
You could use row_number :
SELECT USER_ID, MATCH_ID, POINTS_WON
FROM
(
SELECT user_id, match_id, points_won, row_number() over (partition by user_id order by points_won desc) rn
from table
) q
where q.rn = 1
For a similar function, have a look at Gordon Linoff's answer or at this article.
In your example, you partition your set of result per user then you order by points_won desc to obtain highest winning point first.

Select sum of top three scores for each user

I am having trouble writing a query for the following problem. I have tried some existing queries but cannot get the results I need.
I have a results table like this:
userid score timestamp
1 50 5000
1 100 5000
1 400 5000
1 500 5000
2 100 5000
3 1000 4000
The expected output of the query is like this:
userid score
3 1000
1 1000
2 100
I want to select a top list where I have n best scores summed for each user and if there is a draw the user with the lowest timestamp is highest. I really tried to look at all old posts but could not find one that helped me.
Here is what I have tried:
SELECT sum(score) FROM (
SELECT score
FROM results
WHERE userid=1 ORDER BY score DESC LIMIT 3
) as subquery
This gives me the results for one user, but I would like to have one query that fetches all in order.
This is a pretty typical greatest-n-per-group problem. When I see those, I usually use a correlated subquery like this:
SELECT *
FROM myTable m
WHERE(
SELECT COUNT(*)
FROM myTable mT
WHERE mT.userId = m.userId AND mT.score >= m.score) <= 3;
This is not the whole solution, as it only gives you the top three scores for each user in its own row. To get the total, you can use SUM() wrapped around that subquery like this:
SELECT userId, SUM(score) AS totalScore
FROM(
SELECT userId, score
FROM myTable m
WHERE(
SELECT COUNT(*)
FROM myTable mT
WHERE mT.userId = m.userId AND mT.score >= m.score) <= 3) tmp
GROUP BY userId;
Here is an SQL Fiddle example.
EDIT
Regarding the ordering (which I forgot the first time through), you can just order by totalScore in descending order, and then by MIN(timestamp) in ascending order so that users with the lowest timestamp appears first in the list. Here is the updated query:
SELECT userId, SUM(score) AS totalScore
FROM(
SELECT userId, score, timeCol
FROM myTable m
WHERE(
SELECT COUNT(*)
FROM myTable mT
WHERE mT.userId = m.userId AND mT.score >= m.score) <= 3) tmp
GROUP BY userId
ORDER BY totalScore DESC, MIN(timeCol) ASC;
and here is an updated Fiddle link.
EDIT 2
As JPW pointed out in the comments, this query will not work if the user has the same score for multiple questions. To settle this, you can add an additional condition inside the subquery to order the users three rows by timestamp as well, like this:
SELECT userId, SUM(score) AS totalScore
FROM(
SELECT userId, score, timeCol
FROM myTable m
WHERE(
SELECT COUNT(*)
FROM myTable mT
WHERE mT.userId = m.userId AND mT.score >= m.score
AND mT.timeCol <= m.timeCol) <= 3) tmp
GROUP BY userId
ORDER BY totalScore DESC, MIN(timeCol) ASC;
I am still working on a solution to find out how to handle the scenario where the userid, score, and timestamp are all the same. In that case, you will have to find another tiebreaker. Perhaps you have a primary key column, and you can choose to take a higher/lower primary key?
Query for selecting top three scores from table.
SELECT score FROM result
GROUP BY id
ORDER BY score DESC
LIMIT 3;
Can you please try this?
SELECT score FROM result GROUP BY id ORDER BY score DESC, timestamp ASC LIMIT 3;
if 2 users have same score then it will set order depends on time.
You can use a subquery
SELECT r.userid,
( SELECT sum(r2.score)
FROM results r2
WHERE r2.userid = r.userid
ORDER BY score DESC
LIMIT 3
) as sub
FROM result r
GROUP BY r.userid
ORDER BY sub desc
You should do it like this
SELECT SUM(score) as total, min(timestamp) as first, userid FROM scores
GROUP BY userid
ORDER BY total DESC, first ASC
This is way more efficient than sub queries. If you want to extract more fields than userid, then you need to add them to the group by.
This will of cause not limit the number of scores pr user, which indeed seems to require a subquery to solve.

How to get the end of an Ascending list of records with a limit?

I'm fighting a bit with a query I'm building. Let's say I've got a DB table like this:
id | some_string
----------------
1 | 'lala'
2 | 'jeje'
3 | 'poopoo'
4 | 'wicked wicked'
I now want to get the last three records (2, 3, and 4) ordered ascending by key. I tried this:
SELECT * FROM tableName LIMIT 3 ORDER BY id ASC
This gets me the first three records, instead of the last three. I can of course also use the query below, which gets me the correct records, but then I don't get them in Ascending order:
SELECT * FROM tableName LIMIT 3 ORDER BY id DESC
Does anybody know how I can get the last three records in an ascending order? All tips are welcome!
select * from (
select * from table_name order by id desc limit 3
) last_3_rows
order by id
Sort on the resulting result set ie. do a select * from (<your query here>) order by id
This is a query inside another query. that reorders your query.(SQL - How to reorder a select query that uses the limit constraint)
select * FROM (SELECT * FROM tableName LIMIT 3 ORDER BY id DESC) AN_UNUSUAL_NAME ORDER BY id ASC