GROUP BY로 데이터를 그룹으로 나누고 각 그룹에 집계 함수를 적용해 카테고리별·등급별·월별 통계를 구합니다.
🎯 학습 목표
이 강을 마치면 GROUP BY를 사용해 데이터를 원하는 기준으로 그룹화하고, 각 그룹에 집계 함수를 적용해 그룹별 통계를 구할 수 있습니다. "카테고리별 도서 수와 평균 가격", "등급별 회원 수", "월별 주문 건수"처럼 데이터 분석의 핵심 패턴을 자유롭게 작성할 수 있게 됩니다. 또한 여러 열로 그룹화하는 다중 GROUP BY도 이해할 수 있습니다.
💡 GROUP BY가 필요한 이유
이 섹션에서는 GROUP BY가 왜 필요한지, 9강의 집계 함수와 어떻게 다른지 살펴보겠습니다.
9강에서 SELECT COUNT(*) FROM books는 전체 도서 수 하나를 반환했습니다. 그런데 "카테고리별로 도서가 몇 권인지" 알고 싶다면 어떻게 해야 할까요? WHERE로 카테고리를 하나씩 필터링하면 쿼리를 세 번 실행해야 합니다.
-- 이런 방법은 비효율적
SELECT COUNT(*) FROM books WHERE category = 'IT';
SELECT COUNT(*) FROM books WHERE category = '경제';
SELECT COUNT(*) FROM books WHERE category = '소설';
GROUP BY는 이 문제를 한 번의 쿼리로 해결합니다. 지정한 열의 값이 같은 행들을 하나의 그룹으로 묶은 뒤, 각 그룹에 집계 함수를 적용합니다.
-- GROUP BY를 사용한 카테고리별 도서 수
SELECT category, COUNT(*) AS 도서수
FROM books
GROUP BY category;
결과는 다음과 같습니다.
| category | 도서수 |
|---|---|
| IT | 3 |
| 경제 | 2 |
| 소설 | 2 |
GROUP BY category가 실행되면 books 테이블의 7개 행이 category 값 기준으로 세 그룹으로 나뉘고, 각 그룹에 COUNT(*)가 적용되어 3개 행의 결과가 반환됩니다. GROUP BY 없이 집계 함수만 쓰면 전체가 하나의 그룹이 되고, GROUP BY를 쓰면 여러 그룹으로 나뉩니다.
📊 GROUP BY 기본 사용법
이 섹션에서는 GROUP BY와 집계 함수를 다양하게 조합하는 방법을 살펴보겠습니다.
GROUP BY는 SELECT 문에서 WHERE 다음, ORDER BY 앞에 위치합니다. 전체 실행 순서는 FROM → WHERE → GROUP BY → SELECT(집계) → ORDER BY → LIMIT입니다.
카테고리별 도서 통계를 여러 집계 함수로 한 번에 구해봅니다.
SELECT
category AS 카테고리,
COUNT(*) AS 도서수,
ROUND(AVG(price), 0) AS 평균가격,
MAX(price) AS 최고가,
MIN(price) AS 최저가,
SUM(stock) AS 총재고
FROM books
GROUP BY category
ORDER BY 도서수 DESC;
결과에는 세 카테고리 각각의 통계가 한 행씩 나타납니다. ORDER BY 도서수 DESC는 도서 수가 많은 카테고리부터 정렬합니다. SQLite는 GROUP BY 결과에서 SELECT 별칭을 ORDER BY에 사용할 수 있습니다.
회원을 등급별로 분류해 통계를 내봅니다.
SELECT
grade AS 등급,
COUNT(*) AS 회원수,
MIN(join_date) AS 최초가입일,
MAX(join_date) AS 최근가입일
FROM members
GROUP BY grade
ORDER BY 회원수 DESC;
VIP 2명, REGULAR 2명, PREMIUM 1명, NULL(등급 미설정) 1명이 각 행으로 나타납니다. NULL 등급도 하나의 그룹으로 묶입니다.
날짜 열을 strftime으로 변환해 월별 그룹화도 할 수 있습니다.
SELECT
strftime('%Y-%m', order_date) AS 주문월,
COUNT(*) AS 주문건수,
SUM(quantity) AS 총주문수량
FROM orders
GROUP BY strftime('%Y-%m', order_date)
ORDER BY 주문월 ASC;
2024-03, 2024-04, 2024-05, 2024-06처럼 월별로 주문 건수와 수량이 집계됩니다. GROUP BY에 strftime 함수를 사용하면 날짜 필드의 연도·월 단위 집계가 가능합니다.
🔑 GROUP BY의 핵심 규칙
이 섹션에서는 GROUP BY 사용 시 반드시 지켜야 하는 규칙을 살펴보겠습니다.
GROUP BY를 사용할 때 SELECT에 쓸 수 있는 열에는 규칙이 있습니다. SELECT에는 GROUP BY에 포함된 열 또는 집계 함수만 쓸 수 있습니다.
-- 올바른 예: category는 GROUP BY에 있음, COUNT(*)는 집계 함수
SELECT category, COUNT(*)
FROM books
GROUP BY category;
-- 오류! title은 GROUP BY에 없고 집계 함수도 아님
SELECT category, title, COUNT(*)
FROM books
GROUP BY category;
두 번째 쿼리가 오류인 이유를 생각해봅니다. category로 그룹화하면 IT 그룹에 3개 행이 있습니다. 그런데 SELECT에 title을 요청하면 "IT 그룹의 title은 무엇인가?"라는 질문에 'SQL 첫걸음', '파이썬 독학', '클린 코드' 세 가지 답이 가능합니다. 데이터베이스는 어떤 값을 표시해야 할지 알 수 없어 오류를 냅니다.
이 규칙이 9강에서 언급한 "집계 함수와 일반 열을 함께 쓸 수 없다"는 제한의 근본 원인입니다. GROUP BY 없이 집계 함수를 쓰면 전체가 하나의 그룹이 되는데, 이 하나의 그룹에서 각 행마다 다른 title 값을 어떻게 표시할지 결정할 수 없습니다.
🔀 다중 열 GROUP BY
이 섹션에서는 두 개 이상의 열로 그룹화하는 다중 GROUP BY를 살펴보겠습니다.
GROUP BY에 여러 열을 나열하면 열 조합이 동일한 행끼리 하나의 그룹이 됩니다. 예를 들어 GROUP BY category, status는 "IT + DONE", "IT + PENDING", "경제 + DONE"처럼 두 열의 조합으로 그룹화합니다.
주문 테이블에서 회원별·상태별 주문 수를 구해봅니다.
SELECT
member_id AS 회원번호,
status AS 주문상태,
COUNT(*) AS 주문건수,
SUM(quantity) AS 총수량
FROM orders
GROUP BY member_id, status
ORDER BY member_id, status;
각 회원과 주문 상태의 조합이 하나의 행으로 나타납니다. 1번 회원의 DONE 주문, 1번 회원의 PENDING 주문, 2번 회원의 DONE 주문처럼 세분화됩니다.
카테고리별 상태별 도서 통계도 구할 수 있습니다. 단, 주문 테이블(status)과 도서 테이블(category)은 별개이므로 여기서는 books의 카테고리와 stock의 유무(NULL 여부)로 다중 그룹화를 실습해봅니다.
-- 카테고리와 저자의 조합별 통계
SELECT
category,
author,
COUNT(*) AS 도서수,
SUM(stock) AS 재고합계
FROM books
GROUP BY category, author
ORDER BY category, author;
같은 카테고리에 저자가 여럿이면 각각 별도 행으로 나타납니다. 한 저자가 같은 카테고리에 여러 권을 썼다면 하나의 행으로 집계됩니다.
다중 GROUP BY를 쓸 때 ORDER BY도 함께 명시해 결과가 의미 있는 순서로 나타나게 하는 것이 좋습니다.
🔗 WHERE + GROUP BY 조합
이 섹션에서는 WHERE로 먼저 행을 필터링한 후 GROUP BY로 집계하는 패턴을 살펴보겠습니다.
WHERE와 GROUP BY를 함께 사용할 때 실행 순서가 중요합니다. WHERE는 GROUP BY보다 먼저 실행되므로, GROUP BY 대상이 되는 행을 먼저 WHERE로 걸러낼 수 있습니다.
-- 2024년 5월 이후 주문만 대상으로 회원별 집계
SELECT
member_id AS 회원번호,
COUNT(*) AS 주문건수,
SUM(quantity) AS 총주문수량
FROM orders
WHERE order_date >= '2024-05-01'
GROUP BY member_id
ORDER BY 주문건수 DESC;
WHERE로 5월 이후 주문만 남긴 다음, 그 주문들을 회원별로 집계합니다. WHERE를 먼저 처리하므로 4월 이전 주문은 집계에서 완전히 제외됩니다.
취소되지 않은 주문만 대상으로 도서별 판매량을 구해봅니다.
SELECT
book_id AS 도서번호,
COUNT(*) AS 주문건수,
SUM(quantity) AS 총판매수량
FROM orders
WHERE status != 'CANCEL'
GROUP BY book_id
ORDER BY 총판매수량 DESC;
취소된 주문을 제외하고 도서별 실제 판매 집계가 나타납니다. 가장 많이 팔린 도서(총판매수량 내림차순)를 파악할 수 있습니다.
WHERE + GROUP BY + ORDER BY의 조합은 실무 보고서 쿼리의 기본 골격입니다. 이 패턴을 완전히 익혀두면 대부분의 통계 요구사항을 처리할 수 있습니다.
📝 핵심 요약
이번 강에서 배운 내용을 정리하겠습니다.
| 개념 | 문법 | 설명 |
|---|---|---|
| 단일 열 그룹화 | GROUP BY 열 | 해당 열 값이 같은 행을 하나의 그룹으로 묶음 |
| 다중 열 그룹화 | GROUP BY 열1, 열2 | 열 조합이 같은 행끼리 묶음 |
| SELECT 규칙 | GROUP BY 열 or 집계 함수만 | 그룹화하지 않은 일반 열은 SELECT에 쓸 수 없음 |
| 실행 순서 | FROM → WHERE → GROUP BY → SELECT → ORDER BY | WHERE가 GROUP BY보다 먼저 실행 |
| 날짜 그룹화 | GROUP BY strftime('%Y-%m', 날짜열) | 월·연도 단위 집계에 활용 |
GROUP BY는 집계 함수와 결합해 데이터 분석의 핵심 도구로 활용됩니다. 그런데 "카테고리별 평균 가격이 20,000원 이상인 카테고리만 보고 싶다"처럼 집계 결과를 조건으로 거를 때는 WHERE를 쓸 수 없습니다. WHERE는 GROUP BY보다 먼저 실행되기 때문에 집계 결과를 아직 모르기 때문입니다. 다음 11강에서는 이 문제를 해결하는 HAVING을 배웁니다. GROUP BY + HAVING 조합으로 집계 결과에 조건을 걸 수 있게 됩니다.
관련 주제
- GROUP BY
- 다중 컬럼 그룹화
- 카테고리별 통계
- 그룹별 집계
- 개발·프로그래밍
- 개발·프로그래밍 강의
- SQL·데이터베이스 입문 24강
- 무료강의
- 무료 온라인 강의
- NUGUNA
- 누구나
📚 시리즈 전체 공유
SQL·데이터베이스 입문 24강
이 강의가 속한 시리즈는 총 24강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.
댓글
불러오는 중...
