13 SQL GROUP BY 실습 1
1. 인트로
GROUP BY(그룹 바이)는 같은 값을 가진 행들을 묶어 집계 함수로 통계를 구하는 기능이다. “장르별 게임 수”, “월별 매출”, “카테고리별 평균 가격” 같은 통계 데이터를 만들 때 반드시 필요하다.
2. 실습 DB 준비 (7차시 game_store.db 이어서 사용)
1import sqlite32import pandas as pd3
4conn = sqlite3.connect("game_store.db")5cur = conn.cursor()6
7# 데이터 확인8df = pd.read_sql("SELECT * FROM games", conn)9print(df[["title","genre","platform","price","rating"]])3. GROUP BY(그룹 바이) 명령어 실습
3.1. GROUP BY 기본 개념
GROUP BY는 특정 컬럼의 값이 같은 행들을 하나의 그룹으로 묶는다.
1플랫폼이 'PC'인 행들 → PC 그룹2플랫폼이 'PS5'인 행들 → PS5 그룹3플랫폼이 'Switch'인 행들 → Switch 그룹각 그룹에 COUNT(), AVG(), SUM(), MAX(), MIN() 같은 집계 함수를 적용한다.
3.2. 주요 집계 함수
| 함수 | 의미 |
|---|---|
| COUNT(컬럼) | 행의 수를 센다 |
| SUM(컬럼) | 합계를 구한다 |
| AVG(컬럼) | 평균을 구한다 |
| MAX(컬럼) | 최대값을 구한다 |
| MIN(컬럼) | 최소값을 구한다 |
3.3. 플랫폼별 게임 수 집계
1df = pd.read_sql("""2 SELECT platform AS 플랫폼,3 COUNT(id) AS 게임수4 FROM games5 GROUP BY platform6 ORDER BY 게임수 DESC7""", conn)8print("=== 플랫폼별 게임 수 ===")9print(df)코드 설명:
- 3행:
COUNT(id)— 그룹 내 행의 수를 센다. - 5행:
GROUP BY platform— platform 값이 같은 행끼리 묶는다. - SELECT에는 GROUP BY 기준 컬럼과 집계 함수만 사용할 수 있다.
3.4. 장르별 평균 평점
1df = pd.read_sql("""2 SELECT genre AS 장르,3 COUNT(id) AS 게임수,4 ROUND(AVG(rating), 2) AS 평균평점,5 MAX(rating) AS 최고평점6 FROM games7 GROUP BY genre8 ORDER BY 평균평점 DESC9""", conn)10print("=== 장르별 통계 ===")11print(df)코드 설명:
- 4행:
ROUND(AVG(rating), 2)— 평균 평점을 소수점 둘째 자리까지 반올림한다. ROUND(값, 자릿수)— 지정한 자릿수로 반올림한다.
4. HAVING(해빙) 명령어 실습
4.1. HAVING이란?
WHERE는 개별 행을 필터링한다.
HAVING은 GROUP BY로 묶인 그룹을 필터링한다.
1WHERE → 그룹화 전에 행을 필터링2HAVING → 그룹화 후에 그룹을 필터링4.2. 게임이 2개 이상인 플랫폼만 조회
1df = pd.read_sql("""2 SELECT platform AS 플랫폼,3 COUNT(id) AS 게임수4 FROM games5 GROUP BY platform6 HAVING COUNT(id) >= 27 ORDER BY 게임수 DESC8""", conn)9print("=== 게임 2개 이상 플랫폼 ===")10print(df)코드 설명:
- 6행:
HAVING COUNT(id) >= 2— 그룹화 후 게임 수가 2개 이상인 그룹만 남긴다. - WHERE에는 집계 함수를 쓸 수 없으나 HAVING에는 쓸 수 있다.
4.3. HAVING 명령어 실습 — 평균 평점 4.5 이상인 장르
1df = pd.read_sql("""2 SELECT genre AS 장르,3 COUNT(id) AS 게임수,4 ROUND(AVG(rating), 1) AS 평균평점5 FROM games6 GROUP BY genre7 HAVING AVG(rating) >= 4.58 ORDER BY 평균평점 DESC9""", conn)10print("=== 평균 평점 4.5+ 장르 ===")11print(df)5. GROUP BY & JOIN 동시 적용 실습
1# JOIN + GROUP BY: 플랫폼별 유료 게임의 평균 가격과 최고 평점2df = pd.read_sql("""3 SELECT platform AS 플랫폼,4 COUNT(id) AS 유료게임수,5 ROUND(AVG(price), 0) AS 평균가격,6 MAX(rating) AS 최고평점7 FROM games8 WHERE price > 09 GROUP BY platform10 HAVING COUNT(id) >= 111 ORDER BY 평균가격 DESC12""", conn)13print("=== 플랫폼별 유료게임 통계 ===")14print(df)코드 설명:
- 8행:
WHERE price > 0— 그룹화 전에 무료 게임을 제외한다. - 10행:
HAVING COUNT(id) >= 1— 유료 게임이 1개 이상인 플랫폼만 표시한다. - WHERE와 HAVING이 함께 쓰일 때 실행 순서:
WHERE 먼저 → GROUP BY → HAVING
6. 활용 Plus — 리액트 대시보드 연동
리액트 게임 스토어 앱의 “통계 대시보드” 페이지에서 파이썬 API가 반환하는 데이터 예시이다.
1def get_stats():2 stats = {}3
4 # 플랫폼별 게임 수5 df1 = pd.read_sql(6 "SELECT platform, COUNT(*) AS cnt FROM games GROUP BY platform",7 conn8 )9 stats["by_platform"] = df1.to_dict("records")10
11 # 장르별 평균 평점12 df2 = pd.read_sql(13 "SELECT genre, ROUND(AVG(rating),2) AS avg_r FROM games GROUP BY genre",14 conn15 )16 stats["by_genre"] = df2.to_dict("records")17
18 return stats19
20import json21print(json.dumps(get_stats(), ensure_ascii=False, indent=2))코드 설명:
- 2행: 여러 통계를 딕셔너리에 담아 한 번에 반환한다.
- 21행:
json.dumps()로 JSON 문자열로 변환하여 출력한다. 리액트 API 응답 형태이다.
7. 문제풀기
① GROUP BY에 명시한 컬럼 ② 집계 함수 (COUNT, SUM, AVG, MAX, MIN) 이 두 가지 외의 컬럼은 SELECT에 직접 쓸 수 없다.
WHERE: GROUP BY 실행 전에 개별 행을 필터링한다. 집계 함수 사용 불가. HAVING: GROUP BY 실행 후에 그룹을 필터링한다. 집계 함수 사용 가능.
SELECT genre, COUNT() AS cnt FROM games GROUP BY genre HAVING COUNT() >= 3;
rating의 평균을 구한 후 소수점 첫째 자리까지 반올림한다. 예: 4.433… → 4.4