SQL GROUP BY와 집계 함수: 데이터 요약하기
Published: 2026-04-08
GROUP BY는 하나 이상의 열에서 같은 값을 가진 여러 행을 하나의 요약 행으로 묶습니다. COUNT, SUM, AVG, MIN, MAX 같은 집계 함수와 함께 사용하면 거의 모든 리포팅·분석 쿼리의 기반이 됩니다. GROUP BY는 SQLite, PostgreSQL, MySQL, SQL Server, Oracle을 포함한 모든 SQL 데이터베이스에서 지원됩니다.
샘플 데이터
모든 예제에서 두 개의 테이블을 사용합니다.
employees 테이블:
| id | name | department | hire_date | salary |
|---|---|---|---|---|
| 1 | Alice | Engineering | 2023-01-15 | 95000 |
| 2 | Bob | Engineering | 2023-06-01 | 85000 |
| 3 | Carol | Engineering | 2024-03-10 | 85000 |
| 4 | Dave | Sales | 2023-02-20 | 72000 |
| 5 | Eve | Sales | 2023-09-05 | 68000 |
| 6 | Frank | Sales | 2024-01-15 | 65000 |
| 7 | Grace | Marketing | 2023-04-01 | 82000 |
| 8 | Henry | Marketing | 2024-06-01 | 70000 |
orders 테이블:
| id | customer | product | category | quantity | unit_price | order_date | status |
|---|---|---|---|---|---|---|---|
| 1 | Acme Corp | Laptop Pro | Electronics | 3 | 1299.99 | 2026-01-05 | delivered |
| 2 | Acme Corp | Wireless Mouse | Electronics | 10 | 29.99 | 2026-01-12 | delivered |
| 3 | Globex | Office Chair | Furniture | 5 | 249.99 | 2026-01-18 | delivered |
| 4 | Globex | Standing Desk | Furniture | 2 | 599.99 | 2026-01-20 | shipped |
| 5 | Initech | Notebook Pack | Stationery | 20 | 12.99 | 2026-02-03 | delivered |
| 6 | Initech | Pen Set | Stationery | 15 | 8.99 | 2026-02-07 | delivered |
| 7 | Acme Corp | Monitor 27" | Electronics | 4 | 399.99 | 2026-02-14 | delivered |
| 8 | Umbrella Ltd | Laptop Pro | Electronics | 2 | 1299.99 | 2026-02-20 | cancelled |
| 9 | Umbrella Ltd | Desk Lamp | Furniture | 8 | 39.99 | 2026-03-01 | delivered |
| 10 | Globex | Wireless Mouse | Electronics | 12 | 29.99 | 2026-03-05 | shipped |
| 11 | Initech | Monitor 27" | Electronics | 1 | 399.99 | 2026-03-10 | delivered |
| 12 | Acme Corp | Standing Desk | Furniture | 1 | 599.99 | 2026-03-15 | processing |
GROUP BY 문법
SELECT column1, aggregate_function(column2)
FROM table_name
WHERE condition
GROUP BY column1
HAVING aggregate_condition
ORDER BY column1;
- GROUP BY — 각 그룹을 정의하는 하나 이상의 열(값의 고유 조합이 하나의 그룹이 됨)
- 집계 함수(Aggregate functions) — 그룹 내 모든 행으로부터 단일 값을 계산
- HAVING — 그룹을 필터링(그룹 결과에 대한 WHERE와 같은 역할)
- 집계 함수 안에 있지 않은 SELECT의 모든 열은 반드시 GROUP BY에 포함되어야 함
SQL 실행 순서
SQL 절은 한 가지 순서로 작성되지만, 실제로는 다른 순서로 실행됩니다. 이를 이해하면 왜 HAVING에서는 집계 함수를 사용할 수 있지만 WHERE에서는 사용할 수 없는지, 그리고 왜 HAVING 절에서 SELECT 별칭을 사용할 수 없는지 알 수 있습니다.
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
| Step | Clause | 수행 내용 |
|---|---|---|
| 1 | FROM | 테이블을 로드하고 JOIN을 평가 |
| 2 | WHERE | 개별 행 필터링 — 여기서는 집계 함수를 사용할 수 없음 |
| 3 | GROUP BY | 남은 행들을 그룹으로 묶음 |
| 4 | HAVING | 그룹 필터링 — 여기서는 집계 함수를 사용할 수 있음 |
| 5 | SELECT | 출력 열과 별칭 계산 |
| 6 | ORDER BY | 최종 결과 정렬 |
그래서 WHERE status = 'delivered'는 그룹화 전에 실행되어(빠른 행 단위 필터), HAVING COUNT(*) > 2는 그룹화 후에 실행되어(그룹 전체를 필터) 동작합니다.
COUNT()
COUNT(*)는 그룹 내 모든 행을 셉니다. COUNT(column)은 해당 열에서 NULL이 아닌 값만 셉니다.
예시: 부서별 직원 수
예시: 고객별 주문 수
예시: 배송 완료 vs 기타 상태 개수 세기
CASE 표현식과 함께 COUNT를 사용해 조건에 맞는 주문만 셉니다.
SUM()
SUM(column)은 숫자 열에 대해 그룹 내 모든 값을 더합니다.
예시: 부서별 총 급여
예시: 카테고리별 총 매출
매출은 quantity * unit_price로 계산합니다.
예시: 고객·카테고리별 매출
여러 열로 그룹화하여 매출을 더 세분화합니다.
AVG()
AVG(column)은 그룹 내 NULL이 아닌 값들의 산술 평균을 반환합니다.
예시: 부서별 평균 급여
예시: 고객별 평균 주문 금액
MIN()과 MAX()
MIN(column)과 MAX(column)은 그룹 내 가장 작은 값과 가장 큰 값을 반환합니다.
예시: 부서별 급여 범위
예시: 고객별 첫 주문일과 마지막 주문일
여러 집계 함수 결합하기
하나의 쿼리에서 원하는 만큼 집계 함수를 조합할 수 있습니다.
예시: 부서 전체 요약
예시: 고객별 주문 요약
HAVING
HAVING은 집계 이후 그룹을 필터링합니다 — 그룹 결과에 대한 WHERE와 같습니다.
예시: 직원이 2명 초과인 부서
예시: 총 매출이 2000을 초과하는 고객
예시: 평균 주문 금액이 500 미만인 카테고리
WHERE vs. HAVING
WHERE는 그룹화 이전에 개별 행을 필터링합니다. HAVING은 집계 후 그룹을 필터링합니다.
예시: 고객별 배송 완료 주문 매출
WHERE로 그룹화 전에 행을 제외하고, HAVING으로 결과를 필터링합니다.
JOIN과 함께 사용하는 GROUP BY
먼저 테이블을 조인한 뒤, 여러 테이블에 걸친 데이터를 그룹화할 수 있습니다.
예시: 직원 조인을 통한 부서별 매출
예시: 카테고리·월별 주문 수와 매출
표현식과 함께 사용하는 GROUP BY
단순 열뿐 아니라 계산된 표현식으로도 그룹화할 수 있습니다.
예시: 연도별 입사 직원 수
예시: 월별 주문
COUNT(DISTINCT)
COUNT(DISTINCT column)은 그룹 내 고유 값의 개수를 셉니다.
예시: 고객별 고유 주문 상품 수
여러 조건으로 그룹 필터링하기
HAVING은 AND/OR 조합을 포함한 모든 불리언 표현식을 지원합니다.
예시: 고가·활성 고객 찾기
주문이 1건 초과이고 총 매출이 1000을 넘는 고객을 찾습니다.
실전 예제: 매출 리포트
지금까지 내용을 모두 합쳐 전체 매출 요약 리포트를 만들어 봅니다.
예시: 카테고리 성과 리포트
예시: 매출 기준 상위 상품
GROUP BY vs. 윈도 함수(Window Functions)
GROUP BY는 행을 그룹당 한 행으로 압축합니다. 윈도 함수는 모든 행을 유지한 채 계산된 열을 추가합니다. 자세한 내용은 SQL 윈도 함수 튜토리얼을 참고하세요.
예시: GROUP BY는 부서당 한 행만 반환
예시: 윈도 함수는 모든 행을 유지
GROUP BY의 일반적인 활용 사례
- 리포팅 대시보드 — 카테고리나 기간별 합계, 평균, 개수 요약
- 인원 분석 — 부서·그룹별 직원, 사용자, 레코드 수 집계
- 매출 분해 — 상품, 지역, 고객별 총·평균 매출
- 추세 분석 — 월·연도별로 그룹화해 시간에 따른 패턴 파악
- 그룹별 Top-N — 서브쿼리나 CTE와 결합해 각 그룹의 최고 실적 찾기
- 데이터 검증 — 그룹별 중복이나 NULL 값 개수 세기
- 코호트 분석 — 가입일이나 행동 세그먼트별 사용자 그룹화
성능 팁
- GROUP BY 열에 인덱스 생성 — 그룹화 대상 열에 인덱스를 두면 정렬·그룹 단계가 빨라집니다.
- WHERE로 먼저 필터링 — HAVING으로 나중에 필터링하는 것보다 WHERE로 그룹화 전에 행을 줄이는 것이 더 빠릅니다.
- SELECT * 지양 — 실제로 필요한 열만 선택하면, 집계 연산이 더 적은 데이터를 처리하게 되어 유리합니다.
- 커버링 인덱스 사용 — GROUP BY 열과 집계 대상 열을 모두 포함하는 인덱스는 전체 테이블 스캔을 피할 수 있습니다.
- 머티리얼라이즈드 뷰 고려 — 큰 테이블에서 자주 계산하는 집계는, 일부 DB에서 지원하는 사전 계산 요약(머티리얼라이즈드 뷰)을 활용할 수 있습니다.
정리
- GROUP BY는 같은 열 값을 공유하는 행들을 고유 조합당 하나의 요약 행으로 압축합니다.
- **COUNT(*)**는 모든 행을, **COUNT(column)**은 NULL이 아닌 값만, **COUNT(DISTINCT column)**은 고유 값 개수를 셉니다.
- SUM은 숫자 값을 합산하고, AVG는 평균을, MIN / MAX는 최소·최대 값을 찾습니다.
- SELECT의 모든 열은 GROUP BY에 포함되거나 집계 함수로 감싸져야 합니다.
- WHERE는 그룹화 전 행을 필터링하고, HAVING은 집계 후 그룹을 필터링합니다.
SUBSTR(date, 1, 7)같은 표현식으로도 GROUP BY를 할 수 있어 월 단위 그룹화 등이 가능합니다.- 그룹화 전에 테이블을 JOIN하면 여러 소스의 데이터를 하나의 리포트로 결합할 수 있습니다.
- 개별 행을 유지하면서 그룹 수준 지표가 필요하다면 GROUP BY 대신 윈도 함수를 사용하세요.
GROUP BY를 JOIN, CASE 표현식, HAVING 절과 조합해, 필요한 데이터 분석 리포트를 직접 만들어 보세요!