Skip to content

feat : 학과별 상위 30등만 랭킹에 반영#51

Merged
Juhye0k merged 2 commits intodevfrom
ip
Dec 3, 2025
Merged

feat : 학과별 상위 30등만 랭킹에 반영#51
Juhye0k merged 2 commits intodevfrom
ip

Conversation

@Juhye0k
Copy link
Copy Markdown
Contributor

@Juhye0k Juhye0k commented Dec 3, 2025

🚀 1. 개요

학과별 랭킹 산정 시, 각 학과의 상위 30명만 집계에 반영하도록 로직을 변경했습니다.
학과별 인원 수 편차로 인해 소수 인원 학과가 상대적으로 불리해지는 문제를 완화하고,
보다 공정한 학과 간 비교가 가능하도록 하는 것이 목적입니다.

📝 2. 주요 변경 사항

2-1. 공통 개념: 학과별 상위 30인 집계 로직 도입

기존에는 학과에 속한 모든 유저의 totalMillis 합계를 기반으로 학과 랭킹을 계산했습니다.
이번 변경에서는 아래와 같은 흐름으로 랭킹이 계산됩니다.

  1. user + study_session 기준으로 유저별 totalMillis를 계산
  2. ROW_NUMBER() OVER (PARTITION BY u.department ORDER BY totalMillis DESC) 를 통해
    학과별 유저 랭크(deptRank) 산출
  3. WHERE deptRank <= 30 조건으로 각 학과별 상위 30명만 필터링
  4. 필터링된 유저들에 대해 SUM(totalMillis) 를 다시 계산하여 학과별 totalMillis 재산정
  5. RANK() OVER (ORDER BY SUM(totalMillis) DESC)학과 랭킹 부여

Summary by CodeRabbit

개선 사항

  • 버그 수정
    • 학과별 순위 계산 로직을 개선했습니다.
    • 각 학과별 상위 30명 기준으로 순위를 재산정합니다.
    • 재계산된 집계가 있을 경우 해당 값을 우선 반영해 총합 및 정렬 결과에 반영합니다.

✏️ Tip: You can customize this high-level summary in your review settings.

@Juhye0k Juhye0k requested a review from kon28289 December 3, 2025 10:27
@Juhye0k Juhye0k self-assigned this Dec 3, 2025
@Juhye0k Juhye0k added the enhancement New feature or request label Dec 3, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Dec 3, 2025

Walkthrough

두 저장소 파일의 부서별 순위 계산 로직을 재구성하여, 부서당 상위 30명 기준의 중첩 순위 계산(ROW_NUMBER)과 재계산된 부서 합계(COALESCE 적용)를 도입했습니다.

Changes

Cohort / File(s) Summary
Department ranking CTE 및 출력 변경
src/main/java/com/gpt/geumpumtabackend/rank/repository/DepartmentRankingRepository.java
recalculated_rankings CTE 추가, departmentsLEFT JOIN 하여 재계산된 totalMillis/ranking 노출, SELECT/ORDER BY에서 COALESCE(rr.totalMillis, dr.total_millis, 0) 사용, 헤더 주석 업데이트.
StudySession 쿼리 중첩화 (dept top-30)
src/main/java/com/gpt/geumpumtabackend/study/repository/StudySessionRepository.java
calculateCurrentDepartmentRankingcalculateFinalizedDepartmentRanking 쿼리를 내부에서 사용자별 합계와 ROW_NUMBER() OVER (PARTITION BY department ORDER BY ...)로 deptRank 계산한 뒤 deptRank <= 30로 필터링하는 중첩(subquery) 구조로 재작성하고, 외부에서 부서별 집계 및 정렬 수행.

개요

두 저장소 파일의 부서별 순위 계산 로직을 재구성했습니다. DepartmentRankingRepository에 recalculated_rankings CTE를 추가하여 부서별 상위 30명 기준의 순위를 사전 계산하고, StudySessionRepository의 두 쿼리를 ROW_NUMBER 기반의 중첩된 분할 순위 지정 방식으로 재작성했습니다.

변경사항

집단 / 파일 요약
부서 순위 쿼리 재구성
src/main/java/com/gpt/geumpumtabackend/rank/repository/DepartmentRankingRepository.java
부서별 totalMillis를 집계하고 부서 수준의 순위를 계산하는 recalculated_rankings CTE 추가. 부서당 상위 30명 기준 유지. 최종 SELECT에서 COALESCE를 사용하여 재계산된 순위와 기존 부서 순위 통합. ORDER BY 절 업데이트.
부서 순위 쿼리 다층 구조화
src/main/java/com/gpt/geumpumtabackend/study/repository/StudySessionRepository.java
calculateCurrentDepartmentRanking 및 calculateFinalizedDepartmentRanking 쿼리를 단일 수준 집계에서 ROW_NUMBER 기반의 중첩 분할 순위 방식으로 재작성. 최종 집계 전에 부서 내 사용자별 순위를 계산하고 상위 30명으로 필터링. 내부 subquery의 deptRank 필터링을 통한 두 단계 프로세스 도입.

코드 리뷰 예상 소요 시간

🎯 4 (Complex) | ⏱️ ~45분

  • 주의 깊게 검토할 영역:
    • DepartmentRankingRepositoryrecalculated_rankings CTE 및 COALESCE 로직
    • StudySessionRepositoryROW_NUMBER() 파티셔닝/정렬 기준과 deptRank <= 30 필터 적용 위치
    • TIMESTAMPDIFF / LEAST / GREATEST 계산식의 경계 처리 일관성
    • 최종 ORDER BY 및 NULL/0 처리로 인한 순위 변동 시나리오

Possibly related PRs

제안된 검토자

  • kon28289

🐰 부서별로 모아봤더니 반짝이는 기록,
상위 서른 명씩 줄세워 껑충뛰어,
COALESCE로 이어 붙였더니 빈자리 채워,
중첩 순위로 정리한 나의 작은 정원,
토끼도 박수 칠게요, 빛나는 성적표! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 변경 사항의 핵심을 명확하게 반영하고 있습니다. '학과별 상위 30등만 랭킹에 반영'이라는 표현으로 이번 PR의 주요 목표를 간결하고 구체적으로 나타냅니다.
Description check ✅ Passed PR 설명이 저장소의 템플릿 구조를 따르고 있으며, 변경 사항이 상세히 설명되어 있습니다. 개요와 주요 변경 사항이 잘 작성되어 있으나 스크린샷 섹션이 누락되어 있습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ip

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/com/gpt/geumpumtabackend/study/repository/StudySessionRepository.java (1)

100-119: totalMillis 계산에서 /1000 누락 (단위 불일치 버그)

Line 100-109의 totalMillis 계산에서 /1000이 누락되어 있습니다. 반면 Line 110-119의 ranking 계산에서는 /1000이 포함되어 있습니다.

결과적으로:

  • totalMillis는 마이크로초 단위로 반환됨
  • ranking은 밀리초 기준으로 산정됨

이로 인해 데이터 일관성 문제와 예상치 못한 동작이 발생할 수 있습니다.

        CAST(COALESCE(SUM(
            TIMESTAMPDIFF(MICROSECOND,
                GREATEST(s.start_time, :periodStart),
                CASE
                    WHEN s.end_time IS NULL THEN :periodEnd
                    WHEN s.end_time > :periodEnd THEN :periodEnd
                    ELSE s.end_time
                END
-           ) 
+           ) / 1000
        ), 0) AS SIGNED) as totalMillis,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bca3567 and 926a6f8.

📒 Files selected for processing (2)
  • src/main/java/com/gpt/geumpumtabackend/rank/repository/DepartmentRankingRepository.java (2 hunks)
  • src/main/java/com/gpt/geumpumtabackend/study/repository/StudySessionRepository.java (1 hunks)
🔇 Additional comments (3)
src/main/java/com/gpt/geumpumtabackend/rank/repository/DepartmentRankingRepository.java (1)

46-78: 상위 30명 필터링 로직 구현이 적절합니다

학과별 상위 30명만 집계에 반영하는 로직이 올바르게 구현되었습니다:

  1. 내부 쿼리에서 유저별 totalMillis 계산 및 ROW_NUMBER()로 학과 내 순위 산출
  2. WHERE deptRank <= 30으로 상위 30명 필터링
  3. 외부 쿼리에서 SUM(totalMillis)로 학과별 합산
src/main/java/com/gpt/geumpumtabackend/study/repository/StudySessionRepository.java (2)

143-185: 현재 진행 중인 학과 랭킹 로직이 올바르게 구현되었습니다

학과별 상위 30명 필터링 로직이 정확히 구현되었습니다:

  • 내부 서브쿼리에서 ROW_NUMBER() OVER (PARTITION BY u.department ...)로 학과별 순위 산출
  • WHERE deptRank <= 30으로 필터링
  • 진행 중인 세션(s.end_time IS NULL)에 대해 :now 파라미터를 사용하여 현재 시간까지 계산

191-225: 종료된 기간의 학과 랭킹 로직이 적절합니다

calculateCurrentDepartmentRanking과 일관된 구조로 상위 30명 필터링이 구현되었습니다. Line 218에서 s.end_time >= :periodStart 조건으로 완료된 세션만 집계하며, 종료된 기간에 적합한 처리입니다.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/main/java/com/gpt/geumpumtabackend/rank/repository/DepartmentRankingRepository.java (2)

61-69: 동점 사용자 처리를 위해 RANK() 사용 고려

ROW_NUMBER()는 동점인 경우에도 임의로 순번을 부여합니다. 예를 들어 30위와 31위 사용자의 totalMillis가 동일할 경우, 한 명은 포함되고 한 명은 제외될 수 있습니다.

동점 사용자를 모두 포함시키려면 RANK()를 사용하는 것이 더 공정할 수 있습니다. 다만 이 경우 학과당 30명을 초과할 수 있으므로 의도에 따라 결정해 주세요.


51-76: 중복 계산 리팩토링 고려

totalMillis 계산이 SELECT 절(lines 55-60)과 ORDER BY 절(lines 63-68)에서 중복됩니다. 가독성과 유지보수성을 위해 내부 서브쿼리를 한 단계 더 추가하여 계산을 한 번만 수행하도록 리팩토링할 수 있습니다.

             recalculated_rankings AS (
                 SELECT 
                     department,
                     CAST(SUM(totalMillis) AS SIGNED) as totalMillis,
                     RANK() OVER (ORDER BY SUM(totalMillis) DESC) as ranking
                 FROM (
                     SELECT 
-                        u.department,
-                        u.id as userId,
-                        COALESCE(SUM(
-                            TIMESTAMPDIFF(MICROSECOND,
-                                GREATEST(s.start_time, DATE(:period)),
-                                LEAST(s.end_time, DATE_ADD(DATE(:period), INTERVAL 1 DAY))
-                            ) / 1000
-                        ), 0) as totalMillis,
-                        ROW_NUMBER() OVER (
-                            PARTITION BY u.department 
-                            ORDER BY COALESCE(SUM(
-                                TIMESTAMPDIFF(MICROSECOND,
-                                    GREATEST(s.start_time, DATE(:period)),
-                                    LEAST(s.end_time, DATE_ADD(DATE(:period), INTERVAL 1 DAY))
-                                ) / 1000
-                            ), 0) DESC
-                        ) as deptRank
-                    FROM user u
-                    LEFT JOIN study_session s ON u.id = s.user_id
-                        AND s.start_time < DATE_ADD(DATE(:period), INTERVAL 1 DAY)
-                        AND s.end_time >= DATE(:period)
-                    WHERE u.role = 'USER' AND u.department IS NOT NULL
-                    GROUP BY u.department, u.id
+                        department,
+                        userId,
+                        totalMillis,
+                        ROW_NUMBER() OVER (
+                            PARTITION BY department 
+                            ORDER BY totalMillis DESC
+                        ) as deptRank
+                    FROM (
+                        SELECT 
+                            u.department,
+                            u.id as userId,
+                            COALESCE(SUM(
+                                TIMESTAMPDIFF(MICROSECOND,
+                                    GREATEST(s.start_time, DATE(:period)),
+                                    LEAST(s.end_time, DATE_ADD(DATE(:period), INTERVAL 1 DAY))
+                                ) / 1000
+                            ), 0) as totalMillis
+                        FROM user u
+                        LEFT JOIN study_session s ON u.id = s.user_id
+                            AND s.start_time < DATE_ADD(DATE(:period), INTERVAL 1 DAY)
+                            AND s.end_time >= DATE(:period)
+                        WHERE u.role = 'USER' AND u.department IS NOT NULL
+                        GROUP BY u.department, u.id
+                    ) user_totals
                 ) ranked_users
                 WHERE deptRank <= 30
                 GROUP BY department
             )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 926a6f8 and 43f581f.

📒 Files selected for processing (1)
  • src/main/java/com/gpt/geumpumtabackend/rank/repository/DepartmentRankingRepository.java (2 hunks)
🔇 Additional comments (1)
src/main/java/com/gpt/geumpumtabackend/rank/repository/DepartmentRankingRepository.java (1)

80-88: 이전 리뷰 이슈 해결 확인 및 fallback 로직 검토 필요

Line 82에서 RANK() OVER (ORDER BY COALESCE(...) DESC)를 사용하여 이전 리뷰에서 지적된 랭킹 일관성 문제가 해결되었습니다.

다만, COALESCE(rr.totalMillis, dr.total_millis, 0) fallback 로직에서 dr.total_millis는 기존 로직(전체 사용자 기준)으로 계산된 값입니다. 해당 기간에 활동한 사용자가 없는 학과의 경우, 새로운 상위 30명 기준이 아닌 기존 방식의 값이 사용될 수 있습니다.

이것이 의도된 동작인지 확인해 주세요. 만약 모든 학과에 동일한 계산 방식을 적용하려면 dr fallback을 제거하고 COALESCE(rr.totalMillis, 0)만 사용하는 것을 고려해 주세요.

Copy link
Copy Markdown
Contributor

@kon28289 kon28289 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다!

@Juhye0k Juhye0k merged commit 9ebf443 into dev Dec 3, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants