본문으로 건너뛰기

그룹과 집계: GROUP BY·HAVING

정보처리기사 목차SQL 기본레슨 92 / 233

목차

GROUP BY는 같은 값을 가진 행을 한 그룹으로 묶고, 집계 함수는 그룹마다 값을 하나씩 냅니다. HAVING은 묶은 그룹을 조건으로 거릅니다.

"과목마다 몇 명이 들었고 평균이 몇 점인가?"는 행 하나를 봐서는 답할 수 없습니다. 같은 과목의 행을 모아야 합니다. 행을 묶는 일은 GROUP BY 절이 맡고, 묶인 행을 개수·합계·평균 같은 값 하나로 줄이는 일은 집계 함수(aggregate function)가 맡습니다. "4명 이상 들은 과목만"처럼 그룹을 고르는 조건은 WHERE가 아니라 HAVING에 씁니다. 실기는 집계 결과를 쓰게 하거나 집계 쿼리를 쓰게 합니다.

6단계 중 1단계. 수강 표에는 12행이 있습니다.

그림은 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이 그룹을 거릅니다.

가르는 기준WHEREHAVING
거르는 때묶기 전묶은 뒤
거르는 것행그룹
집계 함수쓸 수 없음(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 과목코드;
과목코드인원합계평균최고최저
DS43208010065
NW5370749550
OS3210707560

위 쿼리의 SELECT에 학번을 더하면 ORA-00979 오류가 납니다. 과목 하나에 학번이 여럿이라 한 줄에 무엇을 보일지 정할 수 없기 때문입니다.

COUNT(*)와 COUNT(열)

학생을 학년으로 묶어 셉니다. 강도현(1학년)은 학과코드가 NULL입니다.

SELECT 학년,
  COUNT(*) AS 전체,
  COUNT(학과코드) AS 학과있음
FROM 학생
GROUP BY 학년
ORDER BY 학년;
학년전체학과있음
121
222
333
  • 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 과목코드;
과목코드인원
DS4
NW5

WHERE와 HAVING

WHERE는 묶기 전에 행을, HAVING은 묶은 뒤에 그룹을 거릅니다.

SELECT 과목코드,
  COUNT(*) AS 인원
FROM 수강
WHERE 점수 >= 80
GROUP BY 과목코드
ORDER BY 과목코드;
과목코드인원
DS2
NW3

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;
학과코드인원
CS2

(3)은 과목을 고르는 조건은 행 하나하나에 걸리니 WHERE에, 평균 조건은 그룹에 걸리니 HAVING에 씁니다.

SELECT 학번,
  AVG(점수) AS 평균
FROM 수강
WHERE 과목코드
    IN ('DS', 'NW')
GROUP BY 학번
HAVING AVG(점수) >= 70
ORDER BY 학번;
학번평균
10182.5
10270
10495
10595
  • 틀리는 꼴 (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 절

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

정보처리기사 목차로