Skip to content

refactor : 학과 랭킹 빈값 문제 해결#32

Merged
Juhye0k merged 1 commit intodevfrom
wifi
Nov 19, 2025
Merged

refactor : 학과 랭킹 빈값 문제 해결#32
Juhye0k merged 1 commit intodevfrom
wifi

Conversation

@Juhye0k
Copy link
Copy Markdown
Contributor

@Juhye0k Juhye0k commented Nov 19, 2025

🚀 1. 개요

학과 랭킹 조회 시 실제 데이터가 없는 학과들이 빈값으로 표시되는 문제를 해결했습니다.

기존에는 하드코딩된 학과 목록(WITH departments)을 기준으로 랭킹을 조회했으나, 실제 사용자가 등록된 학과만 조회하도록 개선했습니다.

📝 2. 주요 변경 사항

1. DepartmentRankingTemp.java

  • department 필드 추가: 원본 enum 값 저장
  • getDepartmentName() 메서드 개선:
    • 생성자에서 즉시 변환하던 방식에서 getter에서 변환하는 방식으로 변경
    • IllegalArgumentException 예외 처리 추가
    • enum에 없는 값인 경우 원본 값 그대로 반환
public String getDepartmentName() {
    if (department == null) return null;
    try {
        Department dept = Department.valueOf(department);
        return dept.getKoreanName();
    } catch (IllegalArgumentException e) {
        return department;
    }
}

2. DepartmentRankingRepository.java

  • 완료된 랭킹 조회 로직 변경: 기존 단순 조회에서 모든 학과를 포함하는 방식으로 변경
  • WITH all_departments AS 절을 통해 전체 24개 학과 목록 정의
  • LEFT JOIN을 사용하여 데이터가 없는 학과도 0으로 표시
  • RANK() OVER (ORDER BY ...) 함수로 동적 순위 계산
  • 메서드 시그니처 변경: RankingType enum → String으로 변경
-- 변경 전
SELECT dr.department as department,
       dr.total_millis as totalMillis,
       dr.ranking as ranking
FROM department_ranking dr
WHERE dr.calculated_at = :period
AND dr.ranking_type = :rankingType
ORDER BY dr.ranking ASC

-- 변경 후
WITH all_departments AS (
    SELECT 'ARCHITECTURE_ENGINEERING' as dept
    UNION ALL SELECT 'ARCHITECTURE'
    UNION ALL SELECT 'CIVIL_ENGINEERING'
    UNION ALL SELECT 'ENVIRONMENTAL_ENGINEERING'
    UNION ALL SELECT 'MECHANICAL_ENGINEERING'
    UNION ALL SELECT 'MECHANICAL_SYSTEMS_ENGINEERING'
    UNION ALL SELECT 'SMART_MOBILITY'
    UNION ALL SELECT 'INDUSTRIAL_ENGINEERING'
    UNION ALL SELECT 'APPLIED_MATH_BIGDATA'
    UNION ALL SELECT 'POLYMER_ENGINEERING'
    UNION ALL SELECT 'MATERIALS_ENGINEERING'
    UNION ALL SELECT 'SEMICONDUCTOR_SYSTEMS'
    UNION ALL SELECT 'ELECTRONIC_SYSTEMS'
    UNION ALL SELECT 'SOFTWARE'
    UNION ALL SELECT 'ARTIFICIAL_INTELLIGENCE'
    UNION ALL SELECT 'COMPUTER_ENGINEERING'
    UNION ALL SELECT 'MATERIALS_DESIGN_ENGINEERING'
    UNION ALL SELECT 'CHEMICAL_ENGINEERING'
    UNION ALL SELECT 'CHEMICAL_BIO_MATERIALS'
    UNION ALL SELECT 'OPTICAL_SYSTEMS'
    UNION ALL SELECT 'BIOMEDICAL_ENGINEERING'
    UNION ALL SELECT 'IT_CONVERGENCE'
    UNION ALL SELECT 'LIBERAL_MAJOR'
    UNION ALL SELECT 'BUSINESS_ADMINISTRATION'
)
SELECT d.dept as department,
       COALESCE(dr.total_millis, 0) as totalMillis,
       RANK() OVER (ORDER BY COALESCE(dr.total_millis, 0) DESC) as ranking
FROM all_departments d
LEFT JOIN department_ranking dr ON d.dept = dr.department
    AND dr.calculated_at = :period
    AND dr.ranking_type = :rankingType
ORDER BY COALESCE(dr.total_millis, 0) DESC

3. DepartmentRankService.java

3-1. enum을 String으로 변환하여 전달

  • RankingType.DAILYRankingType.DAILY.name()
  • RankingType.WEEKLYRankingType.WEEKLY.name()
  • RankingType.MONTHLYRankingType.MONTHLY.name()

Summary by CodeRabbit

릴리스 노트

  • 기능 개선

    • 모든 부서의 순위가 데이터 유무와 관계없이 정상적으로 표시됩니다.
    • 부서 순위 산출 로직을 개선하여 더 정확한 순위 계산이 가능합니다.
  • 버그 수정

    • 사용자의 부서 순위 정보가 없을 때 기본값이 올바르게 처리됩니다.

@Juhye0k Juhye0k requested a review from kon28289 November 19, 2025 03:06
@Juhye0k Juhye0k self-assigned this Nov 19, 2025
@Juhye0k Juhye0k added the enhancement New feature or request label Nov 19, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Nov 19, 2025

개요

부서 랭킹 데이터 처리 방식을 리팩토링했습니다. DepartmentRankingTemp는 원본 열거형 값을 저장하고 별도 메서드로 한국어 이름 변환을 수행하도록 변경하였으며, 저장소 쿼리는 고정된 부서 목록 대신 사용자 테이블에서 부서를 동적으로 조회하도록 수정했습니다.

변경 사항

응집 / 파일 변경 요약
DTO 리팩토링
src/main/java/com/gpt/geumpumtabackend/rank/dto/DepartmentRankingTemp.java
필드를 departmentName에서 department로 변경하여 원본 열거형 값 저장. 생성자에서 직접 저장하고, 새로운 getDepartmentName() 메서드를 추가하여 한국어 이름 변환 처리 및 null/유효하지 않은 값 처리
저장소 쿼리 및 서명 변경
src/main/java/com/gpt/geumpumtabackend/rank/repository/DepartmentRankingRepository.java
고정 부서 목록 CTE에서 LEFT JOIN을 사용한 새로운 쿼리로 변경. RANK 윈도우 함수로 모든 부서에 대해 랭킹 계산. 메서드 서명에서 RankingType 매개변수를 String으로 변경
서비스 로직 업데이트
src/main/java/com/gpt/geumpumtabackend/rank/service/DepartmentRankService.java
저장소 호출 시 RankingType.DAILY.name() 형식으로 문자열 전달. 사용자의 부서 랭킹이 없을 때 기본값으로 새로운 응답 항목 생성 (0초, 마지막 순위)
세션 저장소 쿼리 리팩토링
src/main/java/com/gpt/geumpumtabackend/study/repository/StudySessionRepository.java
세 개 메서드(calculateCurrentDepartmentRanking, calculateFinalizedDepartmentRanking, calculateFinalizedPeriodRanking)의 쿼리를 수정. 고정 부서 CTE 제거, 사용자 테이블에서 동적으로 부서 조회. u.department IS NOT NULL 조건 추가, GROUP BY를 u.department로 변경

시퀀스 다이어그램

sequenceDiagram
    participant Service as DepartmentRankService
    participant Repo as DepartmentRankingRepository
    participant DB as Database
    participant DTO as DepartmentRankingTemp

    Service->>Repo: getFinishedDepartmentRanking(period, "DAILY")
    Repo->>DB: WITH all_departments CTE<br/>SELECT from user table<br/>LEFT JOIN department_ranking<br/>RANK() window function
    DB-->>Repo: List of all departments<br/>with rankings
    Repo->>DTO: Create DepartmentRankingTemp<br/>(department, totalMillis, ranking)
    DTO->>DTO: Store raw enum value<br/>in department field
    Repo-->>Service: List<DepartmentRankingTemp>
    Service->>Service: Find user's ranking<br/>or create default
    Service-->>Service: Return response with<br/>getDepartmentName() results
Loading

코드 리뷰 예상 소요 시간

🎯 3 (중간) | ⏱️ ~25분

추가 검토 필요 영역:

  • 데이터베이스 쿼리 로직: CTE와 윈도우 함수를 사용한 부서 랭킹 계산이 모든 엣지 케이스를 올바르게 처리하는지 확인
  • null 처리: getDepartmentName() 메서드의 null 및 유효하지 않은 열거형 값 처리가 예상대로 동작하는지 검증
  • 매개변수 타입 변경: RankingType enum에서 String으로의 변경이 모든 호출처에서 일관되게 적용되었는지 확인
  • 저장소 서명 변경에 따른 영향: 다른 서비스/컨트롤러에서 해당 저장소 메서드를 호출하는 부분이 올바르게 업데이트되었는지 확인

관련 PR

추천 검토자

  • kon28289

🐰 부서 랭킹 다시 정렬하고,
사용자 데이터에서 살포시 깎아내,
한국말 이름은 따로따로,
CTE와 윈도우 함수로 춤을 추네 ✨
모든 부서가 순위에 오르다! 🏆

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항을 명확하게 요약하고 있으며, 학과 랭킹 빈값 문제 해결이라는 핵심 목표를 잘 나타냅니다.
Description check ✅ Passed PR 설명이 템플릿의 필수 섹션(개요, 주요 변경 사항)을 완전히 포함하고 있으며, 상세한 코드 예시와 함께 각 파일별 변경사항을 명확히 설명합니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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 wifi

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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

@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 671d8c7 into dev Nov 19, 2025
2 of 3 checks passed
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: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6dab196 and f563ad8.

📒 Files selected for processing (4)
  • src/main/java/com/gpt/geumpumtabackend/rank/dto/DepartmentRankingTemp.java (1 hunks)
  • src/main/java/com/gpt/geumpumtabackend/rank/repository/DepartmentRankingRepository.java (1 hunks)
  • src/main/java/com/gpt/geumpumtabackend/rank/service/DepartmentRankService.java (4 hunks)
  • src/main/java/com/gpt/geumpumtabackend/study/repository/StudySessionRepository.java (4 hunks)

Comment on lines +20 to 54
WITH all_departments AS (
SELECT 'ARCHITECTURE_ENGINEERING' as dept
UNION ALL SELECT 'ARCHITECTURE'
UNION ALL SELECT 'CIVIL_ENGINEERING'
UNION ALL SELECT 'ENVIRONMENTAL_ENGINEERING'
UNION ALL SELECT 'MECHANICAL_ENGINEERING'
UNION ALL SELECT 'MECHANICAL_SYSTEMS_ENGINEERING'
UNION ALL SELECT 'SMART_MOBILITY'
UNION ALL SELECT 'INDUSTRIAL_ENGINEERING'
UNION ALL SELECT 'APPLIED_MATH_BIGDATA'
UNION ALL SELECT 'POLYMER_ENGINEERING'
UNION ALL SELECT 'MATERIALS_ENGINEERING'
UNION ALL SELECT 'SEMICONDUCTOR_SYSTEMS'
UNION ALL SELECT 'ELECTRONIC_SYSTEMS'
UNION ALL SELECT 'SOFTWARE'
UNION ALL SELECT 'ARTIFICIAL_INTELLIGENCE'
UNION ALL SELECT 'COMPUTER_ENGINEERING'
UNION ALL SELECT 'MATERIALS_DESIGN_ENGINEERING'
UNION ALL SELECT 'CHEMICAL_ENGINEERING'
UNION ALL SELECT 'CHEMICAL_BIO_MATERIALS'
UNION ALL SELECT 'OPTICAL_SYSTEMS'
UNION ALL SELECT 'BIOMEDICAL_ENGINEERING'
UNION ALL SELECT 'IT_CONVERGENCE'
UNION ALL SELECT 'LIBERAL_MAJOR'
UNION ALL SELECT 'BUSINESS_ADMINISTRATION'
)
SELECT d.dept as department,
COALESCE(dr.total_millis, 0) as totalMillis,
RANK() OVER (ORDER BY COALESCE(dr.total_millis, 0) DESC) as ranking
FROM all_departments d
LEFT JOIN department_ranking dr ON d.dept = dr.department
AND dr.calculated_at = :period
AND dr.ranking_type = :rankingType
ORDER BY COALESCE(dr.total_millis, 0) DESC
""", nativeQuery = true)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

하드코딩된 학과 목록은 enum 변경 시 즉시 깨집니다.
all_departments CTE에 24개 학과 문자열을 직접 나열하면서 Department enum과의 단일 소스가 무너졌습니다. 추후 enum에 신규 학과가 추가되거나 이름이 바뀔 때 이 목록을 잊고 갱신하지 않으면, 해당 학과는 department_ranking 테이블에 데이터가 존재해도 LEFT JOIN 대상에 없어서 완전히 빠지게 됩니다. 즉시 기능 장애가 발생하는 구조입니다. DB에 기준 테이블을 두거나, 최소한 user/department_ranking에서 SELECT DISTINCT department를 합쳐 동적으로 베이스 세트를 만들도록 수정해야 합니다.

Comment on lines +105 to +112
// 사용자의 학과를 찾지 못한 경우 0초, 마지막 순위로 설정
if (myRanking == null && user.getDepartment() != null) {
myRanking = new DepartmentRankingEntryResponse(
user.getDepartment().getKoreanName(),
0L,
(long) departmentRankingList.size() + 1
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

자동 순위가 RANK() 결과와 어긋납니다.
departmentRankingList.size() + 1은 SQL RANK()가 동점에 대해 순위를 건너뛴다는 사실을 무시합니다. 예를 들어 모든 total_millis가 0이면 각 행의 ranking은 1이 되는데, 현재 로직은 사용자 fallback 순위를 25로 만들어 버려 잘못된 정보가 노출됩니다. 마지막 항목의 ranking 값에 +1(리스트가 비어 있으면 1)하도록 보정해야 일관성이 유지됩니다.

         if (myRanking == null && user.getDepartment() != null) {
-            myRanking = new DepartmentRankingEntryResponse(
-                user.getDepartment().getKoreanName(), 
-                0L, 
-                (long) departmentRankingList.size() + 1
-            );
+            long baseRank = departmentRankingList.isEmpty()
+                ? 0L
+                : departmentRankingList.get(departmentRankingList.size() - 1).getRanking();
+            myRanking = new DepartmentRankingEntryResponse(
+                user.getDepartment().getKoreanName(),
+                0L,
+                baseRank + 1
+            );
         }
🤖 Prompt for AI Agents
In
src/main/java/com/gpt/geumpumtabackend/rank/service/DepartmentRankService.java
around lines 105 to 112, the fallback rank uses departmentRankingList.size() + 1
which diverges from SQL RANK() behavior when ties exist; instead compute the
fallback as (last entry's ranking + 1) or 1 if the list is empty. Retrieve the
last DepartmentRankingEntryResponse from departmentRankingList when size > 0 to
get its ranking value and set the fallback rank to that value + 1; if the list
is empty use 1L. Ensure null checks on departmentRankingList and handle
user.getDepartment() condition as before.

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