GROUP BY는 같은 값을 가진 행을 한 그룹으로 묶고, 집계 함수는 그룹마다 값을 하나씩 냅니다. HAVING은 묶은 그룹을 조건으로 거릅니다.
"과목마다 몇 명이 들었고 평균이 몇 점인가?"는 행 하나를 봐서는 답할 수 없습니다. 같은 과목의 행을 모아야 합니다. 행을 묶는 일은 GROUP BY 절이 맡고, 묶인 행을 개수·합계·평균 같은 값 하나로 줄이는 일은 집계 함수(aggregate function)가 맡습니다. "4명 이상 들은 과목만"처럼 그룹을 고르는 조건은 WHERE가 아니라 HAVING에 씁니다. 실기는 집계 결과를 쓰게 하거나 집계 쿼리를 쓰게 합니다.
그림은 12행이 든 수강 표를 묶고, 묶은 결과를 거릅니다.
1GROUP BY — 같은 값끼리 묶기
GROUP BY 과목코드가 과목코드가 같은 행끼리 묶습니다. DS 4행, NW 5행, OS 3행, 세 그룹입니다.
GROUP BY를 쓰면 SELECT에는 GROUP BY에 쓴 열과 집계 함수만 올 수 있습니다.
2집계 함수 — 그룹마다 값 하나씩
집계 함수 다섯은 개수(COUNT), 합계(SUM), 평균(AVG), 최댓값(MAX), 최솟값(MIN)입니다.
COUNT(*)는 그룹마다 행 수를 셉니다. DS 4, NW 5, OS 3입니다.
AVG(점수)는 그룹 안 점수의 평균을 냅니다. DS는 320 ÷ 4 = 80, NW는 370 ÷ 5 = 74, OS는 210 ÷ 3 = 70입니다. 결과는 그룹 하나에 한 줄씩, 세 줄입니다.
3WHERE와 HAVING — 거르는 때가 다르다
HAVING COUNT(*) >= 4는 묶은 뒤에 그룹을 거릅니다. 3명이 들은 OS 그룹이 빠지고 DS와 NW가 남습니다.
WHERE 점수 >= 80은 묶기 전에 행을 거릅니다. 80점 이상인 5행만 묶여 DS 2, NW 3이 됩니다. OS는 남은 행이 없어서 그룹이 생기지 않습니다.
처리는 WHERE가 행을 거른 뒤 GROUP BY가 묶고, 그다음 HAVING이 그룹을 거릅니다.
| 가르는 기준 | WHERE | HAVING |
|---|---|---|
| 거르는 때 | 묶기 전 | 묶은 뒤 |
| 거르는 것 | 행 | 그룹 |
| 집계 함수 | 쓸 수 없음(ORA-00934) | 쓸 수 있음 |
예제
절은 다음 차례로 씁니다.
SELECT 열, 집계 함수
FROM 표
[WHERE 행 조건]
[GROUP BY 열]
[HAVING 그룹 조건]
[ORDER BY 열];
집계 함수
SELECT 과목코드,
COUNT(*) AS 인원,
SUM(점수) AS 합계,
AVG(점수) AS 평균,
MAX(점수) AS 최고,
MIN(점수) AS 최저
FROM 수강
GROUP BY 과목코드
ORDER BY 과목코드;
| 과목코드 | 인원 | 합계 | 평균 | 최고 | 최저 |
|---|---|---|---|---|---|
| DS | 4 | 320 | 80 | 100 | 65 |
| NW | 5 | 370 | 74 | 95 | 50 |
| OS | 3 | 210 | 70 | 75 | 60 |
위 쿼리의 SELECT에 학번을 더하면 ORA-00979 오류가 납니다. 과목 하나에 학번이 여럿이라 한 줄에 무엇을 보일지 정할 수 없기 때문입니다.
COUNT(*)와 COUNT(열)
학생을 학년으로 묶어 셉니다. 강도현(1학년)은 학과코드가 NULL입니다.
SELECT 학년,
COUNT(*) AS 전체,
COUNT(학과코드) AS 학과있음
FROM 학생
GROUP BY 학년
ORDER BY 학년;
| 학년 | 전체 | 학과있음 |
|---|---|---|
| 1 | 2 | 1 |
| 2 | 2 | 2 |
| 3 | 3 | 3 |
COUNT(*)는 NULL과 상관없이 행을 셉니다. 1학년은 박서연과 강도현, 2행입니다.COUNT(학과코드)는 학과코드가 NULL인 행을 빼고 셉니다. 그래서 1학년은 1입니다.- SUM·AVG·MAX·MIN도 NULL을 빼고 계산합니다.
HAVING
SELECT 과목코드,
COUNT(*) AS 인원
FROM 수강
GROUP BY 과목코드
HAVING COUNT(*) >= 4
ORDER BY 과목코드;
| 과목코드 | 인원 |
|---|---|
| DS | 4 |
| NW | 5 |
WHERE와 HAVING
WHERE는 묶기 전에 행을, HAVING은 묶은 뒤에 그룹을 거릅니다.
SELECT 과목코드,
COUNT(*) AS 인원
FROM 수강
WHERE 점수 >= 80
GROUP BY 과목코드
ORDER BY 과목코드;
| 과목코드 | 인원 |
|---|---|
| DS | 2 |
| NW | 3 |
WHERE가 처리될 때는 아직 그룹이 없습니다. 그래서 집계 함수를 WHERE에 쓰면 ORA-00934 오류가 납니다. 그룹의 평균으로 거르려면 HAVING에 씁니다.
SELECT 과목코드
FROM 수강
WHERE AVG(점수) >= 75
GROUP BY 과목코드;
시험 포인트
- 필기: WHERE는 묶기 전에 행을, HAVING은 묶은 뒤에 그룹을 거릅니다. 집계 함수는 WHERE에 쓸 수 없습니다.
COUNT(*)는 모든 행을 세고,COUNT(열)과 SUM·AVG·MAX·MIN은 NULL을 빼고 계산합니다. GROUP BY 열이 NULL인 행은 NULL 그룹 하나가 됩니다. - 실기: 집계 쿼리의 결과를 그룹 하나에 한 줄씩 씁니다. 요구를 읽고 GROUP BY와 HAVING을 넣어 쿼리를 쓰고, 빈칸에 WHERE와 HAVING을 가려 씁니다.
확인 문제
1. 수강 표를 과목코드로 묶는 쿼리에서, HAVING에는 쓸 수 있고 WHERE에는 쓸 수 없는 조건을 고르세요.
- ①
점수 >= 80 - ②
점수 IS NOT NULL - ③
과목코드 IN ('DS', 'NW') - ④
COUNT(*) >= 2
정답
④입니다. 그룹의 개수·합계·평균은 묶은 뒤에야 나오는 값이라 HAVING에만 쓸 수 있습니다. 집계 함수를 WHERE에 쓰면 ORA-00934 오류가 납니다.
- ① 행 하나하나의 값으로 거는 조건이라 WHERE에 씁니다. 묶은 열이 아닌 열을 HAVING에 쓰면 ORA-00979 오류가 납니다.
- ② NULL인지 아닌지도 행 하나의 값입니다. 집계 함수가 아니라 WHERE에 씁니다.
- ③ 여러 값 가운데 하나인지 보는 조건입니다. IN은 집계 함수가 아니어서 WHERE에 씁니다.
2. 수강 표를 과목마다 한 줄로 요약합니다. (가)~(라)에 쓰는 집계 함수와 절의 이름을 SQL 키워드로 쓰세요. 답을 끝까지 쓴 뒤 정답을 펼치세요.
- (가) 과목마다 수강한 사람이 몇 명인지 셉니다. 점수를 아직 적지 않은 사람도 셉니다.
- (나) 과목마다 점수를 모두 더한 값과, 점수의 평균을 냅니다.
- (다) 과목마다 가장 높은 점수와 가장 낮은 점수를 뽑습니다.
- (라) 과목코드로 묶는 절과, 묶은 뒤 그룹을 거르는 절입니다.
정답
받는 답: (가) COUNT(*) · (나) SUM(점수)과 AVG(점수) · (다) MAX(점수)와 MIN(점수) · (라) GROUP BY와 HAVING
(가)는 '적지 않은 사람도 센다'가 실마리입니다. 괄호 안에 열 이름을 쓰면 그 열이 NULL인 행이 빠집니다. (라)는 거르는 때로 가릅니다 — 묶는 것은 GROUP BY, 묶은 뒤 그룹을 거르는 것은 HAVING입니다.
- 틀리는 꼴 (가) COUNT(점수): 점수가 NULL인 행을 빼고 셉니다. 사람 수를 세려면 COUNT(*)입니다.
- 틀리는 꼴 (나) TOTAL·MEAN: 합계는 SUM, 평균은 AVG입니다. 뜻이 같은 다른 영어 낱말은 받지 않습니다.
- 틀리는 꼴 (라) WHERE — 그룹을 거르는 자리에: 묶기 전에 행을 거르는 절입니다. 집계 함수로 그룹을 거르려면 HAVING입니다.
3. 학생 표와 수강 표를 묶어 봅니다. (1)~(3)을 차례로 풀어 보세요. 답을 끝까지 쓴 뒤 정답을 펼치세요.
(1) 다음은 1·2학년만 보고, 그런 학생이 2명 이상인 학과코드를 찾는 쿼리입니다. ①·②에 들어갈 절의 이름을 쓰고, 결과가 몇 행인지 쓰세요. 괄호는 빈칸 표시입니다.
SELECT 학과코드,
COUNT(*) AS 인원
FROM 학생
( ① ) 학년 <= 2
GROUP BY 학과코드
( ② ) COUNT(*) >= 2;
(2) (1)의 ②줄을 '묶인 학생의 평균 학년이 2 이상인 학과코드만 남기기'로 바꾸려면 어떻게 쓸까요?
(3) 수강 표에서 DS와 NW 두 과목만 보고, 그 두 과목의 평균 점수가 70점 이상인 학생의 학번과 평균을 학번 차례로 조회하는 SELECT 문을 쓰세요.
정답
받는 답: (1) ① WHERE · ② HAVING, 1행 · (2) HAVING AVG(학년) >= 2 · (3) 아래 둘째 블록
(1)에서 1·2학년은 102·103(CS)·104(EE)·107(학과코드 NULL)입니다. 묶으면 CS 2 · EE 1 · NULL 1이고, 2명 이상인 CS만 남아 1행입니다.
SELECT 학과코드,
COUNT(*) AS 인원
FROM 학생
WHERE 학년 <= 2
GROUP BY 학과코드
HAVING COUNT(*) >= 2;| 학과코드 | 인원 |
|---|---|
| CS | 2 |
(3)은 과목을 고르는 조건은 행 하나하나에 걸리니 WHERE에, 평균 조건은 그룹에 걸리니 HAVING에 씁니다.
SELECT 학번,
AVG(점수) AS 평균
FROM 수강
WHERE 과목코드
IN ('DS', 'NW')
GROUP BY 학번
HAVING AVG(점수) >= 70
ORDER BY 학번;| 학번 | 평균 |
|---|---|
| 101 | 82.5 |
| 102 | 70 |
| 104 | 95 |
| 105 | 95 |
- 틀리는 꼴 (1) ① HAVING · ② WHERE: 두 절의 자리를 바꿔 썼습니다. 학년은 행의 값이라 묶기 전에, 인원은 그룹의 값이라 묶은 뒤에 거릅니다.
- 틀리는 꼴 (1) 3행: HAVING 줄을 빼고 묶은 결과를 그대로 셌습니다. CS 2 · EE 1 · NULL 1 가운데 2명 이상인 CS만 남습니다.
- 틀리는 꼴 (1) 0행:
학년 <= 2를 1학년만으로 읽었습니다. 2학년인 102·103이 들어가야 CS가 2명이 됩니다. - 틀리는 꼴 (3) 102를 뺀 3행: 평균이 정확히 70인 102는
>=라서 남습니다. - 틀리는 꼴 (3) 평균 조건을
HAVING 점수 >= 70으로 쓴 꼴: 점수는 묶은 열도, 집계 함수도 아니어서 ORA-00979 오류가 납니다.
더 자세히: SQLD의 GROUP BY와 집계 함수 · HAVING 절