class="layout-aside-right paging-number">
본문 바로가기
TIL

[TIL166] solvesql - Facebook 친구 수 집계하기

by heestory323 2026. 6. 23.

문제 설명

Facebook 사용자 간 친구 관계가 edges 테이블에 저장되어 있다.

여기서 한 행은

user_a_id ↔ user_b_id

관계를 의미한다.

즉,

1 | 2

가 있다면

1의 친구는 2
2의 친구는 1

이라는 의미다.

 

문제에서는 데이터베이스에 존재하는 모든 사용자에 대해 친구 수를 집계해야 한다.

핵심 조건은 다음과 같다.

  • 친구 수가 많은 순으로 정렬
  • 친구 수가 같으면 user_id 오름차순
  • 친구가 없는 사용자도 결과에 포함

출력 컬럼

  • user_id
  • num_friends

내가 처음 생각한 접근

처음 문제를 봤을 때는

"친구 관계니까 셀프 조인(Self Join) 해야 하나?"

라는 생각이 들었다.

왜냐하면 친구 관계는 같은 사용자 테이블끼리 연결되는 구조처럼 보였기 때문이다.

 

하지만 조금 더 생각해보니 실제로 필요한 것은

사용자 정보
+
친구 관계 수

였다.

즉 사용자끼리 다시 연결하는 것이 아니라,

친구 관계 테이블을 사용자 기준으로 집계하면 되는 문제였다.


내가 헷갈렸던 부분

1. UNION ALL 하면 중복이 생기는 것 아닌가?

처음에는 가장 의심했던 부분이 여기였다.

SELECT user_a_id AS a,
       user_b_id AS b
FROM edges

UNION ALL

SELECT user_b_id AS a,
       user_a_id AS b
FROM edges

를 보면

 

같은 데이터가 두 번 들어가는 것처럼 보인다.

하지만 실제로는 중복 생성이 아니라

무방향 관계를 양방향 관계로 변환하는 과정이다.

 

예를 들어

1 ↔ 2

라는 관계는

실제로는

1 → 2
2 → 1

로 생각해야 한다.

 

친구 수를 세려면 양쪽 모두에서 조회 가능해야 하므로 UNION ALL이 필요하다.

 

즉 여기서 생성되는 데이터는 중복이 아니라, 친구 관계를 집계하기 위한 확장 데이터이다.


2. 왜 COUNT(u.user_id)는 틀렸을까?

이 부분이 실제 오답 원인이었다.

처음에는

COUNT(u.user_id)

를 사용했다.

 

그런데 문제는 LEFT JOIN 이였다.

 

친구가 없는 사용자의 경우,

user_id = 10
friend = NULL

형태가 된다.

 

이때 COUNT(u.user_id) 는 1을 반환한다.

왜냐하면 u.user_id = 10 이므로, NULL 값은 아니기 때문이다.

-> 즉 친구가 없어도 1명으로 계산되는 문제가 발생한다.


정답은

COUNT(f.a)

또는

COUNT(f.b)

를 사용하는 것이다.

 

이 경우 NULL은 COUNT에서 제외되므로
친구가 없는 사용자는 0명으로 올바르게 계산된다.


최종 해결 방법

1단계. 친구 관계를 양방향으로 변환

원본 데이터는

A ↔ B

형태이다.

이를

A → B
B → A

형태로 변환한다.

 

이를 위해 UNION ALL 사용.


2단계. 모든 사용자 유지

문제에서 "모든 사용자" 라고 했기 때문에, 친구가 없는 사용자도 포함해야 한다.

LEFT JOIN

을 사용한다.


3단계. 친구 수 집계

친구 관계가 존재하는 컬럼을 기준으로 COUNT 수행

COUNT(f.a)

4단계. 정렬

조건에 따라 적용

ORDER BY num_friends DESC,
         user_id ASC

정답 쿼리

WITH friends AS (

    SELECT
        user_a_id AS a,
        user_b_id AS b
    FROM edges

    UNION ALL

    SELECT
        user_b_id AS a,
        user_a_id AS b
    FROM edges

)

SELECT
    u.user_id,
    COUNT(f.a) AS num_friends
FROM users u

LEFT JOIN friends f
    ON u.user_id = f.a

GROUP BY u.user_id

ORDER BY num_friends DESC,
         u.user_id;

핵심 SQL 개념 정리

UNION ALL

정의

  • 여러 SELECT 결과를 합치는 연산

사용 목적

  • 데이터 방향 확장
  • 데이터 누적

실무 활용

  • 친구 관계
  • 네트워크 연결
  • 이동 경로
  • 그래프 데이터 분석

이번 문제에서는

무방향 관계를 양방향 관계로 변환하기 위해 사용했다.


LEFT JOIN

정의

  • 왼쪽 테이블의 모든 데이터를 유지하는 조인

사용 목적

  • 매칭 데이터가 없어도 결과 유지

실무 활용

  • 회원 전체 목록
  • 전체 상품 목록
  • 전체 사용자 집계

이번 문제에서는

친구가 없는 사용자도 포함하기 위해 사용했다.


COUNT()

정의

  • 행 개수 집계 함수

 

주의점

COUNT(*)

COUNT(column)

은 다르게 동작한다.

COUNT(column)은 NULL을 세지 않는다.

실무 활용

  • 주문 수
  • 친구 수
  • 방문 수
  • 클릭 수

이번 문제에서는

LEFT JOIN 이후 NULL 처리 여부가 정답을 결정하는 핵심 포인트였다.


이번 문제에서 배운 패턴

무방향 관계 확인

UNION ALL로 양방향 변환

모든 사용자 유지 (LEFT JOIN)

관계 컬럼 기준 COUNT

정렬


한 줄 요약

친구 관계처럼 무방향 관계를 집계하는 문제는 UNION ALL로 양방향 데이터로 변환한 뒤, LEFT JOIN + COUNT(조인 컬럼) 패턴으로 해결한다.