ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 해커랭크 Top Competitors (다중 JOIN, 필터링)
    Programing Language/SQL 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
    /*
    -- 해커 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가 최적

     

    반응형

    댓글

Designed by Tistory.