지금까지는 행을 골라내는 법을 배웠어요. 이번엔 여러 행을 하나의 값으로 뭉치는 집계와, 같은 값끼리 묶어서 요약하는 GROUP BY를 배웁니다. "몇 명이지? 평균은? 도시별로는?"에 답하는 무기예요.
COUNT(*)와 COUNT(열)의 차이(NULL 처리)를 설명할 수 있어요.GROUP BY로 같은 값끼리 묶어 그룹별 집계를 할 수 있어요.WHERE와 HAVING의 차이(그룹 전/후 필터)를 구분해요.집계 함수(aggregate function)는 여러 행을 훑어서 하나의 값으로 요약해요. "이 표에 몇 명이 있지?", "나이 평균은?" 같은 질문에 답하죠.
| id | name | city | age |
|---|---|---|---|
| 1 | '철수' | '서울' | 20 |
| 2 | '영희' | '서울' | 30 |
| 3 | '민수' | '부산' | 40 |
| 함수 | 하는 일 | 위 표 결과 |
|---|---|---|
COUNT(*) | 행의 개수 | 3 |
SUM(age) | 값의 합계 | 90 |
AVG(age) | 값의 평균 | 30 |
MAX(age) | 가장 큰 값 | 40 |
MIN(age) | 가장 작은 값 | 20 |
SELECT COUNT(*) FROM users; -- 전체 인원 수 → 3
SELECT AVG(age) FROM users; -- 나이 평균 → 30
SELECT는 여러 행을 훑어도 결과가 한 줄(요약값 하나)로 나와요. "표 전체를 하나의 숫자로 접는다"고 생각하면 쉬워요.COUNT는 쓰는 방식에 따라 결과가 달라져요. COUNT(*)는 행 자체를 전부 세지만, COUNT(열)은 그 열이 NULL이 아닌 행만 세요.
| id | name | phone |
|---|---|---|
| 1 | '철수' | '010-1111' |
| 2 | '영희' | NULL |
| 3 | '민수' | '010-3333' |
COUNT(*) → 3 — 행이 3개니까 그냥 3. NULL이든 아니든 행을 셈.COUNT(phone) → 2 — phone이 NULL인 영희는 빠짐. 값이 있는 철수·민수만 셈.
💡 즉 "전체 행 수"가 궁금하면 COUNT(*), "값이 채워진 행 수"가 궁금하면 COUNT(열)"를 써요.
SELECT COUNT(*) FROM members; -- 3 (모든 행)
SELECT COUNT(phone) FROM members; -- 2 (NULL 제외)
COUNT(DISTINCT 열)을 써요. 예: COUNT(DISTINCT city)는 서로 다른 도시가 몇 종류인지 알려줘요.COUNT(DISTINCT city)는 같은 도시가 여러 번 나와도 한 번씩만 세서, "값이 몇 개냐"가 아니라 "몇 종류냐"를 알려줘요.전체를 하나로 요약하는 게 아니라 "도시별로", "부서별로" 나눠서 요약하고 싶을 때 GROUP BY를 써요. 같은 값을 가진 행끼리 하나의 그룹으로 묶고, 그 그룹마다 집계 함수를 적용해요.
SELECT city, COUNT(*)
FROM users
GROUP BY city; -- 도시별 인원 수
앞의 users 표(서울 2명, 부산 1명)에 이 쿼리를 돌리면 도시마다 한 줄씩 결과가 나와요.
| city | COUNT(*) |
|---|---|
| '서울' | 2 |
| '부산' | 1 |
COUNT(*)를 적용 → 서울 2, 부산 1.
💡 "GROUP BY 한 열마다 결과가 한 줄"이라고 외우면 편해요. 여기선 도시가 2종류라 2줄이 나와요.
GROUP BY city, age처럼 여러 열로도 묶을 수 있어요. 이 경우 "도시 그리고 나이가 모두 같은" 행끼리 한 그룹이 돼요.그룹별 집계까지 마쳤는데, 이번엔 "그 집계 결과가 어떤 조건을 넘는 그룹만" 보고 싶을 때가 있어요. 예를 들어 "인원이 2명 이상인 도시만"처럼요. 그런데 이 조건은 여태 쓰던 WHERE로는 걸 수가 없어요 — 그래서 HAVING이 등장합니다.
WHERE·GROUP BY·HAVING·ORDER BY처럼 쿼리를 이루는 각 부분을 "절"이라고 불러요. 문장 속 구절처럼, 절마다 맡은 역할(거르기·묶기·정렬 등)이 정해져 있어요.WHERE로는 집계 결과에 조건을 걸 수 없어요(WHERE는 그룹 짓기 전, 개별 행에만 작동). 그룹으로 묶은 뒤, 집계 결과에 조건을 걸려면 HAVING을 써요.
SELECT city, COUNT(*)
FROM users
GROUP BY city
HAVING COUNT(*) >= 2; -- 인원이 2명 이상인 도시만
위 결과에서는 서울(2명)만 남고 부산(1명)은 걸러져요.
| 구분 | WHERE | HAVING |
|---|---|---|
| 거르는 대상 | 개별 행 | 그룹(집계 결과) |
| 실행 시점 | 그룹 짓기 전 | 그룹 지은 후 |
| 집계 함수 사용 | ❌ 못 씀 | ✅ 쓸 수 있음 |
| 예시 조건 | age >= 20 | COUNT(*) >= 2 |
WHERE(행 거르기) → GROUP BY(묶기) → HAVING(그룹 거르기) 순으로 처리돼요. 그래서 COUNT(*) >= 2 같은 조건은 WHERE에 못 넣고 HAVING에 넣어야 합니다.GROUP BY를 쓰면 SELECT 목록에는 원칙적으로 ① GROUP BY에 적은 열 또는 ② 집계 함수만 올 수 있어요. 그룹으로 묶였는데 묶이지 않은 개별 열을 그냥 적으면 "어느 행의 값을 보여줘야 하지?"가 애매해지기 때문이에요.
SELECT city, COUNT(*) FROM users GROUP BY city — city는 묶은 열, COUNT(*)는 집계. OK.SELECT city, name FROM users GROUP BY city — name은 묶지도, 집계하지도 않은 열. 서울 그룹에 철수·영희 둘인데 어느 name을?
💡 정리: "GROUP BY 한 열 + 집계 함수"만 SELECT에 올린다.
COUNT(개수)·SUM(합계)·AVG(평균)·MAX(최대)·MIN(최소). 여러 행을 하나의 값으로 요약.COUNT(*)는 행 전체, COUNT(열)은 NULL 아닌 행만 셈.GROUP BY: 같은 값끼리 묶어 그룹별 집계. GROUP BY 한 열마다 결과 한 줄.WHERE는 그룹 짓기 전(개별 행), HAVING은 그룹 지은 후(집계 결과)에 필터.