데이터를 집계하고 분석하는 SQL의 강력한 기능을 배워봐요! 📊
COUNT, SUM, AVG 같은 집계함수와 GROUP BY를 활용하면 데이터베이스에서 바로 통계를 계산할 수 있어요. Python으로 일일이 계산할 필요 없이 SQL 한 줄이면 끝! 실무에서는 판매 통계, 사용자 분석, 로그 집계 등에 필수적으로 사용되는 기능이에요.
(30분 완독 ⭐⭐⭐)
🎯 오늘의 학습 목표
📚 사전 지식
🎯 학습 목표 1: 집계함수 이해하기 (COUNT, SUM, AVG, MIN, MAX)
한 줄 설명
집계함수 = 여러 행을 계산하여 하나의 결과로 반환하는 함수
데이터를 세고(COUNT), 합치고(SUM), 평균내는(AVG) 등의 통계 작업을 SQL에서 바로 처리할 수 있어요!
기본 집계함수 5가지
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import sqlite3
# 데이터베이스 연결
conn = sqlite3.connect('store.db')
cursor = conn.cursor()
# 샘플 데이터 생성
cursor.execute('''
CREATE TABLE IF NOT EXISTS sales (
id INTEGER PRIMARY KEY,
product TEXT,
category TEXT,
price INTEGER,
quantity INTEGER,
sale_date TEXT
)
''')
# 샘플 데이터 삽입
sales_data = [
('노트북', '전자제품', 1200000, 2, '2025-01-10'),
('키보드', '전자제품', 80000, 5, '2025-01-11'),
('마우스', '전자제품', 30000, 10, '2025-01-12'),
('책상', '가구', 150000, 3, '2025-01-13'),
('의자', '가구', 200000, 2, '2025-01-14'),
('모니터', '전자제품', 300000, 4, '2025-01-15'),
]
cursor.executemany(
'INSERT INTO sales (product, category, price, quantity, sale_date) VALUES (?, ?, ?, ?, ?)',
sales_data
)
conn.commit()
1. COUNT() - 행 개수 세기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 전체 판매 건수
cursor.execute('SELECT COUNT(*) FROM sales')
print(f"총 판매 건수: {cursor.fetchone()[0]}건")
# 출력: 총 판매 건수: 6건
# 특정 카테고리 판매 건수
cursor.execute("SELECT COUNT(*) FROM sales WHERE category = '전자제품'")
print(f"전자제품 판매 건수: {cursor.fetchone()[0]}건")
# 출력: 전자제품 판매 건수: 4건
# DISTINCT로 고유 카테고리 개수
cursor.execute('SELECT COUNT(DISTINCT category) FROM sales')
print(f"카테고리 종류: {cursor.fetchone()[0]}개")
# 출력: 카테고리 종류: 2개
2. SUM() - 합계 계산
1
2
3
4
5
6
7
8
9
10
# 전체 판매 금액
cursor.execute('SELECT SUM(price * quantity) FROM sales')
total_revenue = cursor.fetchone()[0]
print(f"총 매출: {total_revenue:,}원")
# 출력: 총 매출: 4,450,000원
# 전자제품 판매 수량 합계
cursor.execute("SELECT SUM(quantity) FROM sales WHERE category = '전자제품'")
print(f"전자제품 판매량: {cursor.fetchone()[0]}개")
# 출력: 전자제품 판매량: 21개
3. AVG() - 평균 계산
1
2
3
4
5
6
7
8
9
10
# 평균 가격
cursor.execute('SELECT AVG(price) FROM sales')
avg_price = cursor.fetchone()[0]
print(f"평균 가격: {avg_price:,.0f}원")
# 출력: 평균 가격: 326,667원
# 전자제품 평균 가격
cursor.execute("SELECT AVG(price) FROM sales WHERE category = '전자제품'")
print(f"전자제품 평균 가격: {cursor.fetchone()[0]:,.0f}원")
# 출력: 전자제품 평균 가격: 402,500원
4. MIN() / MAX() - 최소값 / 최대값
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 최저가 상품
cursor.execute('SELECT MIN(price), product FROM sales')
min_price, product = cursor.fetchone()
print(f"최저가 상품: {product} ({min_price:,}원)")
# 출력: 최저가 상품: 마우스 (30,000원)
# 최고가 상품
cursor.execute('SELECT MAX(price), product FROM sales')
max_price, product = cursor.fetchone()
print(f"최고가 상품: {product} ({max_price:,}원)")
# 출력: 최고가 상품: 노트북 (1,200,000원)
# 가격 범위
cursor.execute('SELECT MIN(price) AS 최저가, MAX(price) AS 최고가 FROM sales')
result = cursor.fetchone()
print(f"가격 범위: {result[0]:,}원 ~ {result[1]:,}원")
# 출력: 가격 범위: 30,000원 ~ 1,200,000원
여러 집계함수 동시 사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 한 번에 여러 통계 조회
cursor.execute('''
SELECT
COUNT(*) AS 총건수,
SUM(price * quantity) AS 총매출,
AVG(price) AS 평균가격,
MIN(price) AS 최저가,
MAX(price) AS 최고가
FROM sales
''')
stats = cursor.fetchone()
print(f"""
=== 판매 통계 ===
총 건수: {stats[0]}건
총 매출: {stats[1]:,}원
평균 가격: {stats[2]:,.0f}원
최저가: {stats[3]:,}원
최고가: {stats[4]:,}원
""")
출력:
1
2
3
4
5
6
=== 판매 통계 ===
총 건수: 6건
총 매출: 4,450,000원
평균 가격: 326,667원
최저가: 30,000원
최고가: 1,200,000원
🎯 학습 목표 2: GROUP BY로 그룹화하기
한 줄 설명
GROUP BY = 같은 값을 가진 행들을 그룹으로 묶어 집계
카테고리별, 날짜별, 사용자별 등 특정 기준으로 데이터를 그룹화해서 통계를 낼 수 있어요!
기본 GROUP BY 사용법
1
2
3
4
5
6
7
8
9
10
# 카테고리별 판매 건수
cursor.execute('''
SELECT category, COUNT(*) AS 판매건수
FROM sales
GROUP BY category
''')
print("=== 카테고리별 판매 건수 ===")
for row in cursor.fetchall():
print(f"{row[0]}: {row[1]}건")
출력:
1
2
3
=== 카테고리별 판매 건수 ===
가구: 2건
전자제품: 4건
카테고리별 매출 통계
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cursor.execute('''
SELECT
category,
COUNT(*) AS 판매건수,
SUM(price * quantity) AS 총매출,
AVG(price) AS 평균가격,
SUM(quantity) AS 판매량
FROM sales
GROUP BY category
ORDER BY 총매출 DESC
''')
print("\n=== 카테고리별 상세 통계 ===")
for row in cursor.fetchall():
print(f"""
카테고리: {row[0]}
판매 건수: {row[1]}건
총 매출: {row[2]:,}원
평균 가격: {row[3]:,.0f}원
판매량: {row[4]}개
""")
출력:
1
2
3
4
5
6
7
8
9
10
11
12
=== 카테고리별 상세 통계 ===
카테고리: 전자제품
판매 건수: 4건
총 매출: 3,300,000원
평균 가격: 402,500원
판매량: 21개
카테고리: 가구
판매 건수: 2건
총 매출: 850,000원
평균 가격: 175,000원
판매량: 5개
날짜별 매출 추이
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 날짜별 매출
cursor.execute('''
SELECT
sale_date,
COUNT(*) AS 판매건수,
SUM(price * quantity) AS 일매출
FROM sales
GROUP BY sale_date
ORDER BY sale_date
''')
print("\n=== 일별 매출 추이 ===")
for row in cursor.fetchall():
print(f"{row[0]}: {row[1]}건, {row[2]:,}원")
출력:
1
2
3
4
5
6
7
=== 일별 매출 추이 ===
2025-01-10: 1건, 2,400,000원
2025-01-11: 1건, 400,000원
2025-01-12: 1건, 300,000원
2025-01-13: 1건, 450,000원
2025-01-14: 1건, 400,000원
2025-01-15: 1건, 1,200,000원
가격대별 상품 분류
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cursor.execute('''
SELECT
CASE
WHEN price < 100000 THEN '저가'
WHEN price < 500000 THEN '중가'
ELSE '고가'
END AS 가격대,
COUNT(*) AS 상품수,
AVG(price) AS 평균가격
FROM sales
GROUP BY 가격대
ORDER BY 평균가격
''')
print("\n=== 가격대별 상품 분포 ===")
for row in cursor.fetchall():
print(f"{row[0]}: {row[1]}개 (평균 {row[2]:,.0f}원)")
출력:
1
2
3
4
=== 가격대별 상품 분포 ===
저가: 2개 (평균 55,000원)
중가: 2개 (평균 250,000원)
고가: 2개 (평균 750,000원)
🎯 학습 목표 3: HAVING으로 그룹 조건 걸기
한 줄 설명
HAVING = GROUP BY 결과에 조건을 거는 절
WHERE는 그룹화 전 행을 필터링하고, HAVING은 그룹화 후 결과를 필터링해요!
WHERE vs HAVING 차이
1
2
3
4
5
6
7
8
9
10
11
# WHERE: 그룹화 전 필터링 (개별 행)
cursor.execute('''
SELECT category, COUNT(*) AS 판매건수
FROM sales
WHERE price >= 100000 -- 10만원 이상 상품만
GROUP BY category
''')
print("=== WHERE: 10만원 이상 상품만 집계 ===")
for row in cursor.fetchall():
print(f"{row[0]}: {row[1]}건")
출력:
1
2
3
=== WHERE: 10만원 이상 상품만 집계 ===
가구: 2건
전자제품: 2건
1
2
3
4
5
6
7
8
9
10
11
# HAVING: 그룹화 후 필터링 (그룹 결과)
cursor.execute('''
SELECT category, COUNT(*) AS 판매건수
FROM sales
GROUP BY category
HAVING COUNT(*) >= 3 -- 판매건수 3건 이상인 카테고리만
''')
print("\n=== HAVING: 판매건수 3건 이상 카테고리 ===")
for row in cursor.fetchall():
print(f"{row[0]}: {row[1]}건")
출력:
1
2
=== HAVING: 판매건수 3건 이상 카테고리 ===
전자제품: 4건
HAVING으로 상위 그룹 추출
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 매출 100만원 이상 카테고리
cursor.execute('''
SELECT
category,
SUM(price * quantity) AS 총매출,
COUNT(*) AS 판매건수
FROM sales
GROUP BY category
HAVING SUM(price * quantity) >= 1000000
ORDER BY 총매출 DESC
''')
print("\n=== 매출 100만원 이상 카테고리 ===")
for row in cursor.fetchall():
print(f"{row[0]}: {row[1]:,}원 ({row[2]}건)")
출력:
1
2
=== 매출 100만원 이상 카테고리 ===
전자제품: 3,300,000원 (4건)
WHERE + HAVING 조합
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# WHERE로 먼저 필터링 → GROUP BY → HAVING으로 다시 필터링
cursor.execute('''
SELECT
category,
AVG(price) AS 평균가격,
COUNT(*) AS 상품수
FROM sales
WHERE sale_date >= '2025-01-12' -- 1월 12일 이후
GROUP BY category
HAVING COUNT(*) >= 2 -- 2개 이상 판매된 카테고리만
''')
print("\n=== 1월 12일 이후 2개 이상 판매 카테고리 ===")
for row in cursor.fetchall():
print(f"{row[0]}: 평균 {row[1]:,.0f}원 ({row[2]}개)")
출력:
1
2
3
=== 1월 12일 이후 2개 이상 판매 카테고리 ===
전자제품: 평균 165,000원 (2개)
가구: 평균 175,000원 (2개)
🎯 학습 목표 4: 서브쿼리와 고급 기법 익히기
한 줄 설명
서브쿼리 = 쿼리 안에 포함된 또 다른 쿼리
복잡한 조건이나 계산을 단계적으로 처리할 수 있어요!
서브쿼리 기본
1
2
3
4
5
6
7
8
9
10
11
# 평균 가격보다 비싼 상품 찾기
cursor.execute('''
SELECT product, price
FROM sales
WHERE price > (SELECT AVG(price) FROM sales)
ORDER BY price DESC
''')
print("=== 평균가보다 비싼 상품 ===")
for row in cursor.fetchall():
print(f"{row[0]}: {row[1]:,}원")
출력:
1
2
3
=== 평균가보다 비싼 상품 ===
노트북: 1,200,000원
모니터: 300,000원
IN을 사용한 서브쿼리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 가장 많이 팔린 카테고리의 상품들
cursor.execute('''
SELECT product, category, quantity
FROM sales
WHERE category = (
SELECT category
FROM sales
GROUP BY category
ORDER BY SUM(quantity) DESC
LIMIT 1
)
''')
print("\n=== 판매량 1위 카테고리 상품들 ===")
for row in cursor.fetchall():
print(f"{row[0]} ({row[1]}): {row[2]}개")
출력:
1
2
3
4
5
=== 판매량 1위 카테고리 상품들 ===
노트북 (전자제품): 2개
키보드 (전자제품): 5개
마우스 (전자제품): 10개
모니터 (전자제품): 4개
DISTINCT로 중복 제거
1
2
3
4
5
6
# 판매된 카테고리 목록 (중복 제거)
cursor.execute('SELECT DISTINCT category FROM sales ORDER BY category')
print("\n=== 판매 카테고리 ===")
for row in cursor.fetchall():
print(f"- {row[0]}")
출력:
1
2
3
=== 판매 카테고리 ===
- 가구
- 전자제품
실전 통계 쿼리 예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 카테고리별 베스트셀러
cursor.execute('''
SELECT
category,
product,
price * quantity AS 매출
FROM sales s1
WHERE price * quantity = (
SELECT MAX(price * quantity)
FROM sales s2
WHERE s1.category = s2.category
)
ORDER BY 매출 DESC
''')
print("\n=== 카테고리별 베스트셀러 ===")
for row in cursor.fetchall():
print(f"{row[0]}: {row[1]} ({row[2]:,}원)")
출력:
1
2
3
=== 카테고리별 베스트셀러 ===
전자제품: 노트북 (2,400,000원)
가구: 책상 (450,000원)
💡 실전 팁 & 주의사항
💡 Tip 1: GROUP BY 시 SELECT 절 제한
1
2
3
4
5
6
7
8
9
10
11
# ❌ 잘못된 예 - GROUP BY에 없는 컬럼은 SELECT 불가
# SELECT product, category, COUNT(*)
# FROM sales
# GROUP BY category
# ✅ 올바른 예 - GROUP BY의 컬럼이나 집계함수만 SELECT
cursor.execute('''
SELECT category, COUNT(*) AS 개수
FROM sales
GROUP BY category
''')
💡 Tip 2: HAVING은 항상 GROUP BY 뒤에
1
2
3
4
5
6
7
8
9
# ✅ 올바른 순서
cursor.execute('''
SELECT category, COUNT(*)
FROM sales
WHERE price > 50000 -- 1. WHERE로 행 필터링
GROUP BY category -- 2. 그룹화
HAVING COUNT(*) >= 2 -- 3. HAVING으로 그룹 필터링
ORDER BY COUNT(*) DESC -- 4. 정렬
''')
💡 Tip 3: NULL 값 처리
1
2
3
4
5
6
7
8
# COUNT(*)는 NULL 포함, COUNT(column)은 NULL 제외
cursor.execute('''
SELECT
COUNT(*) AS 전체행수,
COUNT(product) AS 상품수,
COUNT(DISTINCT category) AS 카테고리수
FROM sales
''')
💡 Tip 4: AS 별칭 활용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 별칭으로 가독성 향상
cursor.execute('''
SELECT
category AS 카테고리,
COUNT(*) AS 판매건수,
SUM(price * quantity) AS 총매출,
AVG(price) AS 평균가격
FROM sales
GROUP BY category
''')
# fetchone() 결과를 딕셔너리처럼 사용 (별칭 활용)
for row in cursor.fetchall():
print(f"{row[0]}: 매출 {row[2]:,}원")
🧪 연습 문제
문제 1: 월별 매출 분석
샘플 데이터를 확장하여 여러 달의 판매 데이터를 분석하세요.
요구사항:
- 월별 총 매출을 계산하세요
- 월별 평균 주문 금액을 계산하세요
- 매출이 200만원 이상인 월만 출력하세요
💡 힌트
substr(sale_date, 1, 7)로 ‘YYYY-MM’ 추출- GROUP BY 월별로 그룹화
- HAVING으로 매출 조건 필터링
✅ 정답
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 여러 달 데이터 추가
additional_sales = [
('스마트폰', '전자제품', 900000, 3, '2025-02-10'),
('태블릿', '전자제품', 600000, 2, '2025-02-15'),
('침대', '가구', 800000, 1, '2025-02-20'),
('소파', '가구', 1200000, 1, '2025-03-05'),
('냉장고', '전자제품', 1500000, 2, '2025-03-10'),
]
cursor.executemany(
'INSERT INTO sales (product, category, price, quantity, sale_date) VALUES (?, ?, ?, ?, ?)',
additional_sales
)
conn.commit()
# 월별 매출 분석
cursor.execute('''
SELECT
substr(sale_date, 1, 7) AS 월,
COUNT(*) AS 판매건수,
SUM(price * quantity) AS 총매출,
AVG(price * quantity) AS 평균주문금액
FROM sales
GROUP BY 월
HAVING 총매출 >= 2000000
ORDER BY 월
''')
print("=== 월별 매출 분석 (200만원 이상) ===")
for row in cursor.fetchall():
print(f"{row[0]}: {row[1]}건, 총매출 {row[2]:,}원, 평균 {row[3]:,.0f}원")
출력:
1
2
3
4
=== 월별 매출 분석 (200만원 이상) ===
2025-01: 6건, 총매출 4,450,000원, 평균 741,667원
2025-02: 3건, 총매출 3,500,000원, 평균 1,166,667원
2025-03: 2건, 총매출 4,200,000원, 평균 2,100,000원
💡 힌트
- GROUP BY category, product
- SUM(quantity)로 판매량 합계
- WHERE절에 서브쿼리로 MAX 비교
✅ 정답
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 카테고리별 상품별 판매량
cursor.execute('''
SELECT
category,
product,
SUM(quantity) AS 총판매량,
SUM(price * quantity) AS 총매출
FROM sales
GROUP BY category, product
ORDER BY category, 총판매량 DESC
''')
print("\n=== 카테고리별 상품별 판매량 ===")
for row in cursor.fetchall():
print(f"{row[0]} - {row[1]}: {row[2]}개 (매출 {row[3]:,}원)")
# 카테고리별 베스트셀러 (서브쿼리 사용)
cursor.execute('''
SELECT
s1.category,
s1.product,
SUM(s1.quantity) AS 총판매량
FROM sales s1
GROUP BY s1.category, s1.product
HAVING SUM(s1.quantity) = (
SELECT MAX(total_qty)
FROM (
SELECT SUM(quantity) AS total_qty
FROM sales s2
WHERE s2.category = s1.category
GROUP BY s2.product
)
)
ORDER BY s1.category
''')
print("\n=== 카테고리별 베스트셀러 ===")
for row in cursor.fetchall():
print(f"{row[0]}: {row[1]} ({row[2]}개)")
출력:
1
2
3
4
5
6
7
8
9
10
11
=== 카테고리별 상품별 판매량 ===
가구 - 책상: 3개 (매출 450,000원)
가구 - 의자: 2개 (매출 400,000원)
가구 - 침대: 1개 (매출 800,000원)
전자제품 - 마우스: 10개 (매출 300,000원)
전자제품 - 키보드: 5개 (매출 400,000원)
전자제품 - 모니터: 4개 (매출 1,200,000원)
=== 카테고리별 베스트셀러 ===
가구: 책상 (3개)
전자제품: 마우스 (10개)
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.