Rank Score Rows Returned in SQL
Here's a way to how to rank score rows returned in SQL.
Let's say we have many instances of a thing in our database. In this case, let's say we have many books. We want to create a list of books where we recommend to our friends the best books to read in each genre. If we have read the book, we recommend it first of all. If we haven't read the book, we base our recommendation on the highest rating, as a fallback.
There are two tricky bits. One is the fallback on the recommendation priority (ie, first, if we've read it, second by rating). Second is that we do this for each genre of books we own. How would we accomplish this in SQL? Here's a potential sample:
with ranked_books as (
select b.*,
row_number() over (
partition by b.genre_id
order by
-- 1st priority: book we've read
case
when exists (
select 1
from reading_record rr
where rr.book_id = b.id
and rr.status = 'completed'
) then 1
else 0
end desc,
-- 2nd priority: highest rating
case
when b.rating is not null
then b.rating
else 0
end desc
) as rank
from book b
)
select *
from ranked_books
where rank = 1;
So, how does this query work?
row_number()
is an SQL-built-in function that counts per row returned, starting at 1.- We are partitioning the count based on the
genre_id
value. So there's a fresh incrementing counter per genre. - We alias the count as
rank
. - We are giving an order to the books, so the rank will be determined by the two cases. One: If we've read the book. This case is checked first and is therefore primary. Two: If there's a rating, we're use the rating value.
- A
with
clause is like saving a variable.ranked_books
becomes a selection that we can later further filter and select from. And we do, only returning therank = 1
books.
This would return results like:
id | title | genre_id | rating | rank
---|-----------------------|----------|--------|-----
1 | A Tale of Two Cities | 1 | 4.5 | 1
3 | Pride and Prejudice | 2 | 3.5 | 1
5 | War and Peace | 3 | 4.5 | 1