같은 개체(id, name)가 여러 개의 값을 가지고 있을 때
이를 한 행에 합쳐서 출력하려고 할때 !!
특히 메달, 태그, 카테고리, 옵션 목록처럼 하나의 주체가 여러 값을 가질 수 있는 구조에서 자주 사용된다.
이 글에서는 GROUP BY와 GROUP_CONCAT을 활용해 이 문제를 정리한다.
아래는 단순화한 예시 테이블이다.
participation (
athlete_id INT,
medal VARCHAR
)
athlete_info (
id INT,
name VARCHAR
)
한 선수가 여러 메달을 가진 상황을 가정하자.
예시 데이터:
| athlete_id | name | medal |
|---|---|---|
| 10 | Hana | bronze |
| 10 | Hana | bronze |
| 10 | Hana | gold |
| 12 | Seulgi | silver |
이 경우 원하는 출력 형태는 다음 두 가지 중 하나다.
bronze, bronze, goldbronze, gold처음에 흔히 작성하는 쿼리는 다음 형태다.
SELECT
a.id,
a.name,
p.medal
FROM participation p
JOIN athlete_info a ON p.athlete_id = a.id;
이렇게 하면 메달이 여러 개인 개체는 여러 행으로 출력된다.
| id | name | medal |
|---|---|---|
| 10 | Hana | bronze |
| 10 | Hana | bronze |
| 10 | Hana | gold |
원하는 것은 한 행당 한 선수, 즉 “행 집계”다.
SELECT
a.id,
a.name,
GROUP_CONCAT(p.medal ORDER BY p.medal SEPARATOR ', ') AS medals
FROM participation p
JOIN athlete_info a ON p.athlete_id = a.id
GROUP BY a.id, a.name;
출력:
| id | name | medals |
|---|---|---|
| 10 | Hana | bronze, bronze, gold |
- GROUP_CONCAT은 기본적으로 중복을 제거하지 않는다.
- 원본 데이터의 모든 값이 그대로 나온다.
중복 없이 종류만 보고 싶은 경우는 DISTINCT를 써야 한다.
SELECT
a.id,
a.name,
GROUP_CONCAT(DISTINCT p.medal ORDER BY p.medal SEPARATOR ', ') AS medals
FROM participation p
JOIN athlete_info a ON p.athlete_id = a.id
GROUP BY a.id, a.name;
출력:
| id | name | medals |
|---|---|---|
| 10 | Hana | bronze, gold |
- DISTINCT 사용 시 문자열 합치기 전에 중복이 제거된다.
| DB | 중복 포함 | 중복 제거 |
|---|---|---|
| MySQL | GROUP_CONCAT(p.medal) | GROUP_CONCAT(DISTINCT p.medal) |
| PostgreSQL | STRING_AGG(p.medal, ', ') | STRING_AGG(DISTINCT p.medal, ', ') |
단순한 JOIN으로 해결되는 문제처럼 보여도,
여러 값을 한 행에 집계해야 하는 상황은 결국 GROUP BY + GROUP_CONCAT 조합이 가장 깔끔하다.
특히 “중복을 허용할지, 제거할지”를 명확히 구분해 두면 실수할 일이 줄어든다.