"30대가 몇 명이야?" — 묶어서 세는 SQL,
GROUP BY 와 "~별" 사고법
지금까지는 행을 한 줄씩 꺼내 봤다면, 오늘부터는 묶어서 셉니다. "카테고리별 상품 수", "성별 평균 나이", "회원별 주문 수" — 한국어에 "~별"이 들어가면 거의 100% GROUP BY 입니다. 비개발자가 실무에서 가장 많이 쓰게 될 SQL이 바로 이 강의에서 나옵니다.
이번 섹션의 여정
완료 ✓
지금 여기
그룹 조건
JOIN · 뻥튀기 함정 ⭐ · CASE
강의 메인
행 단위에서 그룹 단위로 — 진짜 분석의 시작
지금까지의 질문은 전부 행 단위였습니다 — “30대 회원 보여줘” 하면 행이 줄줄이 나왔죠. 그런데 실무에서 진짜 자주 듣는 질문은 결이 다릅니다.
”30대 회원 보여줘”
조건에 맞는 행 목록이 답입니다. SELECT + WHERE 로 충분했어요 — 3-1 ~ 3-2에서 끝낸 이야기.
”30대가 몇 명이야?”
행 목록이 아니라 묶어서 계산한 숫자가 답입니다. “카테고리별 평균 가격은?”, “회원별 주문 수는?” — 전부 GROUP BY 의 영역이에요.
SELECT 문의 뼈대에서 오늘 채우는 칸은 여기입니다.
집계 함수 5종 — 이름 그대로입니다
묶기 전에, 계산하는 도구부터. 딱 다섯 개입니다.
| 함수 | 의미 |
|---|---|
COUNT(*) | 행 개수 |
SUM(컬럼) | 합계 |
AVG(컬럼) | 평균 |
MIN(컬럼) | 최솟값 |
MAX(컬럼) | 최댓값 |
count, sum, average, minimum, maximum — 정말 이름 그대로예요. 바로 쳐봅시다.
SELECT
COUNT(*)
FROM
members;
SELECT
AVG(age),
MIN(age),
MAX(age)
FROM
members;| COUNT(*) |
|---|
| 20 |
| AVG(age) | MIN(age) | MAX(age) |
|---|---|---|
| 32.9000 | 21 | 50 |
행 20줄이 아니라 계산된 숫자 한 줄이 나왔죠. 이게 집계입니다.
COUNT(*) 의 * 는 “NULL 포함, 모든 행을 세라” 는 뜻입니다. 가장 자주
쓰는 형태이니 이대로 손에 익혀두세요.
GROUP BY — “~별” 사고법 ⭐
이제 오늘의 핵심. 집계 함수에 GROUP BY 를 붙이면, 전체가 아니라 그룹마다 계산합니다.
SELECT
category_id,
COUNT(*) AS 상품수
FROM
products
GROUP BY
category_id;| category_id | 상품수 |
|---|---|
| 1 | 8 |
| 2 | 7 |
| 3 | 6 |
| 4 | 5 |
| 5 | 4 |
상품 30개가 카테고리 5묶음으로 접히면서, 묶음마다 COUNT(*) 가 한 번씩 돌았습니다. 그리고 여기서 이 강의 전체의 핵심 한 줄 —
한국어에 “~별” 이 보이면 → GROUP BY. “성별 평균 나이”, “날짜별 주문 수”, “카테고리별 매출” — 전부 같은 패턴입니다. 거의 100% 입니다.
| 한국어 질문 | SQL 번역 |
|---|---|
| 성별 평균 나이는? | GROUP BY gender + AVG(age) |
| 날짜별 주문 수는? | GROUP BY ordered_at + COUNT(*) |
| 카테고리별 매출은? | GROUP BY category_id + SUM(...) |
번역 연습 두 개만 직접 쳐봅시다.
-- 성별 평균 나이
SELECT
gender,
AVG(age) AS 평균나이
FROM
members
GROUP BY
gender;
-- 회원별 주문 수
SELECT
member_id,
COUNT(*) AS 주문수
FROM
orders
GROUP BY
member_id;| gender | 평균나이 |
|---|---|
| M | 34.2000 |
| F | 31.6000 |
회원은 20명인데 결과는 16행이에요. orders 테이블만 보고 묶었으니,
주문이 한 건도 없는 회원 4명은 아예 등장하지 않습니다. 고장이 아니라
당연한 동작이고 — 이 4명까지 포함해 세는 방법(LEFT JOIN)은 3-6에서 배웁니다.
이게 데이터 분석의 70% 입니다. 회사에서 오가는 “~별로 얼마나?” 류의 질문 대부분이, 방금 친 GROUP BY 한 덩어리로 끝나요.
GROUP BY 의 규칙 — SELECT 절 제한 ⚠️
여기서 함정 하나. 그대로 따라 쳐보세요 — 일부러 에러를 내볼 겁니다.
SELECT
member_id,
ordered_at,
COUNT(*)
FROM
orders
GROUP BY
member_id;규칙은 이겁니다 — SELECT 절에는 GROUP BY 에 적은 컬럼 + 집계 함수만 쓸 수 있어요. ordered_at 은 GROUP BY 에도 없고 집계 함수도 아닌데 SELECT 에 있어서 에러가 난 거죠.
생각해보면 당연합니다. “회원별로 묶었는데, 그 그룹의 주문일이 뭐냐” — 김철수 한 그룹 안에 주문일이 6개나 들어 있는데, DB 더러 “하나만 골라줘” 라고 할 수는 없으니까요.
GROUP BY 에 없는 컬럼을 SELECT
한 그룹 안에 값이 여러 개일 수 있어서 모호함 → 에러. MySQL 이 Error 1055 로 거부합니다.
GROUP BY 에 넣거나, 집계로 감싸거나
보고 싶은 컬럼을 GROUP BY 에 같이 넣거나, MAX(ordered_at) 처럼 집계 함수로 감싸서 그룹당 값이 하나가 되게 만듭니다.
예를 들어 “회원별 주문 수에 이름도 같이 보고 싶다” 면, 이름을 GROUP BY 에 같이 넣으면 됩니다 (회원ID 와 이름은 1:1 이라 안전).
SELECT
m.member_id,
m.member_name,
COUNT(*) AS 주문수
FROM
orders o
JOIN members m
ON o.member_id = m.member_id
GROUP BY
m.member_id,
m.member_name;두 테이블을 붙이는 JOIN 은 3-6에서 제대로 배웁니다. 지금은 “GROUP BY 에
적은 컬럼 외엔 SELECT 못 한다” 한 줄만 외워두세요.
AI 도 자주 틀리는 지점입니다. AI 가 만들어준 SQL 에 GROUP BY 가 보이면, SELECT 절에 GROUP BY 에 없는 컬럼이 끼어 있지 않은지 한 번 점검하세요 — 그대로 돌리면 Error 1055 가 납니다.
”TOP N” 패턴 — 3-1 ~ 3-4 가 합쳐지는 순간
GROUP BY 결과에 3-3에서 배운 ORDER BY 를 붙이면, 진짜 “분석 보고서” 가 됩니다.
-- 카테고리별 상품 수 — 많은 순으로
SELECT
category_id,
COUNT(*) AS 상품수
FROM
products
GROUP BY
category_id
ORDER BY
상품수 DESC;ORDER BY 상품수 DESC — SELECT 에서 붙인 별칭(AS 상품수)을 ORDER BY 에
그대로 쓸 수 있습니다. 편해요.
여기에 LIMIT 까지 — “가장 주문 많이 한 회원 TOP 5”, 일명 VIP 고객 보고서입니다.
SELECT
member_id,
COUNT(*) AS 주문수
FROM
orders
GROUP BY
member_id
ORDER BY
주문수 DESC
LIMIT 5;| member_id | 주문수 |
|---|---|
| 1 | 6 |
| 2 | 4 |
| 9 | 4 |
| 3 | 3 |
| 4 | 3 |
주문 3건인 회원이 4명(3·4·6·12번)이라 마지막 두 자리는 실행할 때마다 다른 회원이 나올 수 있습니다. 3-3에서 배운 그대로 — 동률의 순서는 보장되지 않아요.
SELECT(3-1) + GROUP BY(3-4) + ORDER BY + LIMIT(3-3). 지금까지 배운 게 전부 들어간 한 쿼리입니다. “TOP N” 패턴 — 실무 보고서의 표준 답안이에요.
한눈에 정리
| 키워드 | 의미 | 예시 |
|---|---|---|
COUNT(*) | 행 개수 (NULL 포함) | SELECT COUNT(*) FROM members; |
SUM(c) | 합계 | SELECT SUM(price) FROM products; |
AVG(c) | 평균 | SELECT AVG(age) FROM members; |
MIN(c) MAX(c) | 최솟값 · 최댓값 | SELECT MIN(price), MAX(price) FROM products; |
GROUP BY c | ”c별” 묶기 | SELECT c, COUNT(*) FROM t GROUP BY c; |
| 규칙 | SELECT 에는 GROUP BY 컬럼 + 집계만 | 어기면 Error 1055 |
| GROUP BY + ORDER BY + LIMIT | ”TOP N” 패턴 | VIP 고객 TOP 5 |
“~별” 단어가 보이면 → GROUP BY. 이 한 줄만 몸에 새겨도, 데이터 분석 질문의 대부분이 SQL 로 번역됩니다.
자주 막히는 지점
오늘 배운 바로 그 규칙 위반입니다. MySQL 기본 설정(only_full_group_by)이 모호한 쿼리를 막아주는 거예요 — 에러가 나는 게 정상이고, 고마운 일입니다.
MAX() 같은 집계 함수로 감싸세요. SELECT 절에 GROUP BY 에 없는 컬럼이 끼어 있다는 신호입니다.GROUP BY 는 묶기만 할 뿐 정렬을 보장하지 않습니다. 순서가 중요하면 ORDER BY 는 항상 직접 붙이는 습관을 들이세요.
ORDER BY 상품수 DESC 처럼 별칭도 됩니다.WHERE 는 묶기 전의 행에 거는 조건이라, 집계 함수를 넣을 수 없습니다. “주문 3건 넘는 회원만” 처럼 그룹에 거는 조건은 따로 있어요.
“~별” 이 보이면 GROUP BY — 오늘은 이 한 줄이면 충분합니다. 연습 문제로 손에 익히고 나면, 다음 3-5에서는 방금 에러 났던 그 질문 — “상품이 5개 이상인 카테고리만 보고 싶다” 처럼 그룹에 조건을 거는 HAVING 으로 넘어갑니다.