행마다 다른 번호가 필요하면 ROW_NUMBER, 공동 순위를 유지하면서 다음 번호를 건너뛰려면 RANK, 점수 그룹에 연속 번호를 붙이려면 DENSE_RANK를 사용합니다. 먼저 ‘어떤 열이 같아야 동점인가’를 정해야 함수를 바르게 고를 수 있습니다.

아래 표와 쿼리는 PostgreSQL 18 문서를 바탕으로 만든 교육용 예시입니다. 같은 점수의 표시 순서와 공동 순위의 기준을 분리해 계산합니다.

순위 기준과 표시 순서는 서로 다릅니다

점수만으로 공동 순위를 매기는 대회라면 90점 두 명은 같은 순위입니다. 화면에서 이름이나 ID 순으로 정렬한다고 공동 순위가 달라지지는 않습니다. 이때 점수는 순위를 결정하는 기준이고, ID는 같은 점수 안에서 누구를 먼저 보여 줄지 결정하는 기준입니다. 쿼리에서도 두 역할을 구분해야 합니다.

ROW_NUMBER는 모든 행을 구별해 번호를 붙이므로 동점 안에서 어떤 순서를 원하는지 정해야 합니다. 점수만 내림차순으로 지정하면 두 90점 중 누가 2번이고 누가 3번인지는 고정되지 않습니다. 반면 RANK와 DENSE_RANK는 창의 ORDER BY에 적힌 값이 같은 행들을 하나의 동점 그룹으로 봅니다.

같은 입력에서 세 결과를 계산합니다

직접 만든 점수 표입니다. ID는 모두 다르며 점수에는 NULL이 없습니다.
WITH scores(id, score) AS (
  VALUES (11, 98), (12, 90), (13, 90), (14, 75)
)
SELECT id, score,
       ROW_NUMBER() OVER (ORDER BY score DESC, id) AS row_no,
       RANK() OVER (ORDER BY score DESC) AS rank_no,
       DENSE_RANK() OVER (ORDER BY score DESC) AS dense_no
FROM scores
ORDER BY score DESC, id;
세 함수가 돌려주는 번호
ID점수ROW_NUMBERRANKDENSE_RANK
1198111
1290222
1390322
1475443

98점보다 높은 행은 없으므로 첫 순위는 1입니다. 90점 두 행보다 높은 행은 98점 한 행뿐이므로 둘 다 RANK 2를 받습니다. 75점보다 높은 행은 세 행이어서 RANK는 4가 됩니다. DENSE_RANK는 앞선 점수 종류가 98과 90 두 개라고 세므로 75점에 3을 붙입니다.

손으로 풀 때는 동점 점수를 먼저 묶으세요. RANK 칸에는 그 묶음의 첫 행 위치를 쓰고, DENSE_RANK 칸에는 묶음 번호를 씁니다. ROW_NUMBER 칸에는 묶음과 무관하게 행 순서대로 1부터 적습니다. 세 정의를 한 문장으로 외우기보다 같은 표의 서로 다른 칸에 적용하면 어디서 차이가 나는지 보입니다.

모든 ORDER BY에 ID를 추가하면 틀릴 수 있습니다

출력을 안정시키겠다고 RANK에도 ‘ORDER BY score DESC, id’를 넣으면 어떻게 될까요? 두 90점 행의 ID가 다르므로 더 이상 같은 정렬값을 가진 행이 아닙니다. RANK와 DENSE_RANK 모두 이 입력에서는 1, 2, 3, 4가 됩니다. 점수의 공동 순위가 필요했다면 요구를 바꾼 셈입니다.

점수 공동 순위를 유지하지 않는 비교용 식입니다.
RANK() OVER (ORDER BY score DESC, id)
DENSE_RANK() OVER (ORDER BY score DESC, id)

따라서 ID를 추가할 위치를 구별합니다. ROW_NUMBER에 유일한 ID를 넣으면 행 번호를 결정할 수 있습니다. 최종 ORDER BY에 ID를 넣으면 출력 순서를 결정할 수 있습니다. 하지만 공동 순위를 계산하는 창 내부에 ID를 넣으면 동점을 판단하는 기준이 달라집니다. 같은 키워드가 보인다고 세 위치를 동일하게 수정하지 마세요.

상위 두 행과 상위 두 점수 그룹은 다릅니다

인원이 정확히 두 명이어야 하는 실습 배정과, 상위 두 점수 그룹을 모두 보여 주는 성적 조회를 비교해 보세요. 위 쿼리 결과에서 row_no가 2 이하인 행은 ID 11과 12 두 개입니다. dense_no가 2 이하인 행은 ID 11, 12, 13 세 개입니다. 공동 순위를 포함할지 결정하지 않고 ‘상위 두 개’라고만 적으면 요구가 모호합니다.

이 네 행에서는 rank_no가 2 이하인 결과도 세 개이지만, RANK와 DENSE_RANK가 항상 같은 필터라는 뜻은 아닙니다. 기준을 3 이하로 바꾸면 RANK는 세 행만 남기고 DENSE_RANK는 75점 행까지 네 행을 남깁니다. 작은 입력에서도 숫자 하나를 바꾸면 두 함수의 차이를 확인할 수 있습니다.

순위 결과를 조건으로 걸 때는 순위를 계산한 쿼리를 CTE나 하위 쿼리로 감싼 뒤 바깥 WHERE에서 필터링합니다. PostgreSQL에서 창 함수는 같은 단계의 WHERE에 직접 넣을 수 없습니다. 계산 전 행을 제거하는 조건과 계산이 끝난 순번을 고르는 조건은 별개의 단계입니다.

틀린 결과를 한 번 더 변형해 봅니다

  1. 점수 세 개가 같은 경우를 만들어 동점 묶음의 크기를 늘립니다.
  2. 점수뿐 아니라 부문까지 같은 사람끼리 비교해야 한다면 PARTITION BY로 부문을 나눌지 판단합니다.
  3. 각 부문에서 순번이 다시 1로 시작하는지 확인합니다.
  4. ROW_NUMBER의 동점 순서를 결정하는 키와 RANK의 동점 기준을 따로 적습니다.
  5. 최종 결과의 행 수와 출력 순서를 각각 확인한 뒤 정답을 비교합니다.

복습할 때는 함수 이름을 가리고 요구부터 적어 보세요. ‘정확히 두 행’, ‘공동 2위까지’, ‘두 종류의 점수’는 다른 요구입니다. 숫자 표를 맞혔더라도 왜 그 행이 남는지 설명하지 못했다면 새로운 동점 수를 넣어 다시 계산합니다. 이 방법은 입력 순서를 암기하는 대신 선택 기준을 회상하게 합니다.

정답을 가리고 확인해 보세요

이 글을 위해 만든 학습 예시이며 공식 기출문제나 판매 카드의 인용이 아닙니다.

점수 95, 95, 80을 내림차순으로 비교합니다. 점수만 동점 기준으로 삼을 때 마지막 80점의 RANK와 DENSE_RANK는 각각 얼마인가요? RANK에 고유 ID까지 정렬 기준으로 넣어도 같은 공동 순위가 유지되나요?

정답과 해설 확인80점의 RANK는 3, DENSE_RANK는 2입니다. RANK의 정렬 기준에 고유 ID를 넣으면 점수가 같아도 동점 그룹이 갈라집니다.

80점 앞에는 두 행이 있지만 서로 다른 높은 점수는 95 하나입니다. 동점 여부는 창 ORDER BY에 적힌 모든 정렬값으로 결정하므로 고유 ID를 더하면 공동 순위를 유지하지 못합니다.