I have a web site that collects high scores for a game - the sidebar shows the latest 10 scores (not necessarily the highest, just the latest 10). However, since a user can play multiple games quickly, they can dominate the latest 10 list. How can I write an SQL squery to show the last 10 scores but limit it to one per user?
SELECT username, max(score)
FROM Sometable
GROUP BY username
ORDER BY Max(score) DESC
and from that, select the top X depending on your db platform. select top(10) in ms-sql 2005+
edit
sorry, I see that you want things ordered by date.
Here's a working query with ms-sql 2005.
;
WITH CTE AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY username ORDER BY dateadded DESC) AS 'RowNo',
username, score, dateadded FROM SomeTable
)
SELECT username, score, dateadded FROM CTE
WHERE RowNo = 1
Group by user... and either select the Max(Score), Max([Submission Date]) or whatever.
In SQL Server, you could use the RANK() OVER() with appropriate PARTITION and GROUP BY, but what platform are you using?
In the interest of providing another point of view you could just add a field "max score" to your user table and then use a simple query with an order by to get the top 10.
Your update will need to check if the new score if higher then the current max score.
It does have the advantage of querying a table that will most probably have less rows then your score table.
Anyway, just another option to consider.
SELECT s2.*
FROM
(SELECT user_id, MAX(action_time) AS max_time
FROM scores s1 GROUP_BY user_id
ORDER BY MAX(action_time) DESC LIMIT 10)s1
INNER JOIN scores s2 ON (s2.user_id = s1.user_id AND s2.action_time = s1.max_time)
This is Mysql syntax, for SQL server you need to use SELECT TOP 10 ... instead of LIMIT 10.
Here is a working example that I built on SQL Server 2008
WITH MyTable AS
(
SELECT 1 as UserId, 10 as Score UNION ALL
SELECT 1 as UserId, 11 as Score UNION ALL
SELECT 1 as UserId, 12 as Score UNION ALL
SELECT 2 as UserId, 13 as Score UNION ALL
SELECT 2 as UserId, 14 as Score UNION ALL
SELECT 3 as UserId, 15 as Score UNION ALL
SELECT 3 as UserId, 16 as Score UNION ALL
SELECT 3 as UserId, 17 as Score UNION ALL
SELECT 4 as UserId, 18 as Score UNION ALL
SELECT 4 as UserId, 19 as Score UNION ALL
SELECT 5 as UserId, 20 as Score UNION ALL
SELECT 6 as UserId, 21 as Score UNION ALL
SELECT 7 as UserId, 22 as Score UNION ALL
SELECT 7 as UserId, 23 as Score UNION ALL
SELECT 7 as UserId, 24 as Score UNION ALL
SELECT 8 as UserId, 25 as Score UNION ALL
SELECT 8 as UserId, 26 as Score UNION ALL
SELECT 9 as UserId, 26 as Score UNION ALL
SELECT 10 as UserId, 20 as Score
),
MyTableNew AS
(
SELECT Row_Number() OVER (Order By UserId) Sequence, *
FROM MyTable
),
RankedUsers AS
(
SELECT *, Row_Number() OVER (Partition By UserId ORDER BY Sequence DESC) Ranks
FROM MyTableNew
)
SELECT *
FROM MyTableNew
WHERE Sequence IN
(
SELECT TOP 5 Sequence
FROM RankedUsers
WHERE Ranks = 1
ORDER BY Sequence DESC
)
Related
I have 3 different recommendation model that gives me the output in three different tables.
Recommendation 1 : In a ideal situation, I want to take top 2 recommendation per user from this table ordered by ProductRecommendation ascending.
Recommendation 2 : In a ideal situation, I want to take top 3 recommendation per user from this table based on top score.
Recommendation 3 : In a ideal situation, take remaining recommendation from this table to add up to 5 recommendation per user
In the end, I want to see a final output which is a merge of all the recommendation into one which would look like this.
I want to take top 5 recommendation across 3 different tables. FYI, not all the user id can appear in all the tables. Ideally, I want to take TOP 2 from recommendation 1, TOP 3 from recommendation 2. Recommendation 3 is just there so that if there are not enough recommendation from the first two table then recommendation 3 will compensate so at the end I will get 5 results per userID. I don't need to refer to recommendation 3 if I can get 5 recommendation (2 from recommendation 1 and 3 from recommendation 2). when the recommendation 1 has < 2 recommendations per user then I want to get the remaining of the recommendation from recommendation 2. For example, when there is 1 recommendation in Recommendtiaon1 then get 4 recommendation from Recommendation2. Alternatively, if there are 0 recommendation in Recommendation1 then get 5 recommendation from Recommendation2. If Recommednation1 and Recommendation2 doesn't add up to 5 that's when I need to refer to recommendation3. I need to do this in big query SQL. Can you please help?
Thanks for your help.
Consider below approach
with output1 as (
select *, null as Score, row_number() over win pos
from Recommendation1
where true
qualify row_number() over win <= 2
window win as (partition by UserID order by ProductRecommendation)
), output2 as (
select *, 2 + row_number() over win pos
from Recommendation2
where not (UserID, ProductRecommendation) in (select as struct UserID, ProductRecommendation from output1)
qualify row_number() over win <= 5
window win as (partition by UserID order by Score desc)
), output3 as (
select *, 7 + row_number() over win pos
from Recommendation3
where not (UserID, ProductRecommendation) in (select as struct UserID, ProductRecommendation from output1)
and not (UserID, ProductRecommendation) in (select as struct UserID, ProductRecommendation from output2)
qualify row_number() over win <= 5
window win as (partition by UserID order by Score desc)
)
select * except(pos) from (
select * from output1 union all
select * from output2 union all
select * from output3
)
where true
qualify row_number() over win <=5
window win as (partition by UserID order by pos)
# order by UserID, pos
if applied to sample data in your question - the output is
Your description is a bit unclear. The following takes 2 rows from the first table for each user, 3 from the second, and additional rows from the third. The outer query then ensures that there are 5 rows (if available) for each user:
select r.*
from ((select userid, recommendation, 1 as which
from recommendation1
where 1=1
qualify row_number() over (partition by userid order by recommendation) <= 2
) union all
(select userid, recommendation, 2 as which
from recommendation2
where 1=1
qualify row_number() over (partition by userid order by score desc) <= 3
) union all
(select userid, recommendation, 3 as which
from recommendation3
)
) r
where 1=1
qualify row_number() over (partition by userid order by which) <= 5;
I have following 'Scores' table which has score of players in a specific year
Sid Name Score Year
1 John 500 2016
2 Kim 900 2015
3 Ren 300 2016
4 John 600 2015
5 Kim 200 2016
6 Ren 200 2016
Find the player who has scored maximum runs in 2016
I can find this using the below query
Select Name
from
( select Name
, sum(Score) as sumScore
from Scores
where year=2016
group
by Name
) sub
order
by sumScore desc
limit 1;
Ouput:
Ren
How can i find the same without using order by?
I tried below but it doesn't work as it can't refer sub in 2nd where clause and complains relation sub doesn't exist
select Name from(select Name,sum(Score) as sumScore from Scores
where year=2016 group by Name)sub where sumScore=(select max(sumScore) from sub)
One simple method uses window functions:
select s.*
from (select s.*, max(s.score) over (partition by year) as max_score
from scores s
where year = 2016
) s
where score = max_score;
You can try using correlated subquery
DEMO
select * from tablename a where score in
(select max(score) from tablename b where a.year=b.year and b.year=2016)
and a.year=2016
OR you can use window function row_number() like below
select * from
(
select *,row_number() over(partition by yr order by score desc) as rn from cte1
)a where rn=1 and yr=2016
OUTPUT:
id name score yr
1 John 500 2016
SELECT Scores.Name, SUM(Scores.Score)
FROM (
select Name,sum(Score) as sumScore, Years
from Scores
where Years=2016
group by Name, Years
)sub INNER JOIN Scores ON sub.Name = Scores.Name
GROUP BY Scores.Name
HAVING SUM(Scores.Score) = MAX(sub.sumScore)
You could also use common table expression in combination with dense rank
with cte as (
select *,
DENSE_RANK() OVER(ORDER BY score desc, year) rank
from demo
where year = 2016
)
select *
from cte
where rank = 1
Demo
Edit to get players with max score of 2016 you can tweak above query as
with cte as (
select name,year ,
DENSE_RANK() OVER(ORDER BY sum(score) desc, year) rank
from demo
where year = 2016
group by name,year
)
select *
from cte
where rank = 1
Demo
I need to select the top score of all combined attempts by a player and I need to use a WITH clause.
create table scorecard(
id integer primary key,
player_name varchar(20));
create table scores(
id integer references scorecard,
attempt integer,
score numeric
primary key(id, attempt));
Sample Data for scorecard:
id player_name
1 Bob
2 Steve
3 Joe
4 Rob
Sample data for scores:
id attempt score
1 1 50
1 2 45
2 1 10
2 2 20
3 1 40
3 2 35
4 1 0
4 2 95
The results would simply look like this:
player_name
Bob
Rob
But would only be Bob if Rob had scored less than 95 total. I've gotten so far as to have the name and the total scores that they got in two columns using this:
select scorecard.player_name, sum(scores.score)
from scorecard
left join scores
on scorecard.id= scores.id
group by scorecard.name
order by sum(scores.score) desc;
But how do I just get the names of the highest score (or scores if tied).
And remember, it should be using a WITH clause.
Who ever told you to "use a WITH clause" was missing a more efficient solution. To just get the (possibly multiple) winners:
SELECT c.player_name
FROM scorecard c
JOIN (
SELECT id, rank() OVER (ORDER BY sum(score) DESC) AS rnk
FROM scores
GROUP BY 1
) s USING (id)
WHERE s.rnk = 1;
A plain subquery is typically faster than a CTE. If you must use a WITH clause:
WITH top_score AS (
SELECT id, rank() OVER (ORDER BY sum(score) DESC) AS rnk
FROM scores
GROUP BY 1
)
SELECT c.player_name
FROM scorecard c
JOIN top_score s USING (id)
WHERE s.rnk = 1;
SQL Fiddle.
You could add a final ORDER BY c.player_name to get a stable sort order, but that's not requested.
The key feature of the query is that you can run a window function like rank() over the result of an aggregate function. Related:
Postgres window function and group by exception
Get the distinct sum of a joined table column
Can try something like follows.
With (SELECT id, sum(score) as sum_scores
FROM scores
group by id) as sumScoresTable,
With (SELECT max(score) as max_scores
FROM scores
group by id) as maxScoresTable
select player_name
FROM scorecard
WHERE scorecard.id in (SELECT sumScoresTable.id
from sumScoresTable
where sumScoresTable.score = (select maxScoresTable.score from maxScoresTable)
Try this code:
WITH CTE AS (
SELECT ID, RANK() OVER(ORDER BY SumScore DESC) As R
FROM (
SELECT ID, SUM(score) AS SumScore
FROM scores
GROUP BY ID )
)
SELECT player_name
FROM scorecard
WHERE ID IN (SELECT ID FROM CTE WHERE R = 1)
Suppose I get the following result for a GROUP BY query on a table:
Name Count(*)
Apple 6
Mango 3
Grape 8
Pomegranate 1
Strawberry 13
How can I get the top three elements listed and the rest of the elements summed up in some name like 'Others'. something like follows.
Name Count(*)
Strawberry 13
Grape 8
Apple 6
Others 4
This is to be done in Oracle. Seaching yields results using TOP which is not available in Oracle.
Here's a complete solution including SQL fiddle:
WITH fruit_summary AS (
SELECT fruit, cnt,
RANK() OVER (ORDER BY cnt DESC) AS cnt_rank
FROM (
SELECT fruit, count(*) AS cnt
FROM fruit_table
GROUP BY fruit
)
)
SELECT fruit, cnt
FROM (
SELECT fruit, cnt, cnt AS val
FROM fruit_summary
WHERE cnt_rank <= 3
UNION ALL
SELECT 'Others', SUM(cnt), -1
FROM fruit_summary
WHERE cnt_rank > 3
)
ORDER BY val DESC
Note that the query can return more than 4 (3 + 1) rows if you have several summary lines with the same count in the top 3.
The WITH clause groups the source table by fruit and assigns a rank to each row. The resulting intermediate table is then used to display the top 3 as well as to summarize the resulting one.
This solution should work for you. You can actually use aggregates in window functions:
WITH f1 AS (
SELECT fruit_name, COUNT(*) AS fruit_cnt, RANK() OVER ( ORDER BY COUNT(*) DESC ) AS fruit_rank
FROM fruits
GROUP BY fruit_name
)
SELECT fruit_name, fruit_cnt, fruit_rank
FROM f1
WHERE fruit_rank <= 3
UNION
SELECT 'Others' AS fruit_name, SUM(fruit_cnt) AS fruit_cnt, MAX(fruit_rank) AS fruit_rank
FROM f1
WHERE fruit_rank > 3
ORDER BY fruit_rank
Please see SQL Fiddle Demo here.
I want to skip first 5 records and then select 10 records
I have a column email in table user. Here I am trying to select top 10 unique rows from table user using this query
select DISTINCT TOP 10 email from user
Now I am trying to select top 10 unique rows from table skipping the first 5 records
select DISTINCT SKIP 5 TOP 10 email from user
which is not done and return error.. can anyone help me
SELECT A.NAME FROM
(SELECT distinct RANK() OVER(ORDER BY NAME) RNK,NAME FROM USERS) A
WHERE A.RNK>4 AND A.RNK<16
Using LIMIT will not guarantee you that you will get top rows with proper order.
If you use ANALYTIC functions, it will give you proper results.
SQL_LIVE_DEMO
Here is one way to do it. I like to use Common Table Expressions for some things like this because it makes the query easy to understand, although this isn't particularly complicated.
WITH CTE AS
(
Select Distinct Email From User
)
,
CTE1 AS
(
Select Email, ROW_NUMBER() over (ORDER BY Email) AS RowNumber
From CTE
)
Select Top 10 * From CTE1 Where RowNumber > 5
with t2 as
(
select t1.*,
row_number() over (order by id) rn
from
(select email, max(id) as id from [user] group by email) as t1
)
select * from t2 where rn between 5 and 10
How about this:
SELECT *
FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY email) AS row
FROM user ) a
WHERE row > 5 and row <= 10
I think you are using SKIP incorrectly, it should be part of the ORDER BY clause.
SELECT DISTINCT TOP(10) Email FROM TableName WHERE Email not in (SELECT TOP(5) Email From TableName)
You can try this code, in this query fetch distinct 10 email ids skip 5 records as you say in this question.