본문 바로가기

원칙 5 + 6 — GROUP BY + HAVING

원칙 5 + 6 — GROUP BY + HAVING

묶어서 집계하고, 집계 후 조건을 걸자.

박지수 팀장 : 민서씨, 상품명별로 총 판매금액이 얼마인지 뽑아줄 수 있어요? 그리고 그 중에서 총 판매금액이 500,000원 이상인 것만요.

김민서 사원 : 상품명별로 묶어서... WHERE로는 안 될 것 같고. 각 상품마다 금액을 더하는 건데, SQL에서 그게 되나요?

박지수 팀장 : 돼요. 그게 GROUP BY예요. 그리고 집계한 결과에 조건을 거는 건 HAVING이고요. WHERE가 행을 걸러낸다면, HAVING은 집계 결과를 걸러요.

지금까지는 있는 데이터를 그대로 가져왔습니다. 이번 챕터부터는 데이터를 묶어서 계산합니다. 데이터를 묶는 것은 데이터 분석의 기본이자 비즈니스의 결과를 보는데 가장 기본적인 뷰이기 때문에 여러 뷰를 추출해보시고 익숙해지시길 바랍니다.

이런 결과를 원할 때

상품명별 총 판매금액

상품명총판매금액
노트북 Pro3,000,000
노트북 Air2,400,000
모니터 32인치550,000
모니터 27인치350,000
......

그 중 총판매금액이 500,000원 이상인 것만

상품명총판매금액
노트북 Pro3,000,000
노트북 Air2,400,000
모니터 32인치550,000

위는 GROUP BY로, 아래는 HAVING을 더해서 만듭니다.

GROUP BY가 하는 일

GROUP BY는 같은 값을 가진 행들을 하나의 그룹으로 묶습니다. 그리고 그 그룹마다 집계 함수로 계산합니다.

SELECT 묶을열, 집계함수(열)
FROM 테이블이름
GROUP BY 묶을열;

집계 함수는 다섯 가지입니다.

함수의미예시
COUNT(*)행 개수주문 건수
SUM(열)합계총 판매금액
AVG(열)평균평균 단가
MAX(열)최댓값가장 높은 단가
MIN(열)최솟값가장 낮은 단가

💡 GROUP BY를 쓸 때 핵심 규칙

SELECT에 쓸 수 있는 것은 두 가지뿐입니다.

  • GROUP BY에 쓴 열
  • 집계 함수

이 두 가지 외의 열을 SELECT에 쓰면 에러가 납니다.

STEP 1 — 상품명별로 묶어서 세어보자

가장 기본적인 집계입니다. 상품명별로 몇 건씩 팔렸는지 셉니다.

SELECT 상품명, COUNT(*) AS 판매건수
FROM 판매정보
GROUP BY 상품명;

실행 결과

상품명판매건수
노트북 Pro2
무선 키보드2
마우스 패드2
모니터 27인치1
USB 허브1
노트북 Air2
모니터 32인치1
웹캠 HD1

12건의 판매 데이터가 상품명별 8행으로 줄었습니다.

STEP 2 — 상품명별 총 판매금액을 구하자

SUM으로 그룹별 합계를 냅니다.

SELECT 상품명, SUM(단가) AS 총판매금액
FROM 판매정보
GROUP BY 상품명;

실행 결과

상품명총판매금액
노트북 Pro3,000,000
무선 키보드90,000
마우스 패드30,000
모니터 27인치350,000
USB 허브25,000
노트북 Air2,400,000
모니터 32인치550,000
웹캠 HD89,000
  • 노트북 Pro : 1,500,000 × 2건 = 3,000,000
  • 마우스 패드 : 15,000 × 2건 = 30,000
  • 노트북 Air : 1,200,000 × 2건 = 2,400,000

그룹별로 단가를 더한 결과입니다.

STEP 3 — 집계 함수를 여러 개 써보자

한 번에 여러 집계를 함께 볼 수 있습니다.

SELECT 상품명,
       COUNT(*) AS 판매건수,
       SUM(단가) AS 총판매금액,
       AVG(단가) AS 평균단가,
       MAX(단가) AS 최고단가,
       MIN(단가) AS 최저단가
FROM 판매정보
GROUP BY 상품명;

실행 결과

상품명판매건수총판매금액평균단가최고단가최저단가
노트북 Pro23,000,0001,500,0001,500,0001,500,000
무선 키보드290,00045,00045,00045,000
마우스 패드230,00015,00015,00015,000
모니터 27인치1350,000350,000350,000350,000
USB 허브125,00025,00025,00025,000
노트북 Air22,400,0001,200,0001,200,0001,200,000
모니터 32인치1550,000550,000550,000550,000
웹캠 HD189,00089,00089,00089,000

한 쿼리로 상품명별 전체 판매 현황이 나왔습니다.

고객정보 테이블에서도 같은 방식으로 씁니다.

SELECT 등급,
       COUNT(*) AS 고객수,
       AVG(나이) AS 평균나이
FROM 고객정보
GROUP BY 등급;

실행 결과

등급고객수평균나이
NULL243
VIP330.5
일반331
프리미엄229

등급별로 고객 수와 평균 나이를 한눈에 볼 수 있습니다. AVG는 NULL 값을 자동으로 제외하고 계산합니다.

STEP 4 — WHERE와 GROUP BY를 함께 쓰자

WHERE는 GROUP BY 이전에 실행됩니다. 먼저 행을 걸러내고, 그 결과를 묶어서 집계합니다.

SELECT 상품명,
       COUNT(*) AS 판매건수,
       SUM(단가) AS 총판매금액
FROM 판매정보
WHERE 고객ID IN (1, 2, 3)
GROUP BY 상품명;

실행 결과

상품명판매건수총판매금액
노트북 Pro23,000,000
마우스 패드115,000
무선 키보드145,000
모니터 27인치1350,000

전체 12건 중 고객ID가 1, 2, 3인 5건만 먼저 걸러낸 뒤, 그 5건을 상품명별로 묶어 집계했습니다.

💡 WHERE와 GROUP BY의 실행 순서

  1. FROM — 테이블을 가져온다
  2. WHERE — 행을 먼저 걸러낸다
  3. GROUP BY — 걸러진 행을 묶는다
  4. SELECT — 묶인 결과에서 열을 고른다

WHERE는 집계 전에, 행 단위로 조건을 겁니다. 집계 후에 조건을 걸고 싶다면 HAVING을 씁니다.

STEP 5 — GROUP BY + ORDER BY를 함께 쓰자

집계 결과를 정렬하고 싶을 때는 ORDER BY를 뒤에 붙입니다.

SELECT 상품명,
       COUNT(*) AS 판매건수,
       SUM(단가) AS 총판매금액
FROM 판매정보
GROUP BY 상품명
ORDER BY 총판매금액 DESC;

실행 결과

상품명판매건수총판매금액
노트북 Pro23,000,000
노트북 Air22,400,000
모니터 32인치1550,000
모니터 27인치1350,000
무선 키보드290,000
웹캠 HD189,000
마우스 패드230,000
USB 허브125,000

ORDER BY에서 AS로 만든 이름(총판매금액)을 쓸 수 있는 이유는, ORDER BY가 SELECT 이후에 실행되기 때문입니다.

HAVING이 하는 일

GROUP BY로 집계한 결과에 조건을 걸고 싶을 때 HAVING을 씁니다.

SELECT 묶을열, 집계함수(열)
FROM 테이블이름
GROUP BY 묶을열
HAVING 집계조건;

WHERE는 행에 조건을 겁니다. HAVING은 집계 결과에 조건을 겁니다.

STEP 6 — 집계 결과를 걸러내자

총 판매금액이 500,000원 이상인 상품만 보고 싶습니다.

SELECT 상품명,
       SUM(단가) AS 총판매금액
FROM 판매정보
GROUP BY 상품명
HAVING SUM(단가) >= 500000;

실행 결과

상품명총판매금액
노트북 Pro3,000,000
노트북 Air2,400,000
모니터 32인치550,000

나머지 상품들은 총판매금액이 500,000원 미만이라 조건을 만족하지 않아 제외됐습니다.

💡 HAVING에서 별칭 사용 주의

HAVING은 SELECT보다 먼저 실행됩니다. 그래서 AS로 만든 별칭을 HAVING에서 쓰면 안 됩니다.

-- ❌
SELECT 상품명, SUM(단가) AS 총판매금액
FROM 판매정보
GROUP BY 상품명
HAVING 총판매금액 >= 500000;

-- ✅
SELECT 상품명, SUM(단가) AS 총판매금액
FROM 판매정보
GROUP BY 상품명
HAVING SUM(단가) >= 500000;

STEP 7 — 판매건수로 걸러내자

COUNT 결과에도 HAVING을 쓸 수 있습니다.

SELECT 상품명, COUNT(*) AS 판매건수
FROM 판매정보
GROUP BY 상품명
HAVING COUNT(*) >= 2;

실행 결과

상품명판매건수
노트북 Pro2
무선 키보드2
마우스 패드2
노트북 Air2

1건만 팔린 모니터 27인치, USB 허브, 모니터 32인치, 웹캠 HD가 제외됐습니다.

STEP 8 — 모든 원칙을 합치자

지금까지 배운 원칙을 모두 한 쿼리에 씁니다.

SELECT 상품명,
       COUNT(*) AS 판매건수,
       SUM(단가) AS 총판매금액
FROM 판매정보
WHERE 고객ID IN (1, 2, 3, 4, 5)
GROUP BY 상품명
HAVING SUM(단가) >= 100000
ORDER BY 총판매금액 DESC
LIMIT 2;

실행 결과

상품명판매건수총판매금액
노트북 Pro23,000,000
노트북 Air11,200,000

이 쿼리는 다음 순서로 실행됩니다.

  1. FROM — 판매정보 테이블을 가져온다
  2. WHERE — 고객ID가 1~5인 행만 남긴다
  3. GROUP BY — 상품명별로 묶는다
  4. HAVING — 총판매금액이 100,000원 이상인 그룹만 남긴다
  5. SELECT — 상품명, 판매건수, 총판매금액 열을 고른다
  6. ORDER BY — 총판매금액 내림차순으로 정렬한다
  7. LIMIT — 상위 2개만 가져온다

WHERE vs HAVING — 결정적 차이

둘은 헷갈리기 쉽습니다. 딱 하나만 기억하면 됩니다.

  • WHERE → 집계 전, 행에 조건
  • HAVING → 집계 후, 그룹에 조건

예시로 비교합니다.

-- WHERE: 집계 전에 15,000원짜리를 제외하고 집계
SELECT 상품명, SUM(단가) AS 총판매금액
FROM 판매정보
WHERE 단가 != 15000
GROUP BY 상품명;

실행 결과

상품명총판매금액
노트북 Pro3,000,000
무선 키보드90,000
모니터 27인치350,000
USB 허브25,000
노트북 Air2,400,000
모니터 32인치550,000
웹캠 HD89,000

마우스 패드(15,000원)가 WHERE로 모두 제외되어 결과에 없습니다.

-- HAVING: 집계 후에 총판매금액이 500,000원 미만인 그룹을 제외
SELECT 상품명, SUM(단가) AS 총판매금액
FROM 판매정보
GROUP BY 상품명
HAVING SUM(단가) >= 500000;

실행 결과

상품명총판매금액
노트북 Pro3,000,000
노트북 Air2,400,000
모니터 32인치550,000

모든 상품이 집계에 포함됐지만, 총판매금액이 500,000원 미만인 그룹은 HAVING으로 제외됐습니다.

자주 하는 실수

실수 1 — SELECT에 GROUP BY에 없는 열을 쓴다

-- ❌
SELECT 상품명, 판매일, SUM(단가)
FROM 판매정보
GROUP BY 상품명;

-- ✅
SELECT 상품명, SUM(단가)
FROM 판매정보
GROUP BY 상품명;

판매일을 함께 보고 싶다면 GROUP BY에도 추가해야 합니다.

SELECT 상품명, 판매일, SUM(단가)
FROM 판매정보
GROUP BY 상품명, 판매일;

실수 2 — WHERE에 집계 함수를 쓴다

-- ❌
SELECT 상품명, SUM(단가)
FROM 판매정보
WHERE SUM(단가) >= 500000
GROUP BY 상품명;

-- ✅
SELECT 상품명, SUM(단가)
FROM 판매정보
GROUP BY 상품명
HAVING SUM(단가) >= 500000;

집계 함수는 WHERE에 쓸 수 없습니다. 집계 결과에 조건을 걸 때는 반드시 HAVING을 씁니다.

실수 3 — HAVING에서 별칭을 쓴다

HAVING은 SELECT 이후에 별칭이 생기기 때문에 사용할 수 없습니다.

-- ❌
SELECT 상품명, SUM(단가) AS 총판매금액
FROM 판매정보
GROUP BY 상품명
HAVING 총판매금액 >= 500000;

-- ✅
SELECT 상품명, SUM(단가) AS 총판매금액
FROM 판매정보
GROUP BY 상품명
HAVING SUM(단가) >= 500000;

개념 정리

집계 함수

함수의미예시
COUNT(*)행 개수COUNT(*) AS 판매건수
SUM(열)합계SUM(단가) AS 총판매금액
AVG(열)평균AVG(나이) AS 평균나이
MAX(열)최댓값MAX(단가) AS 최고단가
MIN(열)최솟값MIN(단가) AS 최저단가

GROUP BY / HAVING 전체 구조

절역할조건 대상
WHERE집계 전 필터개별 행
GROUP BY묶기—
HAVING집계 후 필터집계 결과

전체 작성 순서

SELECT 열, 집계함수
FROM 테이블
WHERE 행 조건
GROUP BY 열
HAVING 집계 조건
ORDER BY 열
LIMIT 개수;

연습문제

Q1. ★☆☆ 판매정보 테이블에서 상품명별 평균 단가를 상품명, 평균단가으로 조회하세요.

Q2. ★★☆ 고객정보 테이블에서 지역별 고객 수를 구하되, 고객 수가 많은 순서대로 정렬해서 조회하세요.

Q3. ★★★ 판매정보 테이블에서 상품명별 총판매금액을 구하되, 총판매금액이 500,000원 이상인 상품만, 총판매금액 높은 순서로 조회하세요.

정답 및 해설

Q1 정답

SELECT 상품명,
       AVG(단가) AS 평균단가
FROM 판매정보
GROUP BY 상품명;

실행 결과

상품명평균단가
노트북 Pro1,500,000
무선 키보드45,000
마우스 패드15,000
모니터 27인치350,000
USB 허브25,000
노트북 Air1,200,000
모니터 32인치550,000
웹캠 HD89,000

이 테이블에서는 같은 상품은 같은 단가라 평균이 단가와 같습니다. 실제 데이터에서는 할인 등으로 금액이 다를 수 있어 AVG가 유용합니다.

Q2 정답

SELECT 지역,
       COUNT(*) AS 고객수
FROM 고객정보
GROUP BY 지역
ORDER BY 고객수 DESC;

실행 결과

지역고객수
서울4
부산2
대구2
NULL2

GROUP BY로 지역별로 묶고, ORDER BY로 많은 순서로 정렬했습니다. ORDER BY에서 별칭 고객수를 쓸 수 있는 이유는 ORDER BY가 SELECT 이후에 실행되기 때문입니다.

Q3 정답

SELECT 상품명,
       SUM(단가) AS 총판매금액
FROM 판매정보
GROUP BY 상품명
HAVING SUM(단가) >= 500000
ORDER BY 총판매금액 DESC;

실행 결과

상품명총판매금액
노트북 Pro3,000,000
노트북 Air2,400,000
모니터 32인치550,000

나머지 상품들은 총판매금액이 500,000원 미만이라 HAVING 조건에서 제외됩니다.

실무 패턴

패턴 1 — 카테고리별 집계 리포트

마케팅 보고서에서 가장 많이 쓰는 패턴입니다.

SELECT 기준열,
       COUNT(*) AS 건수,
       SUM(금액열) AS 합계,
       AVG(금액열) AS 평균
FROM 테이블이름
GROUP BY 기준열
ORDER BY 합계 DESC;

상품명별, 등급별, 지역별 등 어떤 기준이든 GROUP BY에 기준 열만 바꾸면 같은 구조로 씁니다.

패턴 2 — 조건 + 집계 + 필터 세트

SELECT 기준열,
       COUNT(*) AS 건수,
       SUM(금액열) AS 합계
FROM 테이블이름
WHERE 날짜열 BETWEEN '2024-01-01' AND '2024-01-31'
GROUP BY 기준열
HAVING COUNT(*) >= 2
ORDER BY 합계 DESC
LIMIT 5;

특정 기간의 데이터를 기준별로 집계하고, 일정 건수 이상인 것만 상위 N개로 뽑는 패턴입니다.

마무리

처음에 암호처럼 보였던 코드는 이제 한 줄씩 읽힙니다.

다시 보는 프롤로그 쿼리

SELECT 상품명,
       COUNT(*) AS 판매건수,
       SUM(단가) AS 총판매금액
FROM 판매정보
WHERE 고객ID IN (1, 2, 3, 4, 5)
GROUP BY 상품명
ORDER BY 총판매금액 DESC;
  • SELECT 상품명, 판매건수, 총판매금액 → 상품명별 판매건수와 총판매금액을
  • FROM 판매정보 → 판매정보 테이블에서
  • WHERE 고객ID IN (1,2,3,4,5) → 고객ID가 1~5인 것만 걸러낸 뒤
  • GROUP BY 상품명 → 상품명별로 묶어서 집계하고
  • ORDER BY 총판매금액 DESC → 총판매금액이 높은 순서로 정렬한다
SELECT 상품명,
       COUNT(*) AS 판매건수,
       SUM(단가) AS 총판매금액
FROM 판매정보
GROUP BY 상품명
HAVING COUNT(*) >= 2
ORDER BY 총판매금액 DESC;
  • SELECT 상품명, 판매건수, 총판매금액 → 상품명별 판매건수와 총판매금액을
  • FROM 판매정보 → 판매정보 테이블에서
  • GROUP BY 상품명 → 상품명별로 묶어서 집계하고
  • HAVING COUNT(*) >= 2 → 2건 이상 팔린 상품만 남긴 뒤
  • ORDER BY 총판매금액 DESC → 총판매금액이 높은 순서로 정렬한다

처음에 암호처럼 보였던 코드가 이제 한 줄씩 읽힙니다.

김민서 사원 : 결과가 한눈에 다 보여요. 처음엔 뭔지도 몰랐는데, 이제 회사의 현황을 제가 직접 추출해서 쓸 수 있을 것 같아요.

박지수 팀장 : 그게 이 책의 목표였어요. 이제 마지막 챕터가 남았어요. 이 6가지 원칙과 Claude를 합치면 보고서 수준의 대시보드를 바로 만들 수 있어요.

▶ 에필로그 — Claude로 대시보드 완성하기