집계 함수 COUNT(*) vs COUNT(열) GROUP BY HAVING SELECT 규칙 📝 문제풀이
◀ 이전 📋 목차 CH 06 ▶
📊 CHAPTER 05 · 중급

집계 함수와 GROUP BY로 데이터 요약하기

지금까지는 행을 골라내는 법을 배웠어요. 이번엔 여러 행을 하나의 값으로 뭉치는 집계와, 같은 값끼리 묶어서 요약하는 GROUP BY를 배웁니다. "몇 명이지? 평균은? 도시별로는?"에 답하는 무기예요.

🎯 이 장을 끝내면
🧮
집계 함수
여러 행을 하나의 값으로 요약하기
"개수·합계·평균·최대·최소" — 다섯 가지만 알면 돼요.

집계 함수(aggregate function)여러 행을 훑어서 하나의 값으로 요약해요. "이 표에 몇 명이 있지?", "나이 평균은?" 같은 질문에 답하죠.

users 테이블 (예시)
idnamecityage
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(*) vs COUNT(열)
헷갈리는 COUNT의 두 얼굴
NULL을 세느냐 마느냐 — 여기서 결과가 갈려요.

COUNT는 쓰는 방식에 따라 결과가 달라져요. COUNT(*)는 행 자체를 전부 세지만, COUNT(열)은 그 열이 NULL이 아닌 행만 세요.

members 테이블 (phone에 NULL 존재)
idnamephone
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)서로 다른 도시가 몇 종류인지 알려줘요.
🔤
DISTINCT — "중복을 빼고"라는 뜻의 키워드예요. COUNT(DISTINCT city)는 같은 도시가 여러 번 나와도 한 번씩만 세서, "값이 몇 개냐"가 아니라 "몇 종류냐"를 알려줘요.
🧺
GROUP BY
같은 값끼리 묶어서 그룹별로 집계
"전체 평균" 말고 "그룹별 평균"이 필요할 때.

전체를 하나로 요약하는 게 아니라 "도시별로", "부서별로" 나눠서 요약하고 싶을 때 GROUP BY를 써요. 같은 값을 가진 행끼리 하나의 그룹으로 묶고, 그 그룹마다 집계 함수를 적용해요.

SELECT city, COUNT(*)
FROM users
GROUP BY city;        -- 도시별 인원 수

앞의 users 표(서울 2명, 부산 1명)에 이 쿼리를 돌리면 도시마다 한 줄씩 결과가 나와요.

결과: 도시별 인원
cityCOUNT(*)
'서울'2
'부산'1
🧺 GROUP BY의 그림 철수(서울)·영희(서울)·민수(부산)를 city 값으로 묶으면{서울: 철수·영희}, {부산: 민수} 두 그룹.
각 그룹에 COUNT(*)를 적용 → 서울 2, 부산 1. 💡 "GROUP BY 한 열마다 결과가 한 줄"이라고 외우면 편해요. 여기선 도시가 2종류라 2줄이 나와요.
🧭
GROUP BY city, age처럼 여러 열로도 묶을 수 있어요. 이 경우 "도시 그리고 나이가 모두 같은" 행끼리 한 그룹이 돼요.
🚦
HAVING
그룹에 조건을 걸고 싶다면 HAVING
WHERE는 그룹 짓기 전, HAVING은 그룹 지은 후.

그룹별 집계까지 마쳤는데, 이번엔 "그 집계 결과가 어떤 조건을 넘는 그룹만" 보고 싶을 때가 있어요. 예를 들어 "인원이 2명 이상인 도시만"처럼요. 그런데 이 조건은 여태 쓰던 WHERE로는 걸 수가 없어요 — 그래서 HAVING이 등장합니다.

🔤
절(clause) — WHERE·GROUP BY·HAVING·ORDER BY처럼 쿼리를 이루는 각 부분을 "절"이라고 불러요. 문장 속 구절처럼, 절마다 맡은 역할(거르기·묶기·정렬 등)이 정해져 있어요.

WHERE로는 집계 결과에 조건을 걸 수 없어요(WHERE는 그룹 짓기 전, 개별 행에만 작동). 그룹으로 묶은 , 집계 결과에 조건을 걸려면 HAVING을 써요.

SELECT city, COUNT(*)
FROM users
GROUP BY city
HAVING COUNT(*) >= 2;   -- 인원이 2명 이상인 도시만

위 결과에서는 서울(2명)만 남고 부산(1명)은 걸러져요.

구분WHEREHAVING
거르는 대상개별 행그룹(집계 결과)
실행 시점그룹 짓기 그룹 지은
집계 함수 사용❌ 못 씀✅ 쓸 수 있음
예시 조건age >= 20COUNT(*) >= 2
⚠️
실행 순서를 기억하세요. SQL은 대략 WHERE(행 거르기) → GROUP BY(묶기) → HAVING(그룹 거르기) 순으로 처리돼요. 그래서 COUNT(*) >= 2 같은 조건은 WHERE에 못 넣고 HAVING에 넣어야 합니다.
📋
SELECT 규칙
GROUP BY 쓸 때 SELECT에 올 수 있는 것
아무 열이나 막 못 적어요 — 규칙 하나만.

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에 올린다.
💡
MySQL 일부 설정에선 이 규칙이 느슨해 오류 없이 아무 값이나 나오기도 하지만, 결과가 뒤죽박죽일 수 있어요. 표준을 따르는 습관이 안전합니다.
🧠 이 장 핵심 요약
📝
문제풀이 · 점검
배운 걸 가볍게 점검해봐요
시험이 아니라 "내가 이해했나" 확인용이에요. 틀려도 바로 해설이 나와요.
🧪
문제를 풀면 즉시 정답과 해설이 나오고, 위쪽 바에 점수가 쌓여요. 네 가지 유형(객관식 · O/X · 빈칸 · 쿼리결과)을 섞어 두었어요.
CH 06 🔗 JOIN — 여러 테이블을 연결해서 한 번에 조회하기