HAVING과 실행 순서
WHERE와 HAVING의 평가 단계를 분리해 조건에 맞는 회원 그룹만 정확히 선택합니다.
‘7월의 게시글만 모아 회원별 합계를 만들고, 총 조회수 120회 이상인 회원만 보여 달라’는 질문에는 두 종류의 필터가 있습니다.
게시 상태·날짜는 원본 게시글 행에, 조회수 120회는 그룹 합계에 적용됩니다.
둘을 같은 WHERE에 넣으면 집계 함수를 사용할 수 없다는 오류가 납니다.
FROM→WHERE→GROUP BY→HAVING→SELECT→ORDER BY→LIMIT의 논리적 순서를 회원 게시판 통계 한 문장에 적용합니다.
실제 물리 실행은 옵티마이저가 바꿀 수 있지만 결과 의미를 읽는 순서는 이 모델로 설명합니다.
HAVING 예제 데이터
원본·필터·그룹·결과 건수를 12 → 8 → 4 → 2로 고정하는 전용 fixture를 사용합니다.
DROP TABLE IF EXISTS ch4_having_post_demo;
CREATE TABLE ch4_having_post_demo (
id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
author_id BIGINT UNSIGNED NOT NULL,
status VARCHAR(16) NOT NULL,
view_count BIGINT UNSIGNED NOT NULL,
created_at DATETIME(6) NOT NULL
) ENGINE = InnoDB;
INSERT INTO ch4_having_post_demo
(id, author_id, status, view_count, created_at)
VALUES
(1, 1, 'PUBLISHED', 50, '2026-07-02 09:00:00'),
(2, 1, 'PUBLISHED', 55, '2026-07-10 09:00:00'),
(3, 1, 'PUBLISHED', 60, '2026-07-20 09:00:00'),
(4, 2, 'PUBLISHED', 45, '2026-07-03 09:00:00'),
(5, 2, 'PUBLISHED', 55, '2026-07-11 09:00:00'),
(6, 3, 'PUBLISHED', 50, '2026-07-12 09:00:00'),
(7, 4, 'PUBLISHED', 60, '2026-07-13 09:00:00'),
(8, 4, 'PUBLISHED', 70, '2026-07-14 09:00:00'),
(9, 1, 'DRAFT', 100, '2026-07-15 09:00:00'),
(10, 2, 'PUBLISHED', 100, '2026-06-30 09:00:00'),
(11, 3, 'PUBLISHED', 100, '2026-08-01 09:00:00'),
(12, 4, 'HIDDEN', 100, '2026-07-16 09:00:00');WHERE의 집계식 오류
총 조회수가 120회 이상인 회원을 찾으려고 WHERE에 SUM을 넣습니다.
WHERE는 아직 개별 행을 평가하는 단계라 회원 그룹과 합계가 존재하지 않습니다.
SELECT author_id, SUM(view_count) AS total_views
FROM ch4_having_post_demo
WHERE status = 'PUBLISHED'
AND SUM(view_count) >= 120
GROUP BY author_id;MySQL은 그룹이 만들어지기 전 집계 함수를 사용할 수 없다고 거절합니다.
오류 실행 결과ERROR 1111 (HY000): Invalid use of group functionWHERE는 원본 행의 상태와 날짜를 줄이고, GROUP BY가 남은 행을 회원별로 묶은 뒤 SUM이 계산됩니다.
합계 조건은 그 뒤 HAVING에서 평가해야 합니다.
오류를 피하려고 모든 조건을 HAVING으로 옮기면 결과는 맞을 수 있어도 불필요한 행까지 그룹화하고 조건의 책임이 흐려집니다.
SQL의 논리적 실행 순서
논리적 처리에서 FROM이 입력 집합을 만들고 WHERE가 개별 행을 거릅니다.
GROUP BY는 그룹을 만들고 집계가 그룹 값을 계산합니다.
HAVING은 그룹 결과를 거릅니다.
SELECT는 결과 표현과 별칭을 만들고 ORDER BY가 순서를 정한 뒤 LIMIT이 범위를 자릅니다.
이 순서를 알면 별칭을 WHERE에서 못 쓰는 이유와 집계가 HAVING에 있어야 하는 이유를 하나의 모델로 이해할 수 있습니다.
WHERE, GROUP BY, HAVING, ORDER BY는 표준 SQL입니다.
MySQL은 HAVING과 ORDER BY에서 SELECT 별칭 사용을 허용하지만 제품별 차이가 있을 수 있습니다.
이 교재는 읽기 쉬운 별칭을 사용하되 WHERE에는 원본 컬럼 조건을 둡니다.
실제 옵티마이저가 조건식을 안전하게 앞당길 수 있어도 SQL 의미는 논리적 순서로 설명합니다.
WHERE와 HAVING의 분리
7월에 게시된 글이라는 모집단은 WHERE, 회원별 조회수 120회라는 그룹 기준은 HAVING에 둡니다.
정렬은 합계와 회원 ID로 완전하게 만듭니다.
SELECT
author_id,
COUNT(*) AS published_posts,
SUM(view_count) AS total_views
FROM ch4_having_post_demo
WHERE status = 'PUBLISHED'
AND created_at >= '2026-07-01 00:00:00'
AND created_at < '2026-08-01 00:00:00'
GROUP BY author_id
HAVING SUM(view_count) >= 120
ORDER BY total_views DESC, author_id;7월에 게시된 행만 그룹 입력이 되고, 합계가 기준을 넘은 회원만 결과에 남습니다.
개선 실행 결과author_id | published_posts | total_views
1 | 3 | 165
4 | 2 | 130HAVING에 SUM(...)을 반복하면 표준 의미가 명확하고, MySQL에서는 HAVING total_views >= 120도 가능합니다.
팀의 이식성과 가독성 기준을 하나로 정합니다.
그룹 기준이 사용자 입력이라면 음수나 지나치게 큰 값의 유효성도 애플리케이션 경계에서 검사합니다.
단계별 행 수 관찰
최종 두 행만 보면 어느 단계에서 데이터가 줄었는지 알기 어렵습니다.
원본, WHERE 통과 행, 그룹 수, HAVING 통과 그룹 수를 별도 진단 쿼리로 기록합니다.
WITH filtered AS (
SELECT author_id, view_count
FROM ch4_having_post_demo
WHERE status = 'PUBLISHED'
AND created_at >= '2026-07-01'
AND created_at < '2026-08-01'
), grouped AS (
SELECT author_id, SUM(view_count) AS total_views
FROM filtered
GROUP BY author_id
)
SELECT
(SELECT COUNT(*) FROM ch4_having_post_demo) AS source_rows,
(SELECT COUNT(*) FROM filtered) AS filtered_rows,
(SELECT COUNT(*) FROM grouped) AS group_rows,
(SELECT COUNT(*) FROM grouped WHERE total_views >= 120) AS result_rows;CTE는 6장에서 정식으로 배우지만 여기서는 처리 단계 관찰 도구로 사용합니다.
예를 들어 12 → 8 → 4 → 2가 나오면 각 조건의 선택 범위를 설명할 수 있습니다.
HAVING은 GROUP BY가 없을 때 전체 입력을 하나의 그룹처럼 다루는 쿼리에서도 쓰일 수 있지만, 일반 필터를 HAVING에 몰아넣는 습관은 피합니다.
WHERE로 안전하게 앞당길 수 있는 조건은 원본 행을 일찍 줄이고 의도를 명확히 합니다.
반면 집계값이나 그룹별 비율처럼 그룹이 있어야 정의되는 조건은 HAVING에 남습니다.
실행 순서를 종이에 적은 뒤 각 절에서 사용할 수 있는 이름을 표시해 보세요.
WHERE에서는 원본 컬럼, HAVING에서는 그룹 키와 집계, ORDER BY에서는 결과 별칭을 사용할 수 있습니다.
날짜·상태 조건을 WHERE와 HAVING에 각각 두고 결과와 실행 계획을 비교하면 의미와 최적화의 차이를 볼 수 있습니다.
HAVING 경계값 바로 아래·같음·위 그룹을 만들어 >=와 >의 차이도 검증합니다.
그룹이 하나도 없는 경우 SUM이 NULL이 되는 상황과 COALESCE 필요 여부도 확인합니다.
통계 API에서 조건 순서는 권한에도 영향을 줍니다.
소유자나 조직 범위는 반드시 원본 행 단계에서 적용되어 다른 사용자의 행이 그룹에 섞이지 않게 합니다.
결과만 마스킹해도 집계값을 통해 다른 집단 규모가 추론될 수 있습니다.
대시보드 필터의 시간대를 월 경계에 적용할 때 서버 날짜 문자열을 그대로 사용하지 말고 기준 시각으로 변환합니다.
논리적 실행 순서는 정확성과 정보 노출 경계를 함께 검토하는 도구입니다.
GROUP BY 결과의 ORDER BY가 합계만 사용하면 동률 순서가 불완전하므로 그룹 키를 추가합니다.
LIMIT를 붙여 상위 N을 만들 때도 동률을 모두 포함할지 정확히 N행만 줄지 제품 규칙을 정해야 합니다.
비율 조건은 분모가 0인 그룹을 어떻게 처리할지 정의하고 NULLIF를 사용합니다.
HAVING에서 반올림된 표시 별칭을 비교하면 원본 정밀도 기준과 경계가 달라질 수 있으므로 필터에 사용할 값과 표시값을 구분합니다.
논리적 실행 순서는 복잡한 SQL을 디버깅할 공통 언어입니다.
결과가 많으면 WHERE 모집단, 그룹 수, HAVING 경계를 차례로 점검하고, 결과가 비면 같은 순서로 어느 단계에서 0이 되었는지 찾습니다.
이 모델을 실제 물리 실행 순서와 혼동하지는 않습니다.
MySQL 옵티마이저는 안전한 변환과 인덱스를 이용해 다른 경로를 선택할 수 있지만 최종 의미는 논리 순서를 만족해야 합니다.
이후 EXPLAIN ANALYZE를 읽을 때도 ‘의미 단계’와 ‘물리 연산’을 나란히 비교하면 최적화 때문에 SQL 결과가 바뀐다고 오해하지 않습니다.
각 단계의 기대 건수를 실행 기록에 남깁니다.
조건식의 절 선택
| 판단 축 | 확인할 질문 |
|---|---|
| 행 조건 | 집계 전 개별 게시글만 보고 판단할 수 있는가? |
| 그룹 조건 | SUM·건수·AVG가 있어야 판단할 수 있는가? |
| 경계 | 필터가 포함·제외하는 정확한 모집단을 설명할 수 있는가? |
| 이름 | 별칭 사용 범위가 DBMS 지원과 팀 기준에 맞는가? |
| 순서 | 동률 해소와 LIMIT가 마지막에 적용되는가? |
조건을 작성하기 전에 자연어 문장에서 ‘게시글 중’과 ‘회원별 합계 중’을 표시합니다.
전자는 WHERE, 후자는 HAVING으로 옮기면 긴 집계 쿼리의 구조가 흔들리지 않습니다.
연습 문제
7월에 작성된 게시글 중 조회수 20회 이상인 행만 대상으로 회원별 총 조회수와 게시글 수를 구하고, 게시글이 2개 이상이면서 평균 조회수 45회 이상인 회원만 보여 주세요.
평균 조회수가 높은 순, 회원 ID 순으로 정렬하세요.
해설과 예시 답안
날짜와 개별 게시글 조회수는 WHERE, 게시글 수와 평균 조회수는 HAVING입니다.
두 그룹 조건을 AND로 연결합니다.
SELECT
author_id,
COUNT(*) AS post_count,
SUM(view_count) AS total_views,
ROUND(AVG(view_count), 1) AS average_views
FROM ch4_having_post_demo
WHERE created_at >= '2026-07-01'
AND created_at < '2026-08-01'
AND view_count >= 20
GROUP BY author_id
HAVING COUNT(*) >= 2
AND AVG(view_count) >= 45
ORDER BY average_views DESC, author_id;조회수 20회 미만 행이 그룹 입력에서 빠지는지, 게시글 1개인 고평균 그룹이 HAVING에서 빠지는지 각각 확인합니다.
핵심 정리
- WHERE는 그룹 전 원본 행을, HAVING은 계산된 그룹을 필터링합니다.
- 논리적 순서는 별칭과 집계 함수가 어느 절에서 보이는지 설명합니다.
- 조건을 가능한 가장 이른 의미 단계에 두되 업무 뜻을 바꾸지 않습니다.
- 단계별 행·그룹 수를 기록해 최종 통계의 근거를 검증합니다.
다음 장에서는 분리된 테이블의 행을 키로 연결해 하나의 질문에 답합니다.