Search
moon
sun

11.GROUP BY와 집계함수

•
5대 집계함수 COUNT · SUM · AVG · MAX · MIN 을 사용할 수 있다.
•
COUNT(*) · COUNT(컬럼) · COUNT(DISTINCT 컬럼) 의 차이를 설명할 수 있다.
•
AVG 가 NULL을 분모에서 제외한다는 것을 계산으로 증명할 수 있다.
•
GROUP BY 로 데이터를 묶고, SELECT 절 작성 규칙을 지킬 수 있다.
•
WHERE 와 HAVING 의 차이를 실행 순서로 설명할 수 있다.

1. 집계함수 개요

1) 여러 행을 하나로 묶는다

구분
단일행 함수 (09·10강)
집계함수(다중행 함수)
입력 → 출력
N행 → N행
N행 → 1행
예
UPPER(ENAME)
SUM(SAL)
EMP 14건에 적용
14건 출력
1건 출력

2) 5대 집계함수

함수
기능
대상 타입
COUNT
행의 개수
모든 타입
SUM
합계
숫자만
AVG
평균
숫자만
MAX
최댓값
숫자·문자·날짜
MIN
최솟값
숫자·문자·날짜
⭐ MAX MIN 은 문자·날짜에도 됩니다. 문자는 사전 순, 날짜는 시간 순으로 비교합니다.

2. 집계함수와 NULL — "무시" 가 대원칙 ⭐⭐⭐

1) 집계함수는 NULL을 계산에서 제외한다

-- ============================================================ -- 【시험용 예제】 EMP 의 커미션(COMM) 으로 5대 함수를 모두 적용 ★★ -- COMM 은 14명 중 4명만 값이 있고, 나머지 10명은 NULL 이다. -- ============================================================ SELECT COUNT(*) AS "행 개수", -- 전체 행 수 COUNT(COMM) AS "COMM 개수", -- COMM 이 NULL이 아닌 행 수 SUM(COMM) AS "COMM 합계", AVG(COMM) AS "COMM 평균", MAX(COMM) AS "COMM 최대", MIN(COMM) AS "COMM 최소" FROM EMP; -- 행 개수 COMM 개수 COMM 합계 COMM 평균 COMM 최대 COMM 최소 -- ------- --------- --------- --------- --------- --------- -- 14 4 2200 550 1400 0 -- ↑ ↑ -- NULL 10건은 세지 않음 2200 ÷ 4 = 550 (NULL 제외!) -- -- ⭐ 핵심 : SUM, AVG, MAX, MIN, COUNT(컬럼) 은 모두 NULL 을 '무시' 한다. -- 유일한 예외가 COUNT(*) 이다 (다음 절).
SQL
복사
커미션을 받는 4명만 뽑아보면 계산이 확인됩니다
사원
COMM
ALLEN
300
WARD
500
MARTIN
1400
TURNER
0 ← 0도 값이므로 포함
합계
2200
개수
4
평균
2200 ÷ 4 = 550
⭐ TURNER 의 커미션 0 은 NULL이 아니므로 계산에 포함됩니다. NULL인 나머지 10명만 빠집니다.

3. COUNT 의 세 가지 얼굴 ⭐⭐⭐

-- ============================================================ -- 【시험용 예제】 COUNT 세 가지의 결과가 모두 다르다 ★★★ -- ============================================================ SELECT COUNT(*) AS "전체 행", -- 14 COUNT(COMM) AS "COMM 있는 행", -- 4 (NULL 제외) COUNT(DISTINCT DEPTNO) AS "부서 종류", -- 3 (10,20,30) COUNT(DISTINCT JOB) AS "직무 종류" -- 5 FROM EMP; -- 전체 행 COMM 있는 행 부서 종류 직무 종류 -- ------- ------------ --------- --------- -- 14 4 3 5 -- -- ⭐ 정리 -- COUNT(*) → 무조건 전체 행 (NULL 신경 안 씀) -- COUNT(COMM) → COMM 이 있는 행만 = 14 - (NULL 10건) = 4 -- COUNT(DISTINCT DEPTNO)→ 서로 다른 부서번호의 개수 (40번은 사원이 없어 안 나옴)
SQL
복사
표현
세는 대상
NULL
COUNT(*)
모든 행
포함 ⭐
COUNT(COMM)
COMM이 NULL이 아닌 행
제외
COUNT(DISTINCT COMM)
COMM의 서로 다른 값
제외
⭐ COUNT(*) 만 NULL을 셉니다. 나머지는 전부 NULL을 제외합니다. "행이 몇 개인가"를 물으면 COUNT(*), "값이 몇 개 있나"를 물으면 COUNT(컬럼) 입니다.

4. AVG 와 NULL 분모 함정 ⭐⭐⭐

🔥 이 단원 최고 빈출 문제입니다. 분모가 몇이냐로 답이 완전히 갈립니다.

1) AVG(COMM) 은 14가 아니라 4로 나눈다

-- ============================================================ -- 【시험용 예제】 같은 데이터, 다른 분모 → 다른 답 ★★★ -- ============================================================ SELECT AVG(COMM) AS "AVG(COMM)", -- NULL 제외 → 분모 4 AVG(NVL(COMM, 0)) AS "AVG(NVL(COMM,0))", -- NULL을 0으로 → 분모 14 SUM(COMM) AS "SUM", -- 2200 (둘 다 동일) SUM(COMM) / COUNT(*) AS "SUM/COUNT(*)", -- 2200 ÷ 14 SUM(COMM) / COUNT(COMM) AS "SUM/COUNT(COMM)" -- 2200 ÷ 4 FROM EMP; -- AVG(COMM) AVG(NVL(COMM,0)) SUM SUM/COUNT(*) SUM/COUNT(COMM) -- --------- ---------------- ---- ------------ --------------- -- 550 157 2200 157 550 -- -- ⭐ 결론 -- AVG(COMM) = SUM(COMM) / COUNT(COMM) ← 분모 4 -- AVG(NVL(COMM,0)) = SUM(COMM) / COUNT(*) ← 분모 14 -- 분자(SUM)는 같지만 분모가 다르다!
SQL
복사
계산식
분자
분모
결과
AVG(COMM)
2200
4 (NULL 제외)
550
AVG(NVL(COMM, 0))
2200
14 (NULL→0으로 참여)
≈157
SUM(COMM) / COUNT(*)
2200
14
≈157
SUM(COMM) / COUNT(COMM)
2200
4
550
⚠️ 시험 함정: "커미션의 평균을 구하라"고 할 때
•
순수 평균 (커미션 받는 사람 기준) → AVG(COMM) = 550
•
전체 사원 기준 평균 (안 받는 사람을 0으로) → AVG(NVL(COMM,0)) ≈ 157
문제가 어느 쪽을 원하는지 반드시 확인해야 합니다.

5. GROUP BY — 그룹으로 묶기 ⭐⭐

1) 부서별로 묶어서 집계

-- ============================================================ -- 【이해용 예제】 부서별 인원수와 급여 합계 -- ============================================================ SELECT DEPTNO AS 부서번호, COUNT(*) AS 인원수, -- 그룹마다 행 수를 센다 SUM(SAL) AS 급여합계, ROUND(AVG(SAL)) AS 평균급여 FROM EMP GROUP BY DEPTNO -- DEPTNO 값이 같은 행끼리 묶는다 ORDER BY DEPTNO; -- 부서번호 인원수 급여합계 평균급여 -- -------- ------ -------- -------- -- 10 3 8750 2917 -- 20 5 10875 2175 -- 30 6 9400 1567 -- -- ⭐ GROUP BY DEPTNO → 부서 3개 그룹 → 결과 3행 (14행이 3행으로 압축됨)
SQL
복사

2) ⚠️ SELECT 절의 철칙 ⭐⭐⭐

GROUP BY를 쓰면, SELECT 에는 ①GROUP BY 컬럼 또는 ②집계함수만 올 수 있다.
-- ============================================================ -- 【시험용 예제】 그룹 컬럼이 아닌 일반 컬럼은 SELECT 에 못 온다 ★★ -- ============================================================ -- ❌ 오류: ENAME 은 GROUP BY 에도 없고 집계함수도 아니다 SELECT DEPTNO, ENAME, SUM(SAL) FROM EMP GROUP BY DEPTNO; -- ORA-00979: not a GROUP BY expression -- → 한 부서에 사원이 여러 명인데 ENAME 을 무엇으로 보여줘야 할지 알 수 없다! -- (10번 그룹의 ENAME 은 CLARK? KING? MILLER? → 결정 불가) -- ✅ 해결 ① ENAME 을 빼거나 SELECT DEPTNO, SUM(SAL) FROM EMP GROUP BY DEPTNO; -- ✅ 해결 ② ENAME 도 GROUP BY 에 넣는다 (그러면 그룹 기준이 달라진다) SELECT DEPTNO, ENAME, SAL FROM EMP GROUP BY DEPTNO, ENAME, SAL;
SQL
복사
⭐ 왜 안 되는가? 한 그룹은 여러 행이 뭉친 것인데, 일반 컬럼은 행마다 값이 달라 하나로 표시할 수 없기 때문입니다. 반면 집계함수는 그룹을 하나의 값으로 요약하므로 괜찮습니다.

3) GROUP BY 컬럼은 SELECT에 없어도 된다

-- SELECT 에 DEPTNO 가 없어도 GROUP BY 는 정상 동작한다 (반대는 불가) SELECT SUM(SAL) AS 급여합계 FROM EMP GROUP BY DEPTNO; -- 부서별로 묶지만, 어느 부서인지는 표시하지 않음 -- 급여합계 -- -------- -- 8750 -- 10875 -- 9400 → 3행 (어느 부서인지 알 수 없어 실용성은 낮다)
SQL
복사

6. HAVING — 그룹을 걸러내기 ⭐⭐⭐

1) WHERE 로는 집계함수를 조건에 쓸 수 없다

-- ============================================================ -- 【시험용 예제】 "급여합계가 9000 초과인 부서" 를 찾을 때 ★★★ -- ============================================================ -- ❌ WHERE 에 집계함수를 쓸 수 없다 SELECT DEPTNO, SUM(SAL) FROM EMP WHERE SUM(SAL) > 9000 -- ORA-00934: group function is not allowed here GROUP BY DEPTNO; -- → WHERE 는 '그룹으로 묶기 전' 에 실행되므로, 아직 SUM(SAL) 이 존재하지 않는다! -- ✅ 그룹에 대한 조건은 HAVING 을 쓴다 SELECT DEPTNO, SUM(SAL) AS 급여합계 FROM EMP GROUP BY DEPTNO HAVING SUM(SAL) > 9000; -- 그룹핑 후, 합계로 그룹을 거른다 -- DEPTNO 급여합계 -- ------ -------- -- 20 10875 -- 30 9400 → 10번(8750)은 9000 이하라 제외됨
SQL
복사

2) WHERE vs HAVING — 실행 순서로 이해 ⭐⭐⭐

WHERE
HAVING
실행 시점
그룹핑 전
그룹핑 후
필터 대상
개별 행
그룹
집계함수
❌ 쓸 수 없음
✅ 쓸 수 있음
예
WHERE SAL > 1000
HAVING SUM(SAL) > 9000
-- ============================================================ -- 【시험용 예제】 WHERE 와 HAVING 을 함께 쓰기 ★★ -- "급여 1000 이상인 사원들만 대상으로, 부서별 인원이 3명 이상인 부서" -- ============================================================ SELECT DEPTNO, COUNT(*) AS 인원수, SUM(SAL) AS 급여합계 FROM EMP WHERE SAL >= 1000 -- ① 먼저 급여 1000 미만인 개별 행을 제거 (SMITH 800, JAMES 950 탈락) GROUP BY DEPTNO -- ② 남은 행을 부서별로 묶고 HAVING COUNT(*) >= 3 -- ③ 인원이 3명 이상인 그룹만 남긴다 ORDER BY DEPTNO; -- DEPTNO 인원수 급여합계 -- ------ ------ -------- -- 10 3 8750 ← 3명 모두 1000 이상이라 그대로 통과 -- 20 4 10075 ← SMITH(800) 제외되어 5명→4명 -- 30 5 8450 ← JAMES(950) 제외되어 6명→5명 -- -- ⭐ 순서가 핵심 -- WHERE 로 '행' 을 먼저 거르고 → GROUP BY 로 묶고 → HAVING 으로 '그룹' 을 거른다. -- 같은 조건이라도 WHERE 에 쓸지 HAVING 에 쓸지에 따라 결과가 달라질 수 있다.
SQL
복사
⭐ 판단 기준
•
조건이 개별 행에 대한 것 (SAL, DEPTNO, JOB) → WHERE (더 빠름)
•
조건이 그룹 전체에 대한 것 (SUM, COUNT, AVG) → HAVING

7. 다중 컬럼 GROUP BY

1) 여러 기준으로 묶기

-- ============================================================ -- 【이해용 예제】 부서별 · 직무별 인원과 급여합계 -- ============================================================ SELECT DEPTNO AS 부서, JOB AS 직무, COUNT(*) AS 인원수, SUM(SAL) AS 급여합계 FROM EMP GROUP BY DEPTNO, JOB -- (부서, 직무) 조합마다 하나의 그룹 ORDER BY DEPTNO, JOB; -- 부서 직무 인원수 급여합계 -- ---- --------- ------ -------- -- 10 CLERK 1 1300 -- 10 MANAGER 1 2450 -- 10 PRESIDENT 1 5000 -- 20 ANALYST 2 6000 -- 20 CLERK 2 1900 -- 20 MANAGER 1 2975 -- 30 CLERK 1 950 -- 30 MANAGER 1 2850 -- 30 SALESMAN 4 5600 -- → 9개 그룹 -- -- ⭐ 07강에서 배운 DISTINCT DEPTNO, JOB 이 9건이었던 것과 같은 원리다. -- (실제 존재하는 조합의 수만큼 그룹이 생긴다)
SQL
복사

2) NULL도 하나의 그룹이 된다 ⭐

-- ============================================================ -- 【시험용 예제】 GROUP BY 는 NULL 을 하나의 그룹으로 묶는다 ★ -- ============================================================ SELECT COMM AS 커미션, COUNT(*) AS 인원수 FROM EMP GROUP BY COMM ORDER BY COMM; -- 커미션 인원수 -- ------ ------ -- 0 1 -- 300 1 -- 500 1 -- 1400 1 -- (NULL) 10 ← NULL 인 10명이 '하나의 그룹' 으로 묶인다 ⭐ -- -- ⭐ 06강 복습 : NULL 끼리는 비교 불가(UNKNOWN)지만, -- GROUP BY / DISTINCT 는 예외적으로 NULL 을 '같은 것' 으로 묶는다.
SQL
복사

8. 출제 포인트 한눈에 정리

#
꼭 외울 것
핵심
1
집계함수 = 다중행 함수
N행 → 1행
2
5대 함수
COUNT SUM AVG MAX MIN
3
MAX/MIN
문자·날짜에도 사용 가능
4
집계함수와 NULL
SUM AVG MAX MIN COUNT(컬럼) 모두 NULL 무시
5
COUNT(*)
유일하게 NULL 포함, 전체 행
6
COUNT(컬럼)
그 컬럼이 NULL 아닌 행
7
COUNT(DISTINCT 컬럼)
중복·NULL 제거한 값의 종류
8
AVG(COMM) 분모
NULL 제외 → 값 있는 행 수로 나눔 ⭐
9
AVG(COMM) ≠ AVG(NVL(COMM,0))
분모가 4 vs 14 로 달라짐 ⭐
10
값 0 은
NULL이 아니므로 집계에 포함
11
GROUP BY 시 SELECT 규칙
그룹 컬럼 or 집계함수만 ⭐
12
위반 시 오류
ORA-00979: not a GROUP BY expression
13
GROUP BY 컬럼
SELECT에 없어도 됨 (반대는 불가)
14
WHERE에 집계함수
❌ 불가 → HAVING
15
WHERE vs HAVING
WHERE=그룹 전(행) / HAVING=그룹 후(그룹) ⭐
16
GROUP BY와 NULL
NULL도 하나의 그룹

9. 확인 문제

Q1. EMP(14건, COMM은 4명만 값 있음)에서 각 결과는?
표현
결과
COUNT(*)
?
COUNT(COMM)
?
정답 보기
Q2. COMM 값이 300, 500, 1400, 0 (나머지 10명은 NULL)일 때 AVG(COMM) 의 결과는? ① 157 ② 550 ③ 2200 ④ 440
정답 보기
Q3. AVG(COMM) 과 AVG(NVL(COMM, 0)) 의 결과가 다른 이유는?
정답 보기
Q4. 다음 SQL이 오류가 나는 이유는?
SELECT DEPTNO, ENAME, SUM(SAL) FROM EMP GROUP BY DEPTNO;
SQL
복사
정답 보기
Q5. "급여합계가 9000을 초과하는 부서"를 찾을 때, 아래 SQL이 오류인 이유와 해결책은?
SELECT DEPTNO, SUM(SAL) FROM EMP WHERE SUM(SAL) > 9000 GROUP BY DEPTNO;
SQL
복사
정답 보기
Q6. WHERE 와 HAVING 의 차이를 실행 순서로 설명하시오.
정답 보기
Q7. SELECT COMM, COUNT(*) FROM EMP GROUP BY COMM 에서 NULL인 커미션은 어떻게 처리되는가?
정답 보기
Q8. MAX(HIREDATE) 와 MIN(HIREDATE) 는 무엇을 의미하는가?
정답 보기
Q9. 아래 두 SQL의 결과가 다를 수 있는 이유는?
-- (A) SELECT DEPTNO, COUNT(*) FROM EMP GROUP BY DEPTNO; -- (B) SELECT DEPTNO, COUNT(*) FROM EMP WHERE SAL >= 1000 GROUP BY DEPTNO;
SQL
복사
정답 보기