Programing Language/SQL
해커랭크 Top Competitors (다중 JOIN, 필터링)
Data-SSung
2025. 6. 15. 23:34
반응형
https://www.hackerrank.com/challenges/full-score/problem
Top Competitors | HackerRank
Query a list of top-scoring hackers.
www.hackerrank.com
문제 정의 (영어 -> 한국어 번역 후 문제 파악하기)
줄리아가 방금 코딩 경진대회를 마쳤는데, 리더보드를 만드는 데 여러분의 도움이 필요합니다! 두 개 이상의 과제 에서 만점을 받은 해커의 hacker_id 와 이름을 출력하는 쿼리를 작성하세요. 해커가 만점을 받은 과제의 총 개수를 기준으로 내림차순으로 정렬하세요. 두 명 이상의 해커가 같은 개수의 과제에서 만점을 받은 경우, hacker_id를 기준으로 오름차순으로 정렬하세요 .
1차 풀기
- 풀기 전략
- 단계별 cte
- 해커 id, 이름
- 조건
- 2개 이상 과제에서 만점 받은 해커
- 정렬 기준
- 만점 갯수 내림차순
- 해커 id 오름차순
- 피드백
- 오타 조심
- 만약 groupby를 하나 줄여서 연산 처리 속도를 빠르게 하고 싶다면, 윈도우 함수 사용할 수는 있지만...비추
, max(score) over(partition by hacker_id, challeng_id order by score desc) max_score
, row_number() over(partition by hacker_id, challeng_id order by score desc) rn
- 단계별 cte
/*
-- 해커 id, 이름
-- 조건
-- 2개 이상 과제에서 만점 받은 해커
-- 정렬 기준
-- 만점 갯수 내림차순
-- 해커 id 오름차순
-- 테이블 정리
-- Hackers: 해커, 이름
-- Difficulty : 어려움, 만점 점수
-- Challenges : 챌린지 id, 해커(만든) id, 어려움
-- Submissions : 제출 id, 해커(제출) id, 챌린지 id, 점수
-- join 관계 정리
select s.hacker_id
, h.name
,
from submissions s
inner join challenges c -- 어려움 붙이기
on s.challenge_id = c.challenge_id
inner join difficulty d -- 만점 기준
on c.difficulty_level = d.difficlty_level
inner join hackers h -- 해커 이름 붙이기
on s.hacker_id = h.hacker_id
*/
-- cte 작성
-- 1. 해커별 최고 점수 구하기
with tmp as (
select hacker_id, challenge_id
, max(score) max_score
from submissions
group by hacker_id, challenge_id
)
-- 2. 챌린지별 만점 여부 붙여서 해커별로 만점 과제 count
, tmp2 as (
select s.hacker_id, h.name
, count(distinct s.challenge_id) cnt_full_score
from tmp s
inner join challenges c -- 어려움 붙이기
on s.challenge_id = c.challenge_id
inner join difficulty d -- 만점 기준
on c.difficulty_level = d.difficulty_level
and s.max_score = d.score -- 과제별 만점자 필터링
inner join hackers h -- 해커 이름 붙이기
on s.hacker_id = h.hacker_id
group by s.hacker_id, h.name
)
select hacker_id, name
from tmp2
where cnt_full_score >= 2
order by cnt_full_score desc
, hacker_id asc
;
최적화
- 가독성을 위해서 해커별 만점 과제 정보 만든 후, hacker별 2개이상 만점 수 필터링 과정을 나누기
-- ⚡ 방법 1: CTE 최적화 (권장 - 가독성 + 성능)
WITH hacker_max_scores AS (
-- 1단계: 해커별 챌린지별 최고점만 (중복 제거)
SELECT hacker_id, challenge_id, MAX(score) AS max_score
FROM submissions
GROUP BY hacker_id, challenge_id
),
full_score_challenges AS (
-- 2단계: 만점받은 챌린지만 필터링
SELECT h.hacker_id, h.challenge_id
FROM hacker_max_scores h
INNER JOIN challenges c ON h.challenge_id = c.challenge_id
INNER JOIN difficulty d ON c.difficulty_level = d.difficulty_level
AND h.max_score = d.score
)
-- 3단계: 최종 집계 및 필터링
SELECT f.hacker_id,
h.name,
COUNT(*) AS cnt_full_score
FROM full_score_challenges f
INNER JOIN hackers h ON f.hacker_id = h.hacker_id
GROUP BY f.hacker_id, h.name
HAVING COUNT(*) >= 2
ORDER BY cnt_full_score DESC, f.hacker_id ASC;
📊 이 문제에서 윈도우 함수 분석
❌ 윈도우 함수가 부적합한 이유
1️⃣ GROUP BY 최적화 효과 없음
sql
-- 현재 문제의 GROUP BY 구조
GROUP BY hacker_id, challenge_id -- 1차
GROUP BY hacker_id, name -- 2차
윈도우 함수 핵심 목적인 "GROUP BY 줄이기"가 이 문제에선 적용 안 됨
2️⃣ 불필요한 복잡성 증가
sql
-- ❌ 윈도우 함수 방식 (복잡)
MAX(score) OVER(PARTITION BY ...), ROW_NUMBER() OVER(...), 중복 제거
-- ✅ 기본 GROUP BY (간단)
MAX(score) GROUP BY hacker_id, challenge_id
3️⃣ 성능상 이점 없음
- 윈도우 함수: 전체 스캔 + 윈도우 계산 + 필터링
- GROUP BY: 인덱스 활용 + 직접 집계
🎯 언제 윈도우 함수를 써야 하나?
✅ 윈도우 함수가 유리한 경우
예시 1: Contest Leaderboard 타입
sql
-- 중첩 GROUP BY 제거 효과
GROUP BY hacker_id, challenge_id → MAX(score) -- 1차
GROUP BY hacker_id → SUM(score) -- 2차
↓
윈도우 함수로 한 번에 처리
예시 2: 순위 + 원본 데이터
sql
-- 각 해커의 순위 + 원본 제출 정보
SELECT hacker_id, submission_id, score,
RANK() OVER(ORDER BY score DESC)
예시 3: 이전/다음 값 참조
sql
-- 이전 제출과 비교
SELECT hacker_id, score,
LAG(score) OVER(PARTITION BY hacker_id ORDER BY date)
📈 Top Competitors 특성 분석
이 문제의 구조:
1. submissions → 해커별 챌린지별 최고점 (GROUP BY 필수)
2. difficulty와 JOIN → 만점 여부 확인 (조건부 필터링)
3. 해커별 만점 개수 집계 (GROUP BY 필수)
💡 각 단계가 독립적이고 GROUP BY가 최적 → 윈도우 함수 불필요
🏆 결론
Top Competitors에서는:
- ❌ 윈도우 함수: 오버엔지니어링
- ✅ CTE + GROUP BY: 최적 솔루션
- ✅ 단순 명확함: 코테에서 중요
윈도우 함수 vs GROUP BY 판단 기준:
중첩된 GROUP BY 있음? → 윈도우 함수 고려
순위/누적값 필요? → 윈도우 함수 필수
단순 집계만? → GROUP BY가 최적
반응형