반응형
Web Development · MySQL
MySQL HAVING —
GROUP BY 결과 필터링 완전 정리
HAVING vs WHERE · 집계함수 조건 · WHERE + GROUP BY + HAVING 전체 흐름
시작 오늘 분석할 코드
-- 총 구매액이 1000 초과인 회원만 SELECT userID AS '사용자', SUM(price*amount) AS '총구매액' FROM buytbl GROUP BY userID HAVING SUM(price*amount) > 1000 ; -- 집계함수(SUM)를 필터링하기 때문에 WHERE가 아닌 HAVING 사용
HAVING은 WHERE와 달리 GROUP BY 이후에 적용된다. 집계 결과(SUM, COUNT, AVG...)에 조건을 걸 때만 쓴다. 개별 행 조건은 여전히 WHERE로 걸면 된다.
1 HAVING이 필요한 이유
WHERE는 개별 행을 필터링하지만, GROUP BY로 집계한 결과에 조건을 걸 수는 없다. 집계 결과(SUM > 1000)는 GROUP BY 이후에 생성되기 때문이다.
-- ❌ 잘못된 방법: WHERE에 집계함수 사용 시 오류 SELECT userID, SUM(price*amount) FROM buytbl WHERE SUM(price*amount) > 1000 -- Error: Invalid use of group function GROUP BY userID; -- ✅ 올바른 방법: HAVING 사용 SELECT userID, SUM(price*amount) FROM buytbl GROUP BY userID HAVING SUM(price*amount) > 1000;
2 WHERE vs HAVING 차이
| 구분 | WHERE | HAVING |
|---|---|---|
| 적용 대상 | 개별 행 (GROUP BY 이전) | 그룹 결과 (GROUP BY 이후) |
| 실행 시점 | GROUP BY 이전 | GROUP BY 이후 |
| 집계함수 사용 | 불가 (오류) | 가능 (필수 시 사용) |
| 일반 컬럼 비교 | 가능 | 가능 (하지만 WHERE로 먼저 거르는 게 성능상 유리) |
| 예시 | WHERE price > 100 | HAVING SUM(price*amount) > 1000 |
HAVING도 일반 컬럼 조건을 받을 수 있지만, 그 경우 WHERE가 훨씬 빠르다. HAVING은 집계 결과 전체에 적용되지만 WHERE는 집계 전에 불필요한 행을 미리 제거한다.
3 WHERE + GROUP BY + HAVING 조합
WHERE로 먼저 행을 줄이고, GROUP BY로 그룹화하고, HAVING으로 그룹 결과를 필터링한다. 세 가지를 조합하는 것이 실무에서 가장 자주 쓰는 패턴이다.
-- ① WHERE로 먼저 모니터 제외 -- ② GROUP BY로 회원별 그룹화 -- ③ HAVING으로 총 구매액 500 이상만 SELECT userID AS '회원', SUM(price*amount) AS '총구매액' FROM buytbl WHERE prodName != '모니터' GROUP BY userID HAVING SUM(price*amount) >= 500 ORDER BY 총구매액 DESC;
| 실행 단계 | 키워드 | 역할 |
|---|---|---|
| ① | FROM buytbl | 테이블 선택 |
| ② | WHERE prodName != '모니터' | 개별 행 필터 (집계 전) |
| ③ | GROUP BY userID | userID별 그룹화 |
| ④ | SUM(price*amount) | 그룹별 집계 |
| ⑤ | HAVING SUM() >= 500 | 그룹 결과 필터 |
| ⑥ | SELECT userID, SUM() | 출력 컬럼 선택 + AS 별칭 |
| ⑦ | ORDER BY 총구매액 DESC | 정렬 |
반응형
4 HAVING 다양한 활용
-- 구매 건수 3건 이상 회원 SELECT userID, COUNT(*) AS 구매건수 FROM buytbl GROUP BY userID HAVING COUNT(*) >= 3; -- 평균 단가 200 이상인 그룹 SELECT groupName, AVG(price) AS 평균단가 FROM buytbl GROUP BY groupName HAVING AVG(price) >= 200; -- 최고 단가가 500 이상인 그룹 SELECT groupName, MAX(price) AS 최고단가 FROM buytbl GROUP BY groupName HAVING MAX(price) >= 500; -- AS 별칭을 HAVING에서 사용 (MySQL 허용) SELECT userID, SUM(price*amount) AS 총구매액 FROM buytbl GROUP BY userID HAVING 총구매액 > 1000; -- MySQL에서는 허용 (표준 SQL과 다름)
MySQL은 HAVING 절에서 SELECT에 정의한 AS 별칭을 참조할 수 있다. 하지만 표준 SQL에서는 HAVING은 SELECT 이전에 실행되므로 별칭 참조가 불가능하다. 이식성이 중요하면 원래 식을 그대로 써야 한다.
5 HAVING + ORDER BY 조합
HAVING으로 필터링된 그룹을 정렬까지 하면 실무 보고서 형태가 완성된다. 예: "총 구매액 1000 초과 회원, 많이 산 순으로"
-- 총 구매액 1000 초과, 많이 산 순 정렬 SELECT userID AS '사용자', SUM(price*amount) AS '총구매액', COUNT(*) AS '구매건수' FROM buytbl GROUP BY userID HAVING SUM(price*amount) > 1000 ORDER BY 총구매액 DESC; -- 구매 건수 1건인 회원 (한 번만 구매한 회원 식별) SELECT userID, COUNT(*) AS 구매건수 FROM buytbl GROUP BY userID HAVING COUNT(*) = 1 ORDER BY userID; -- 특정 범위: 총구매액 500 이상 1500 미만 SELECT userID, SUM(price*amount) AS 총구매액 FROM buytbl GROUP BY userID HAVING SUM(price*amount) BETWEEN 500 AND 1499;
HAVING에도 BETWEEN, IN 등 다양한 비교 연산자를 쓸 수 있다. COUNT(*) = 1은 "딱 한 번만 구매한 회원" 같은 특정 그룹 식별에 유용하다.
6 실행 순서 정리 — SQL 처리 단계
SQL은 작성 순서대로 실행되지 않는다. 내부 처리 순서를 알면 WHERE/HAVING/SELECT의 적용 타이밍이 명확해진다.
| 처리 순서 | 키워드 | 역할 | 가능/불가 |
|---|---|---|---|
| 1 | FROM | 테이블 선택 | — |
| 2 | WHERE | 개별 행 필터 | 일반 컬럼 ✅ / 집계함수 ❌ |
| 3 | GROUP BY | 그룹 생성 | — |
| 4 | 집계함수 실행 | SUM/COUNT 계산 | — |
| 5 | HAVING | 그룹 필터 | 집계함수 ✅ / 일반 컬럼 △ |
| 6 | SELECT | 출력 컬럼 선택 | AS 별칭 정의 |
| 7 | ORDER BY | 정렬 | AS 별칭 사용 가능 |
| 8 | LIMIT | 출력 행 수 제한 | — |
MySQL에서 HAVING이 SELECT의 AS 별칭을 참조할 수 있는 것은 옵티마이저가 처리 순서를 최적화하기 때문이지, 표준 실행 순서상 올바른 것이 아니다. 다른 DBMS에서는 동작하지 않을 수 있다.
7 자주 하는 실수
| 실수 | 증상 | 해결 |
|---|---|---|
| WHERE에 SUM/COUNT 사용 | Invalid use of group function | HAVING SUM(...) > N 으로 이동 |
| HAVING만 쓰고 GROUP BY 생략 | 의미 없는 결과 or 오류 | HAVING은 항상 GROUP BY와 함께 |
| HAVING 위치 잘못 | Syntax error | GROUP BY 다음에 HAVING 위치 |
| 일반 컬럼 조건을 HAVING에 넣음 | 동작은 하지만 느림 | WHERE로 이동 — GROUP BY 전에 필터 |
| HAVING에 집계함수 없이 컬럼만 | 동작은 하지만 혼란 | 일반 컬럼 조건은 WHERE가 더 적절 |
핵심 한 줄 요약
HAVINGGROUP BY 결과에 조건 — 집계함수 필터링 전용
WHERE개별 행 필터 — GROUP BY 이전에 실행
집계함수 WHERE 불가WHERE SUM() > N → Invalid use of group function 오류
실행 순서FROM → WHERE → GROUP BY → 집계 → HAVING → SELECT → ORDER BY
HAVING COUNT(*) >= 3구매 건수 3건 이상인 그룹만 출력
HAVING AVG(price) >= 200평균 단가 200 이상인 그룹만 출력
WHERE + HAVING 조합WHERE로 먼저 필터 후 GROUP BY → HAVING — 성능 최적화
MySQL AS 별칭 참조HAVING 총구매액 > 1000 — MySQL 한정 허용 (표준 SQL 비준수)
HAVING 위치GROUP BY 바로 다음 — ORDER BY보다 앞
그룹 필터 전략먼저 WHERE로 범위 줄이기 → GROUP BY → 집계 확인 → HAVING 조건
HAVING SUM > 서브쿼리HAVING SUM() > (SELECT AVG(SUM) ...) — 평균 이상 그룹 필터
집계함수 종류HAVING에 SUM/AVG/COUNT/MAX/MIN 모두 사용 가능
HAVING의 핵심집계 후 필터 — WHERE는 행 단위, HAVING은 그룹 단위
HAVING COUNT(*) = 1딱 한 번만 구매한 회원 — 비활성 고객 분류에 활용
HAVING BETWEENHAVING SUM() BETWEEN 500 AND 1499 — 집계 범위 필터
WHERE 먼저 거르기WHERE로 불필요한 행 제거 → GROUP BY할 데이터 줄어 빠름
HAVING MAX 활용HAVING MAX(price) >= 500 — 그룹 내 최고 단가 조건 필터
HAVING 없는 GROUP BY모든 그룹 출력 — HAVING은 그룹을 추가 필터할 때만 필요
다중 집계 HAVINGHAVING SUM() > 1000 AND COUNT(*) >= 2 — AND/OR 복합 조건
이 파일의 핵심 쿼리GROUP BY userID HAVING SUM(price*amount) > 1000 — 우수 고객 분류
ORDER BY와 조합HAVING 필터 후 ORDER BY로 정렬 — 실무 보고서 완성형
WHERE vs HAVING 한 줄WHERE=집계 전 행 필터, HAVING=집계 후 그룹 필터
실행 순서FROM→WHERE→GROUP BY→집계→HAVING→SELECT→ORDER BY→LIMIT
집계함수 WHERE 오류 메시지Error Code 1111: Invalid use of group function
HAVING IN 사용HAVING userID IN ('AAA','BBB') — 특정 그룹만 (단, WHERE가 적절)
집계 결과 정렬ORDER BY SUM(price*amount) DESC — 집계함수 직접 사용
HAVING NULL 체크HAVING MIN(price) IS NOT NULL — 집계가 NULL인 그룹 제외
GROUP BY 없이 HAVING전체 테이블을 하나의 그룹으로 보고 HAVING 적용 — 비표준 사용
서브쿼리와 HAVINGHAVING SUM() > (SELECT AVG(...) FROM tbl) — 동적 기준값 비교
MySQL HAVING 허용 범위집계함수 + 일반 컬럼 + SELECT AS 별칭 — MySQL 기준
표준 SQL HAVING집계함수와 GROUP BY 컬럼만 허용 — Oracle/PostgreSQL/MSSQL
HAVING 생략 가능GROUP BY 후 모든 그룹을 출력할 때는 HAVING 없어도 됨
성능 팁인덱스가 있는 컬럼은 WHERE → GROUP BY → HAVING 순서가 최적
HAVING = 그룹의 WHEREWHERE가 행을 선택하듯, HAVING은 그룹을 선택하는 역할
그룹 수 확인SELECT COUNT(DISTINCT groupName) — HAVING 적용 전 그룹 수 파악
HAVING이 없는 경우GROUP BY 결과 전체 출력 = HAVING 조건이 항상 TRUE인 것과 동일
WHERE + GROUP BY + HAVING 전형3단 필터: 행 → 그룹 → 그룹 조건 순서로 단계별 좁힘
집계 전 HAVING 불가HAVING은 집계 후 — 집계 전 조건은 반드시 WHERE로 걸어야 함
HAVING OR 조건HAVING SUM()>1000 OR COUNT(*)>=5 — 두 조건 중 하나 만족하는 그룹
NULL 그룹GROUP BY 컬럼에 NULL이 있으면 NULL도 하나의 그룹으로 처리
HAVING vs LIMITHAVING=집계 조건 필터, LIMIT=출력 행 수 제한 — 역할 완전히 다름
HAVING 핵심 규칙집계함수를 조건에 쓰면 → WHERE 아니라 HAVING
buytbl 실습 쿼리GROUP BY userID HAVING SUM(price*amount) > 1000 ORDER BY 총구매액 DESC
WHERE prodName != X특정 상품 제외 후 그룹 집계 — WHERE로 먼저 필터
HAVING 정리GROUP BY 결과를 한번 더 걸러내는 두 번째 필터 관문
HAVING AVG vs WHERE AVGWHERE AVG 오류, HAVING AVG 정상 — 평균 조건은 반드시 HAVING
실무 예월별 매출 합계 1000만 이상인 월만 → GROUP BY 월 HAVING SUM() >= 10000000
HAVING 없어도 되는 경우모든 그룹이 필요하면 HAVING 생략 — GROUP BY만으로 충분
HAVING의 실무 목적우수 고객·저조 상품·빈번 오류 그룹 등 집계 기준 선별
MySQL에서만 되는 것HAVING 별칭 참조, GROUP BY 순서 자유로움 — 다른 DBMS 주의
HAVING COUNT DISTINCTHAVING COUNT(DISTINCT prodName) > 2 — 구매 품목 종류 2개 초과
Tags
#MySQL #HAVING #GROUP BY #WHERE #집계함수 #SUM #조건필터 #SQL #데이터베이스 #웹개발 #티스토리
▼ 티스토리 태그 입력란 복사용
MySQL, HAVING, GROUP BY, WHERE, 집계함수, SUM, 조건필터, SQL, 데이터베이스, 웹개발, 티스토리
반응형
'php' 카테고리의 다른 글
| MySQL ON DUPLICATE KEY UPDATE — UPSERT 완전 정리 (0) | 2026.07.14 |
|---|---|
| MySQL WITH ROLLUP — 소계·합계 자동 계산 완전 정리 (0) | 2026.07.14 |
| MySQL MIN · MAX — 최솟값·최댓값 완전 정리 (0) | 2026.07.14 |
| MySQL 테이블 복사 · CREATE TABLE SELECT 완전 정리 (0) | 2026.07.14 |
| MySQL 서브쿼리 · ANY · ALL · IN 완전 정리 (0) | 2026.07.14 |
왕진 블로그

댓글