COUNT·SUM·AVG·MAX·MIN으로 여러 행을 단 하나의 요약 값으로 집계하는 집계 함수의 원리와 활용법을 익힙니다.
🎯 학습 목표
이 강을 마치면 여러 행에 걸친 데이터를 하나의 요약 값으로 집계하는 집계 함수 다섯 가지(COUNT, SUM, AVG, MAX, MIN)를 자유롭게 사용할 수 있습니다. "회원이 총 몇 명인지", "전체 주문 금액 합계는 얼마인지", "도서 평균 가격은 얼마인지"처럼 실무에서 가장 자주 필요한 통계 쿼리를 작성할 수 있게 됩니다. 또한 NULL이 집계 함수에 미치는 영향을 이해해 정확한 결과를 얻을 수 있습니다.
💡 집계 함수란 무엇인가
이 섹션에서는 집계 함수의 개념과 단일 행 함수(8강)와의 차이를 살펴보겠습니다.
8강에서 배운 ROUND(), ABS()는 단일 행 함수(Scalar Function)입니다. 각 행에 독립적으로 적용되어 행마다 하나의 결과를 반환합니다. 예를 들어 books 테이블에 7개 행이 있으면, ROUND(price, 0)도 7개의 결과를 반환합니다.
반면 집계 함수(Aggregate Function)는 여러 행을 받아 하나의 요약 값을 반환합니다. COUNT(*)는 7개 행을 받아 7이라는 단 하나의 숫자를 반환합니다. 이것이 집계 함수의 핵심 특성입니다. 단일 행 함수는 행의 수를 줄이지 않고, 집계 함수는 여러 행을 하나로 압축합니다.
SQL의 집계 함수 다섯 가지를 정리하면 다음과 같습니다.
| 함수 | 역할 | NULL 처리 |
|---|---|---|
| COUNT(*) | 전체 행 수 (NULL 포함) | NULL 포함 |
| COUNT(열) | 특정 열이 NULL이 아닌 행 수 | NULL 제외 |
| SUM(열) | 열 값의 합계 | NULL 제외 |
| AVG(열) | 열 값의 평균 | NULL 제외 (개수도 줄어듦) |
| MAX(열) | 열 값 중 최댓값 | NULL 제외 |
| MIN(열) | 열 값 중 최솟값 | NULL 제외 |
NULL 처리 방식이 중요합니다. COUNT(*)를 제외한 모든 집계 함수는 NULL 값을 무시하고 계산합니다. 이 때문에 COUNT(*)와 COUNT(열)의 결과가 달라질 수 있습니다.
🔢 COUNT — 행 수 세기
이 섹션에서는 가장 자주 사용하는 집계 함수 COUNT를 자세히 살펴보겠습니다.
COUNT(*)는 WHERE 조건을 통과한 모든 행의 수를 셉니다. NULL 포함입니다.
-- 전체 회원 수
SELECT COUNT(*) AS 전체회원수
FROM members;
-- 전체 도서 수
SELECT COUNT(*) AS 전체도서수
FROM books;
-- VIP 회원 수
SELECT COUNT(*) AS VIP회원수
FROM members
WHERE grade = 'VIP';
첫 번째는 6(홍길동 추가분 포함), 두 번째는 7, 세 번째는 2가 반환됩니다. WHERE를 사용하면 조건을 만족하는 행만 COUNT합니다.
COUNT(열)는 해당 열의 값이 NULL이 아닌 행 수만 셉니다.
-- grade가 설정된 회원 수 (NULL 제외)
SELECT
COUNT(*) AS 전체회원수,
COUNT(grade) AS 등급설정회원수
FROM members;
COUNT(*)는 6, COUNT(grade)는 5가 나타납니다. 6강에서 추가한 홍길동(grade = NULL)이 COUNT(grade)에서 제외되기 때문입니다. 이 차이를 이해하면 NULL이 있는 열의 집계를 정확하게 처리할 수 있습니다.
DISTINCT와 결합하면 유니크한 값의 수를 셀 수 있습니다.
-- 몇 가지 카테고리가 있는지
SELECT COUNT(DISTINCT category) AS 카테고리수
FROM books;
-- 주문한 회원이 총 몇 명인지 (중복 제거)
SELECT COUNT(DISTINCT member_id) AS 주문회원수
FROM orders;
첫 번째는 3(IT, 경제, 소설), 두 번째는 주문 테이블에서 중복 없는 회원 수가 반환됩니다.
➕ SUM과 AVG — 합계와 평균
이 섹션에서는 수치 열의 합계와 평균을 구하는 SUM과 AVG를 살펴보겠습니다.
SUM(열)은 수치 열의 모든 값을 더합니다. NULL은 0처럼 취급하는 게 아니라 완전히 무시합니다.
-- 도서 전체 재고 합계
SELECT SUM(stock) AS 총재고수량
FROM books;
-- IT 카테고리 도서 가격 합계
SELECT SUM(price) AS IT도서가격합계
FROM books
WHERE category = 'IT';
첫 번째는 books 테이블의 모든 재고(stock)를 더한 값, 두 번째는 IT 카테고리 3권(22000 + 28000 + 33000 = 83000)이 반환됩니다.
AVG(열)는 수치 열의 평균값을 계산합니다. NULL 행을 제외한 합계 ÷ NULL 제외 행 수입니다.
-- 도서 평균 가격
SELECT
AVG(price) AS 평균가격,
ROUND(AVG(price), 0) AS 평균가격_정수
FROM books;
-- 카테고리별 평균 가격 (10강 GROUP BY로 더 정확히 가능)
SELECT AVG(price) AS IT평균가격
FROM books
WHERE category = 'IT';
전체 평균은 7개 도서 가격의 합 ÷ 7입니다. ROUND를 결합해 소수점을 없앨 수 있습니다.
NULL이 AVG에 미치는 영향을 이해하는 것이 중요합니다. 만약 stock이 NULL인 행이 있다면 AVG(stock)는 NULL을 제외한 나머지 행으로만 평균을 냅니다. 이는 "NULL = 0으로 취급"과 전혀 다른 결과입니다. 재고가 없어서 NULL인지, 아직 파악 안 되어 NULL인지에 따라 AVG(stock)와 AVG(COALESCE(stock, 0))는 다른 의미를 가집니다.
🏆 MAX와 MIN — 최댓값과 최솟값
이 섹션에서는 열에서 가장 크거나 작은 값을 찾는 MAX와 MIN을 살펴보겠습니다.
MAX(열)과 MIN(열)은 수치뿐 아니라 문자열·날짜에도 사용 가능합니다. 문자열은 사전 순서(유니코드) 기준으로 가장 나중(MAX)·가장 앞(MIN) 값을 반환합니다.
-- 가장 비싼/저렴한 도서 가격
SELECT
MAX(price) AS 최고가,
MIN(price) AS 최저가,
MAX(price) - MIN(price) AS 가격범위
FROM books;
최고가 33,000, 최저가 13,000, 가격 범위 20,000이 반환됩니다.
-- 가장 최근/오래된 주문 날짜
SELECT
MAX(order_date) AS 최신주문일,
MIN(order_date) AS 최초주문일
FROM orders;
-- 가나다 순서 첫 번째/마지막 회원 이름
SELECT
MIN(name) AS 가나다첫번째,
MAX(name) AS 가나다마지막
FROM members;
날짜 문자열('YYYY-MM-DD')은 사전 순서가 날짜 순서와 일치하므로 MAX/MIN이 올바른 최신·최고일을 반환합니다.
7강에서 ORDER BY + LIMIT 1로 최댓값 행 전체를 가져올 수 있었습니다. MAX/MIN은 값 하나만 반환하지만, 값 외에 다른 열 정보도 함께 보고 싶다면 16강(서브쿼리)에서 배울 패턴이 필요합니다. 지금은 집계 값 자체를 구하는 데 집중합니다.
🔗 여러 집계 함수 조합
이 섹션에서는 하나의 SELECT에서 여러 집계 함수를 동시에 사용하는 방법을 살펴보겠습니다.
하나의 SELECT에서 여러 집계 함수를 나열하면 한 번의 쿼리로 여러 통계를 얻을 수 있습니다.
-- 도서 관련 주요 통계 한 번에 조회
SELECT
COUNT(*) AS 총도서수,
SUM(price) AS 가격합계,
ROUND(AVG(price), 0) AS 평균가격,
MAX(price) AS 최고가,
MIN(price) AS 최저가,
SUM(stock) AS 총재고
FROM books;
결과는 단 한 행으로, 도서 현황을 한눈에 파악할 수 있는 대시보드 쿼리가 됩니다.
WHERE를 결합하면 특정 조건에서의 통계를 구할 수 있습니다.
-- 완료된 주문의 통계
SELECT
COUNT(*) AS 완료주문수,
SUM(quantity) AS 총주문수량,
AVG(quantity) AS 평균주문수량
FROM orders
WHERE status = 'DONE';
-- PENDING 또는 CANCEL 상태 주문 수 비교
SELECT
COUNT(CASE WHEN status = 'PENDING' THEN 1 END) AS 대기중,
COUNT(CASE WHEN status = 'CANCEL' THEN 1 END) AS 취소됨,
COUNT(CASE WHEN status = 'DONE' THEN 1 END) AS 완료됨
FROM orders;
두 번째 쿼리에서 CASE WHEN은 18강에서 자세히 배웁니다. 여기서는 "조건부 COUNT"가 가능하다는 것만 알아두세요. 이 패턴은 피벗 집계(Pivot Aggregation)라고 불리며 보고서 쿼리에서 매우 자주 씁니다.
⚠️ 집계 함수의 주의사항
이 섹션에서는 집계 함수 사용 시 자주 마주치는 오류와 함정을 살펴보겠습니다.
첫 번째 주의사항은 집계 함수와 일반 열을 SELECT에서 함께 쓸 수 없다는 것입니다.
-- 오류! title은 일반 열, COUNT(*)는 집계 함수
SELECT title, COUNT(*)
FROM books;
-- 올바른 방법 1: 집계 함수만 사용
SELECT COUNT(*) FROM books;
-- 올바른 방법 2: GROUP BY로 그룹화 (10강)
SELECT category, COUNT(*) FROM books GROUP BY category;
title은 각 행마다 다른 값을 가지지만 COUNT(*)는 하나의 값을 반환합니다. 이 두 가지를 어떻게 표시해야 할지 데이터베이스가 알 수 없어 오류가 발생합니다. 일반 열과 함께 집계 함수를 쓰려면 반드시 GROUP BY가 필요하며, 이는 10강에서 배웁니다.
두 번째는 AVG의 NULL 처리입니다. 예를 들어 10명 중 3명의 성적이 NULL(미응시)이라면 AVG(score)는 7명의 합 ÷ 7입니다. "전체 10명의 평균"을 구하고 싶다면 AVG(COALESCE(score, 0))처럼 NULL을 0으로 치환해야 합니다.
-- NULL을 0으로 대체한 평균 (NULL = 0점으로 취급)
SELECT AVG(COALESCE(quantity, 0)) AS 실제평균
FROM orders;
세 번째는 집계 결과에 ORDER BY 적용 가능하다는 점입니다. 실행 순서는 FROM → WHERE → 집계 → ORDER BY이므로, ORDER BY에서는 집계 함수 별칭이나 식을 사용할 수 있습니다. 10강의 GROUP BY와 결합하면 "카테고리별 평균 가격 내림차순 정렬" 같은 쿼리를 작성할 수 있게 됩니다.
📝 핵심 요약
이번 강에서 배운 내용을 정리하겠습니다.
| 함수 | 역할 | NULL 처리 | 예시 |
|---|---|---|---|
| COUNT(*) | 전체 행 수 | 포함 | COUNT(*) AS 총회원수 |
| COUNT(열) | NULL 아닌 행 수 | 제외 | COUNT(grade) |
| COUNT(DISTINCT 열) | 유니크한 값의 수 | 제외 | COUNT(DISTINCT category) |
| SUM(열) | 합계 | 제외 | SUM(price) |
| AVG(열) | 평균 (NULL 행 분모에서 제외) | 제외 | AVG(price) |
| MAX(열) | 최댓값 | 제외 | MAX(order_date) |
| MIN(열) | 최솟값 | 제외 | MIN(price) |
집계 함수는 SQL로 할 수 있는 가장 강력한 작업 중 하나입니다. 하지만 지금까지는 테이블 전체 또는 WHERE로 필터된 전체 행에 대해 집계를 구했습니다. 다음 10강에서는 GROUP BY를 배웁니다. "카테고리별 도서 수", "등급별 회원 수", "월별 주문 건수"처럼 그룹을 나누어 각 그룹마다 집계하는 강력한 기능을 익히게 됩니다. GROUP BY와 집계 함수의 조합은 실무 SQL의 핵심입니다.
관련 주제
- COUNT SUM AVG
- MAX MIN
- 집계 함수
- NULL 처리
- 통계 쿼리
- 개발·프로그래밍
- 개발·프로그래밍 강의
- SQL·데이터베이스 입문 24강
- 무료강의
- 무료 온라인 강의
- NUGUNA
- 누구나
📚 시리즈 전체 공유
SQL·데이터베이스 입문 24강
이 강의가 속한 시리즈는 총 24강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.
댓글
불러오는 중...
