Fetch one row per account id from list, part 2 - sql

Not sure how to ask a followup on SO, but this is in reference to an earlier question:
Fetch one row per account id from list
The query I'm working with is:
SELECT *
FROM scores s1
WHERE accountid NOT IN (SELECT accountid FROM scores s2 WHERE s1.score < s2.score)
ORDER BY score DESC
This selects the top scores, and limits results to one row per accountid; their top score.
The last hurdle is that this query is returning multiple rows for accountids that have multiple occurrences of their top score. So if accountid 17 has scores of 40, 75, 30, 75 the query returns both rows with scores of 75.
Can anyone modify this query (or provide a better one) to fix this case, and truly limit it to one row per account id?
Thanks again!

If you're only interested in the accountid and the score, then you can use the simple GROUP BY query given by Paul above.
SELECT accountid, MAX(score)
FROM scores
GROUP BY accountid;
If you need other attributes from the scores table, then you can get other attributes from the row with a query like the following:
SELECT s1.*
FROM scores AS s1
LEFT OUTER JOIN scores AS s2 ON (s1.accountid = s2.accountid
AND s1.score < s2.score)
WHERE s2.accountid IS NULL;
But this still gives multiple rows, in your example where a given accountid has two scores matching its maximum value. To further reduce the result set to a single row, for example the row with the latest gamedate, try this:
SELECT s1.*
FROM scores AS s1
LEFT OUTER JOIN scores AS s2 ON (s1.accountid = s2.accountid
AND s1.score < s2.score)
LEFT OUTER JOIN scores AS s3 ON (s1.accountid = s3.accountid
AND s1.score = s3.score AND s1.gamedate < s3.gamedate)
WHERE s2.accountid IS NULL
AND s3.accountid IS NULL;

select accountid, max(score) from scores group by accountid;

If your RDBMS supports them, then an analytic function would be a good approach particularly if you need all the columns of the row.
select ...
from (
select accountid,
score,
...
row_number() over
(partition by accountid
order by score desc) score_rank
from scores)
where score_rank = 1;
The row returned is indeterminate in the case you describe, but you can easily modify the analytic function, for example by ordering on (score desc, test_date desc) to get the more recent of two matching high scores.
Other analytic functions based on rank will achieve a similar purpose.
If you don't mind duplicates then the following would probably me more efficient than your current method:
select ...
from (
select accountid,
score,
...
max(score) over (partition by accountid) max_score
from scores)
where score = max_score;

If you are selecting a subset of columns then you can use the DISTINCT keyword to filter results.
SELECT DISTINCT UserID, score
FROM scores s1
WHERE accountid NOT IN (SELECT accountid FROM scores s2 WHERE s1.score < s2.score)
ORDER BY score DESC

Does your database support distinct? As in select distinct x from y?

This solutions works in MS SQL, giving you the whole row.
SELECT *
FROM scores
WHERE scoreid in
(
SELECT max(scoreid)
FROM scores as s2
JOIN
(
SELECT max(score) as maxscore, accountid
FROM scores s1
GROUP BY accountid
) sub ON s2.score = sub.maxscore AND s2.accountid = s1.accountid
GROUP BY s2.score, s2.accountid
)

Related

How to Rank Based on Multiple Columns

I'm trying to score people in Microsoft Access based on the count they have for a particular category.
There are 7 possible categories a person can have against them, and I want to assigned each person a score from 1-7, with 1 being assigned to the highest scoring category, 7 being the lowest. They might not have an answer for every category, in which case that category can be ignored.
The aim would be to have an output result as shown in this image:
I've tried a few different things, including partition over and joins, but none have worked. To be honest I think I'm way off the mark with the queries I've been trying. I've tried to write the code in SQL from scratch, and used query builder.
Any help is really appreciated!
As you for an email can have duplicated counts, you will need two subqueries for this:
SELECT
Score.email,
Score.category,
Score.[Count],
(Select Count(*) From Score As T Where
T.email = Score.email And
T.[Count] >= Score.[Count])-
(Select Count(*) From Score As S Where
S.email = Score.email And
S.[Count] = Score.[Count] And
S.category > Score.category) AS Rank
FROM
Score
ORDER BY
Score.email,
Score.[Count] DESC,
Score.category;
For categories with equal Count values for the same email, the following will rank the records alphabetically descending by Category name (since this is what is shown in your example):
select t.email, t.category, t.count,
(
select count(*) from YourTable u
where t.email = u.email and
((t.count = u.count and t.category <= u.category) or t.count < u.count)
) as rank
from YourTable t
order by t.email, t.count desc, t.category desc
Change both references of YourTable to the name of your table.

TSQL - Sum of Top 3 records of multiple teams

I am trying to generate a TSQL query that will take the top 3 scores (out of about 50) for a group of teams, sum the total of just those 3 scores and give me a result set that has just the name of the team, and that total score ordered by the score descending. I'm pretty sure it is a nested query - but for the life of me can't get it to work!
Here are the specifics, there is only 1 table involved....
table = comp_lineup (this table holds a separate record for each athlete in a match)
* athlete
* team
* score
There are many athletes to a match - each one belongs to a team.
Example:
id athlete team score<br>
1 1 1 24<br>
2 2 1 23<br>
3 3 2 21<br>
4 4 2 25<br>
5 5 1 20<br>
Thank You!
It is indeed a subquery, which I often put in a CTE instead just for clarity. The trick is the use of the rank() function.
;with RankedScores as (
select
id,
athlete,
team,
score,
rank() over (partition by team order by score desc) ScoreRank
from
#scores
)
select
Team,
sum(Score) TotalScore
from
RankedScores
where
ScoreRank <= 3
group by
team
order by
TotalScore desc
To get the top n value for every group of data a query template is
Select group_value, sum(value) total_value
From mytable ext
Where id in (Select top *n* id
From mytable sub
Where ext.group_value = sub.group_value
Order By value desc)
Group By group_value
The subquery retrieve only the ID of the valid data for the current group_value, the connection between the two dataset is the Where ext.group_value = sub.group_value part, the WHERE in the main query is used to mask every other ID, like a cursor.
For the specific question the template became
Select team, sum(score) total_score
From mytable ext
Where id in (Select top 3 id
From mytable sub
Where ext.team = sub.team
Order By score desc)
Group By team
Order By sum(score) Desc
with the added Order By in the main query for the descending total score

SQL Finding maximum value without top command

Let's say I have a bases with a table:
-courses (key: name [ofthecourse], other attributes: year in which the course takes place)
I want to complete a query looking for an answer to the question:
On which year of study there is a maximum number of courses?
Normally, the query would be:
SELECT TOP 1 STUDYEAR
FROM COURSES
GROUP BY STUDYEAR
ORDER BY COUNT(CNO) DESC;
But my question is, which query could complete this without using the TOP 1 phrase?
You can use an inner query to get the maximum count. The only difference is though that it can return more than one record if they have the same count.
SELECT STUDYEAR
FROM COURSES
GROUP BY STUDYEAR
HAVING COUNT(CNO) = (SELECT MAX(CNOCount) FROM
(SELECT COUNT(CNO) CNOCount
FROM COURSES
GROUP BY STUDYEAR) X)
Another version with only one inner query:
SELECT STUDYEAR
FROM
(SELECT STUDYEAR, ROW_NUMBER() OVER (ORDER BY COUNT(CNO) DESC) RowNumber
FROM COURSES
GROUP BY STUDYEAR) X
WHERE RowNumber = 1

How can I get the rank of rows relative to total number of rows based on a field?

I have a scores table that has two fields:
user_id
score
I'm fetching specific rows that match a list of user_id's. How can I determine a rank for each row relative to the total number of rows, based on score? The rows in the result set are not necessarily sequential (the scores will vary widely from one row to the next). I'm not sure if this matters, but user_id is a unique field.
Edit
#Greelmo
I'm already ordering the rows. If I fetch 15 rows, I don't want the rank to be 1-15. I need it to be the position of that row compared against the entire table by the score property. So if I have 200 rows, one row's rank may be 3 and another may be 179 (these are arbitrary #'s for example only).
Edit 2
I'm having some luck with this query, but I actually want to avoid ties
SELECT
s.score
, s.created_at
, u.name
, u.location
, u.icon_id
, u.photo
, (SELECT COUNT(*) + 1 FROM scores WHERE score > s.score) AS rank
FROM
scores s
LEFT JOIN
users u ON u.uID = s.user_id
ORDER BY
s.score DESC
, s.created_at DESC
LIMIT 15
If two or more rows have the same score, I want the latest one (or earliest - I don't care) to be ranked higher. I tried modifying the subquery with AND id > s.id but that ended up giving me an unexpected result set and different ties.
Select S.score, S.created_at, U.name
, U.location, U.icon_id, U.photo
, (Select Count(*) + 1
From scores S2
Where S2.score > S.score
Or (S2.score = S.Score And S2.created_at > S.created_at)
) AS rank
From scores S
Left Join users U
On U.uID = S.user_id
Order By S.score DESC, S.created_at DESC
LIMIT 15
Of course, if it is possible for two scores to have the same created_at date, then you will still get ties and need to determine a third tie-breaker.
You could order the data in your query.
SELECT user_id, score FROM table ORDER BY score ASC;
This will give you your data from lowest score to highest.
If this doesn't answer your question, then I don't understand what you're asking.
EDIT
To get the position, while iterating through the database results, just keep a counter.

Fetch one row per account id from list

I have a table with game scores, allowing multiple rows per account id: scores (id, score, accountid). I want a list of the top 10 scorer ids and their scores.
Can you provide an sql statement to select the top 10 scores, but only one score per account id?
Thanks!
select username, max(score) from usertable group by username order by max(score) desc limit 10;
First limit the selection to the highest score for each account id.
Then take the top ten scores.
SELECT TOP 10 AccountId, Score
FROM Scores s1
WHERE AccountId NOT IN
(SELECT AccountId s2 FROM Scores
WHERE s1.AccountId = s2.AccountId and s1.Score > s2.Score)
ORDER BY Score DESC
Try this:
select top 10 username,
max(score)
from usertable
group by username
order by max(score) desc
PostgreSQL has the DISTINCT ON clause, that works this way:
SELECT DISTINCT ON (accountid) id, score, accountid
FROM scoretable
ORDER BY score DESC
LIMIT 10;
I don't think it's standard SQL though, so expect other databases to do it differently.
SELECT accountid, MAX(score) as top_score
FROM Scores
GROUP BY accountid,
ORDER BY top_score DESC
LIMIT 0, 10
That should work fine in mysql. It's possible you may need to use 'ORDER BY MAX(score) DESC' instead of that order by - I don't have my SQL reference on hand.
I believe that PostgreSQL (at least 8.3) will require that the DISTINCT ON expressions must match initial ORDER BY expressions. I.E. you can't use DISTINCT ON (accountid) when you have ORDER BY score DESC. To fix this, add it into the ORDER BY:
SELECT DISTINCT ON (accountid) *
FROM scoretable
ORDER BY accountid, score DESC
LIMIT 10;
Using this method allows you to select all the columns in a table. It will only return 1 row per accountid even if there are duplicate 'max' values for score.
This was useful for me, as I was not finding the maximum score (which is easy to do with the max() function) but for the most recent time a score was entered for an accountid.