문제 설명
사용자의 반응(Reaction) 데이터를 이용하여 감정 표현이 일관된 사용자(Emotionally Consistent User) 를 찾는 문제였다.
Emotionally Consistent User의 조건은 다음과 같다.
- 전체 리액션이 5개 이상
- 가장 많이 사용한 리액션이 전체 리액션의 60% 이상
- 가장 많이 사용한 리액션(Dominant Reaction)과 그 비율을 출력
출력 컬럼은 다음과 같다.
- user_id
- dominant_reaction
- reaction_ratio
이 문제는 사용자별 최빈값(Mode)을 찾고, 그 비율을 계산하는 집계 + 윈도우 함수 문제였다.
내가 처음 생각한 접근
처음 문제를 읽었을 때 가장 먼저 떠오른 것은
유저별 리액션 개수 집계
↓
가장 많이 사용한 리액션 찾기
↓
전체 리액션과 비교하여 비율 계산
이었다.
문제를 풀기 위해서는
- 전체 리액션 수
- 리액션 종류별 개수
- 가장 많이 사용한 리액션
세 가지 정보가 모두 필요하다고 생각했다.
결국 전체 집계와 그룹 내 1등을 동시에 구해야 하는 문제라는 것을 알게 되었다.
내가 헷갈렸던 부분
1. "가장 많이 사용한 리액션"을 어떻게 가져올까?
처음에는
MAX(count)
처럼 생각했다.
하지만
좋아요 : 10
웃음 : 8
화남 : 5
이라면
10이라는 숫자는 알 수 있어도
좋아요
라는 리액션 자체는 가져올 수 없다.
즉
최대 개수
≠
최대 개수를 가진 행
이라는 점을 다시 확인했다.
그래서
RANK()
OVER(PARTITION BY user_id ORDER BY COUNT(*) DESC)
를 사용하여
가장 많이 사용한 리액션 자체를 가져오는 방식으로 해결했다.
2. 전체 리액션 수와 가장 많이 사용한 리액션 수는 서로 다른 집계이다.
이번 문제에서는
두 가지 집계가 필요했다.
첫 번째
좋아요 : 10
웃음 : 8
화남 : 5
처럼
리액션 종류별 개수
두 번째
전체 리액션
23개
이다.
처음에는 하나의 GROUP BY에서 모두 해결하려고 생각했지만,
생각해보면
GROUP BY user_id, reaction
을 하면
전체 리액션 수를 바로 알 수 없고,
반대로
GROUP BY user_id
를 하면
리액션별 개수를 알 수 없다.
그래서
집계를 두 번 해야 하는 문제라는 것을 이해했다.
3. 집계와 순위를 함께 사용하는 패턴을 익혔다.
이번 문제에서는
COUNT(*)
를 먼저 계산하고,
그 결과를 기준으로
RANK()
를 계산했다.
즉
집계
↓
순위
↓
1등 선택
이라는 흐름을 처음부터 떠올리는 것이 중요했다.
최종 해결 방법
1단계. 사용자별 리액션 종류별 개수를 계산한다.
먼저
좋아요
웃음
화남
각 리액션의 개수를 계산한다.
2단계. 가장 많이 사용한 리액션을 찾는다.
집계 결과를 기준으로
RANK()를 이용하여
사용자별 1등 리액션을 찾는다.
3단계. 전체 리액션 수를 계산한다.
비율 계산을 위해
사용자별 전체 리액션 수를 따로 계산한다.
4단계. 두 결과를 JOIN한다.
가장 많이 사용한 리액션 개수와
전체 리액션 수를 합친다.
5단계. 비율을 계산하고 조건을 적용한다.
가장 많이 사용한 리액션 수
/
전체 리액션 수
를 계산하여
60% 이상인 사용자만 남긴다.
정답 쿼리
-- emotionally consistent users 찾기
-- - 각 유저별 전체 리액션 수
-- - 5개 이상의 다른 컨텐츠에서 반응
-- - 리액션의 60% 이상이 같은 타입이면 emotionally consistent
-- order by reaction_ratio desc, user_id
WITH r AS (
SELECT
user_id,
reaction,
COUNT(reaction) AS cnt,
RANK() OVER(
PARTITION BY user_id
ORDER BY COUNT(*) DESC
) rn
FROM reactions
GROUP BY user_id, reaction
)
SELECT
r.user_id,
r.reaction AS dominant_reaction,
ROUND(SUM(cnt) / total_cnt,2) AS reaction_ratio
FROM r
JOIN (
SELECT
user_id,
COUNT(*) AS total_cnt
FROM reactions
GROUP BY user_id
HAVING COUNT(*)>=5
) a
ON r.user_id = a.user_id
WHERE rn=1
AND (cnt/total_cnt>=0.6)
GROUP BY user_id
ORDER BY reaction_ratio DESC,
user_id;
오답 및 시행착오
이번 문제에서 가장 중요한 포인트는
"최대값을 구하는 것"과 "최대값을 가진 행을 가져오는 것"은 다르다는 점이었다.
또한
리액션별 개수
↓
전체 리액션 수
는 서로 다른 집계이기 때문에
한 번의 GROUP BY로 해결하려고 하면 오히려 복잡해진다.
이번 문제를 통해
집계 목적이 다르면 GROUP BY도 분리해야 한다는 점을 다시 배웠다.
정답 풀이 해설
이 문제를 처음 봤다면 떠올려야 하는 사고 흐름
사용자별 리액션 종류별 개수 집계
↓
가장 많이 사용한 리액션 찾기(RANK)
↓
전체 리액션 수 계산
↓
두 결과 JOIN
↓
비율 계산
↓
조건 만족 사용자만 추출
이 문제의 핵심은
"그룹 내 최빈값 찾기 + 전체 대비 비율 계산"이다.
핵심 SQL 개념 정리
RANK() vs ROW_NUMBER()
RANK()
동점이 있으면 같은 순위를 부여한다.
10
10
8
↓
1
1
3
ROW_NUMBER()
무조건 순서를 부여한다.
10
10
8
↓
1
2
3
왜 RANK()가 적절한가?
이번 문제에서는
동일한 개수의 리액션이 여러 개라면
모두 가장 많이 사용한 리액션이 될 수 있으므로
RANK()가 적절하다.
이번 문제에서 배운 패턴
그룹별 집계
↓
집계 결과로 순위 생성
↓
1등 선택
↓
전체 집계와 JOIN
↓
비율 계산
↓
조건 필터링
비슷한 문제가 나오면 어떻게 접근할까?
다음과 같은 문제에서도 같은 패턴을 사용할 수 있다.
- 가장 많이 구매한 상품 찾기
- 가장 많이 이용한 카테고리 찾기
- 가장 많이 클릭한 메뉴 찾기
- 가장 자주 이용한 결제수단 찾기
공통적으로
"그룹별 최빈값 + 전체 대비 비율" 문제가 나오면
이번 패턴을 그대로 적용할 수 있다.
오늘 배운 점
최빈값을 구하는 문제에서는
단순히 MAX()를 찾는 것이 아니라
최대값을 가진 행을 가져와야 한다.
또한
전체 개수와 그룹별 개수는
집계 목적이 다르므로
분리해서 계산하는 것이 훨씬 자연스럽다.
다음에 같은 문제를 만나면
먼저
전체 개수가 필요한가?
↓
그룹별 개수가 필요한가?
↓
최빈값이 필요한가?
↓
RANK() 또는 ROW_NUMBER()를 사용할 수 있는가?
를 먼저 생각한다.
핵심 한 줄 요약
그룹 내 최빈값을 찾는 문제는 집계 → RANK() → 1등 선택 → 전체 집계 JOIN → 비율 계산 패턴으로 해결한다.
'TIL' 카테고리의 다른 글
| [TIL178] leetcode - Trips and Users (0) | 2026.06.27 |
|---|---|
| [TIL176] leetcode - 이탈 위험 고객(Churn Risk Customers) 찾기 (0) | 2026.06.26 |
| [TIL175] leetcode - Find Golden Hour Customers (0) | 2026.06.26 |
| [TIL174] leetcode - Find Books with Polarized Opinions (0) | 2026.06.25 |
| [TIL173] leetcode - Find Stores with Inventory Imbalance (0) | 2026.06.25 |