[PostgreSQL] FETCH FIRST n ROWS WITH TIES — 동점까지 포함한 Top-N
설치·접속: PostgreSQL 설치와 접속
부제: 이벤트 "상위 3명 경품"에서 3위가 동점일 때, 억울하게 잘리는 사람 없이 뽑을 때
SELECT player, score
FROM leaderboard
ORDER BY score DESC
FETCH FIRST 3 ROWS WITH TIES;
player | score
--------+-------
alice | 100
bob | 95
carol | 90
dave | 90
erin | 90
(5 rows)3명을 요청했는데 5행이 나왔다. FETCH FIRST 3 ROWS WITH TIES는 상위 3행 + ORDER BY 상 마지막 자리(3위, score=90)와 동점인 행을 모두 추가로 반환한다. carol·dave·erin이 전부 90점이라 셋 다 포함됐다. 경품이 걸린 이벤트라면 이게 공정한 처리다.
LIMIT 3은 이걸 못 한다. 정확히 3행에서 잘라버려 동점자를 임의로 탈락시킨다.
SELECT player, score
FROM leaderboard
ORDER BY score DESC
LIMIT 3;
player | score
--------+-------
alice | 100
bob | 95
carol | 90
(3 rows)같은 90점인데 carol만 살아남고 dave·erin은 사라졌다. 정렬이 안정적이지 않으면 다음 실행에서는 carol 대신 dave가 남을 수도 있다 — "왜 저 사람은 빠졌지"류 버그의 전형이다. WITH TIES는 SQL 표준 FETCH 구문에만 있고 LIMIT에는 없다. 대신 WITH TIES는 ORDER BY가 필수이고(동점 기준이 있어야 하니까), SKIP LOCKED와는 함께 못 쓴다.
이렇게도 쓴다
순위 값 자체가 필요하거나 "정확히 상위 3개 순위"만 원하면 rank()로 간다. 3위 동점자가 모두 rank=3을 받는다. (대비: window rank)
SELECT player, score, rank() OVER (ORDER BY score DESC) AS rnk
FROM leaderboard ORDER BY score DESC;
player | score | rnk
--------+-------+-----
alice | 100 | 1
bob | 95 | 2
carol | 90 | 3
dave | 90 | 3
erin | 90 | 3
frank | 85 | 6
...여기서 WHERE rnk <= 3으로 감싸면 FETCH WITH TIES와 같은 결과가 나온다. 동점을 건너뛰고 다음 순위를 4로 매기고 싶으면 dense_rank().
앞에서 몇 명 건너뛰고 그 지점의 동점까지 포함하려면 OFFSET과 조합한다. (조합: OFFSET)
SELECT player, score FROM leaderboard
ORDER BY score DESC OFFSET 1 ROWS FETCH FIRST 2 ROWS WITH TIES;
동점을 무시하고 딱 N행만 원하면 ONLY(= LIMIT n과 동일).
SELECT player, score FROM leaderboard
ORDER BY score DESC FETCH FIRST 3 ROWS ONLY;
동점 판정을 여러 컬럼으로 엄격히 하려면 ORDER BY에 tiebreaker를 더 넣는다. 그러면 동점이 줄어 WITH TIES가 정확히 n행에 수렴한다.
SELECT player, score FROM leaderboard
ORDER BY score DESC, player ASC FETCH FIRST 3 ROWS WITH TIES;
경계: "상위 그룹별로 각각 Top-N"처럼 파티션이 끼면 FETCH로는 안 되고 rank()/row_number() 윈도우로 가야 한다. FETCH WITH TIES는 전체 결과의 단일 Top-N에만 적용된다.