설치·접속: 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에만 적용된다.
'명령어 > DB' 카테고리의 다른 글
| [PostgreSQL] BRIN 인덱스로 200만 행 시계열을 24kB로 색인한다 (0) | 2026.07.23 |
|---|---|
| [PostgreSQL] pg_advisory_lock 크론·배치가 두 번 동시에 도는 걸 앱 레벨 뮤텍스로 막는다 (0) | 2026.07.23 |
| [PostgreSQL] DEFERRABLE INITIALLY DEFERRED — 순환 FK를 커밋 시점에 검증한다 (0) | 2026.07.22 |
| [PostgreSQL] ntile(4) / percent_rank() / cume_dist() — 분위수 버킷으로 나누기 (0) | 2026.07.22 |
| [PostgreSQL] multirange — 불연속 기간을 한 값으로 다룬다 (0) | 2026.07.22 |