본문 바로가기

코딩테스트/SQL - Leetcode

178. Rank Scores

Table: Scores
+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| id          | int     |
| score       | decimal |
+-------------+---------+
id is the primary key (column with unique values) for this table.
Each row of this table contains the score of a game.
Score is a floating point value with two decimal places.
 
Write a solution to find the rank of the scores.
The ranking should be calculated according to the following rules:

The scores should be ranked from the highest to the lowest.
If there is a tie between two scores, both should have the same ranking.
After a tie, the next ranking number should be the next consecutive integer value.
In other words, there should be no holes between ranks.
Return the result table ordered by score in descending order.

The result format is in the following example.

 

Example 1:

Input: 
Scores table:
+----+-------+
| id | score |
+----+-------+
| 1  | 3.50  |
| 2  | 3.65  |
| 3  | 4.00  |
| 4  | 3.85  |
| 5  | 4.00  |
| 6  | 3.65  |
+----+-------+
Output: 
+-------+------+
| score | rank |
+-------+------+
| 4.00  | 1    |
| 4.00  | 1    |
| 3.85  | 2    |
| 3.65  | 3    |
| 3.65  | 3    |
| 3.50  | 4    |
+-------+------+
-- 풀이 (1).
-- Window function(O)
select score
     , dense_rank() over(order by score desc) as 'rank'
  from Scores;

 

딱 봐도 순위 관련 함수 중 desne_rank()를 쓰면 되는 문제였고, 역시나 그러했다.

 

그런데... 너무 쉽다!

자만하는 것이 아니라, dense_rank()가 무엇인지 알고 어떻게 사용하는지만 안다면 뭐 별다른 논리 구조를 짤 필요도 없이 바로 풀 수 있는 문제가 아닌가.

 

 

그러니, 윈도우 함수를 안 쓰고 풀어보도록 하자.

 

셀프 조인을 이용해서 풀어볼 것이다. 

 

1. Scores와 Scores를 join 하는데, 오른쪽 테이블(s2)의 score가 왼쪽 테이블(s1)의 score보다 크거나 같다 는 조건을 걸어준다.

 

이렇게 join을 해 주고 나면,

Table : Scores
+----+-------+
| id | score |
+----+-------+
| 1  | 3.50  |
| 2  | 3.65  |
| 3  | 4.00  |
| 4  | 3.85  |
| 5  | 4.00  |
| 6  | 3.65  |
+----+-------+


select * 
  from Scores s1
  left join Scores s2
    on s1.score <= s2.score
    
Output
+----+-------+----+-------+
| id | score | id | score |
+----+-------+----+-------+
| 1  | 3.5   | 6  | 3.65  |
| 1  | 3.5   | 5  | 4     |
| 1  | 3.5   | 4  | 3.85  |
| 1  | 3.5   | 3  | 4     |
| 1  | 3.5   | 2  | 3.65  |
| 1  | 3.5   | 1  | 3.5   |
| 2  | 3.65  | 6  | 3.65  |
| 2  | 3.65  | 5  | 4     |
| 2  | 3.65  | 4  | 3.85  |
| 2  | 3.65  | 3  | 4     |
| 2  | 3.65  | 2  | 3.65  |
| 3  | 4     | 5  | 4     |
| 3  | 4     | 3  | 4     |
| 4  | 3.85  | 5  | 4     |
| 4  | 3.85  | 4  | 3.85  |
| 4  | 3.85  | 3  | 4     |
| 5  | 4     | 5  | 4     |
| 5  | 4     | 3  | 4     |
| 6  | 3.65  | 6  | 3.65  |
| 6  | 3.65  | 5  | 4     |
| 6  | 3.65  | 4  | 3.85  |
| 6  | 3.65  | 3  | 4     |
| 6  | 3.65  | 2  | 3.65  |
+----+-------+----+-------+

 

위와 같은 결과가 나올 것이다.

 

 

2.

이제, s1의 각각의 score의 순위를 매겨줘야 한다.

 

내 위에 아무도 없다면? => 나는 1등

내 위에 1명이 있다면? => 나는 2등

내 위에 n명이 있다면? => 나는 n+1등

 

s1의 각 score별 s2의 score의 개수를 rank로 두면 될 것 같다.

s1에서 score가 겹쳐도 순위가 매겨져야 하니까 s1의 score가 아닌 id를 기준으로 그룹화 하고, 각 그룹별 s2의 score 개수를 count 하면 된다.

-- 풀이(2)
-- Window function(X)
select s1.score
     , count(distinct s2.score) as 'rank'
  from Scores s1
  left join Scores s2
    on s1.score <= s2.score
  group by s1.id
  order by s1.score desc;

 

 ** 조인 조건을 s1.score < s2.score로 두었다면, rank를 count(distinct s2.score) +1 로 두면 될 것이다.