437. 순위 함수 (RANK vs DENSE_RANK)

⚠️ 이 문서는 윈도우 함수(436번 문서) 중에서도 게시판의 베스트 글 순위나, 전교 1등부터 100등까지의 성적을 매길 때 필수적으로 사용하는 '순위(Ranking)' 함수들과 그들 간의 미묘하고도 중요한 동점자 처리 방식의 차이점을 다룹니다.

핵심 인사이트 (3줄 요약)

  1. 본질: ORDER BY로 줄을 세운 뒤, 1번부터 차례대로 등수(1, 2, 3...)를 매겨주는 함수다. 무조건 OVER() 절과 함께 써야 한다.
  2. RANK(): 동점자가 나오면 똑같은 등수를 주고, 그 동점자 수만큼 다음 등수를 훌쩍 뛰어넘는(건너뛰는) 함수다. (예: 1등, 2등, 2등, 4등)
  3. DENSE_RANK(): 동점자가 나와도 다음 등수를 건너뛰지 않고 빽빽하게(Dense) 등수를 이어가는 함수다. (예: 1등, 2등, 2등, 3등)

Ⅰ. 개요: 공동 2등 다음은 몇 등일까? (Context & Necessity)

"우리 반 학생들의 수학 점수 순위를 매겨줘!" 선생님의 지시에 따라 점수대로 학생들을 세웠다.

  • 김철수: 100점
  • 이영희: 90점
  • 박민수: 90점
  • 최보스: 80점

문제는 영희와 민수가 동점이라는 것이다. 이 둘을 '공동 2등'으로 처리하는 것까진 좋은데, 그다음 80점인 최보스를 3등으로 불러야 할까, 아니면 4등으로 불러야 할까? 데이터베이스는 이 고민을 덜어주기 위해, 두 가지 상황에 맞춰 골라 쓸 수 있는 서로 다른 2개의 함수를 제공한다.

📢 섹션 요약 비유: RANK()는 올림픽 메달 수여식과 같습니다. 공동 은메달이 2명 나오면 동메달은 아무에게도 주지 않죠 (1등, 2등, 2등, 4등). 반면 DENSE_RANK()는 게임의 '티어(Tier)'와 같습니다. 브론즈, 실버, 골드처럼 사람이 몇 명이든 중간에 비는 등급(등수) 없이 빽빽하게 채워줍니다.


Ⅱ. 3가지 순위 함수의 완벽 비교 ★

시험에서 점수표를 던져주고 "결과값이 다른 하나는?"이라는 형태로 무조건 출제되는 3형제다.

1. RANK() - 동점자 점프 O

  • 동점자에게 같은 등수를 준다.
  • 그다음 등수는 동점자 수만큼 건너뛴다.
  • 결과: 1, 2, 2, 4, 5...

2. DENSE_RANK() - 동점자 점프 X

  • 동점자에게 같은 등수를 준다.
  • 그다음 등수는 무조건 바로 다음 숫자(연속된 숫자)를 준다.
  • 결과: 1, 2, 2, 3, 4...

3. ROW_NUMBER() - 무자비한 일련번호

  • 동점이고 나발이고 신경 안 쓴다.
  • 무조건 1부터 시작해서 1, 2, 3, 4 고유한 일련번호를 억지로 부여한다. (동점자는 내부적인 랜덤 순서나 다른 기준에 의해 줄이 세워짐)
  • 결과: 1, 2, 3, 4, 5...
  • 페이징 처리(게시판 1페이지에 10개씩 보여주기)를 할 때 압도적으로 많이 쓰인다.

Ⅲ. 실무 쿼리 예시

"부서별(PARTITION BY)로 월급 순위(ORDER BY)를 매겨줘!"

SELECT 이름, 부서, 월급,
       RANK() OVER (PARTITION BY 부서 ORDER BY 월급 DESC) AS 일반순위,
       DENSE_RANK() OVER (PARTITION BY 부서 ORDER BY 월급 DESC) AS 빽빽한순위,
       ROW_NUMBER() OVER (PARTITION BY 부서 ORDER BY 월급 DESC) AS 그냥번호
FROM 직원;
┌──────────────────────────────────────────────────────────────┐
│           3가지 순위 함수의 동점자 처리 결과 시각화                 │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│ 이름 │ 월급 │ RANK() │ DENSE_RANK() │ ROW_NUMBER()           │
│ ────┼──────┼────────┼──────────────┼──────────────           │
│ 철수 │ 500  │   1    │      1       │      1                 │
│ 영희 │ 400  │   2 🌟 │      2 🌟    │      2                 │
│ 민수 │ 400  │   2 🌟 │      2 🌟    │      3 (동점 무시)       │
│ 동건 │ 300  │   4 💥 │      3 🛡️    │      4                 │
│ 진우 │ 200  │   5    │      4       │      5                 │
│                                                              │
│ ★ 특징: 400점으로 동점인 영희와 민수 다음 사람(동건)의 등수를 보라!         │
│       RANK()는 3등을 빼먹었고, DENSE_RANK()는 3등으로 꽉 채웠다.   │
└──────────────────────────────────────────────────────────────┘

Ⅳ. 결론

"1등을 찾는 것보다, 2등을 어떻게 대우할 것인가가 더 어렵다." 순위 함수는 윈도우 함수의 가장 대표적인 활용 사례다. 특히 서브쿼리와 묶어서 "부서별로 월급이 가장 높은 사람 3명씩만 뽑아와(WHERE rank <= 3)" 같은 Top-N 쿼리를 짤 때 이 함수들의 가치는 빛을 발한다. 비즈니스 요구사항에 따라 "공동 우승자가 3명이면 4등에게 상금을 줄 것인가(RANK), 아니면 2등 상금을 줄 것인가(DENSE_RANK)?"를 고민하여 적절한 함수를 꺼내 드는 것이 백엔드 엔지니어의 디테일이다.


📌 관련 개념 맵

  • 기반 문법: Window Function OVER() 절 (436번 문서)
  • Top-N 쿼리: ORDER BY ... LIMIT 3 (MySQL), FETCH FIRST 3 ROWS ONLY (Oracle)
  • 보조 키워드: PARTITION BY (그룹별 순위를 매길 때 사용)

👶 어린이를 위한 3줄 비유 설명

  1. 달리기 시합에서 찰리와 스누피가 똑같이 10초에 들어와서 '공동 2등'이 됐어요.
  2. RANK 심판은 "너희 둘이 2등, 3등 자리를 다 차지했으니까 다음으로 들어온 우드스탁은 4등이야!"라고 해요.
  3. DENSE_RANK 심판은 "너희 둘이 공동 2등이니까, 3등 상장이 아직 남았네? 다음으로 들어온 우드스탁이 3등이야!"라고 빽빽하게 상장을 나눠준답니다!