MSSQL LAB
MSSQL 2.3 · 2부. 조회 심화

HAVING 과 집계 조건

WHERE 는 행을, HAVING 은 묶음을 거릅니다. 절이 실행되는 차례를 알면 별칭을 어디서 사용할 수 있는지도 함께 풀립니다.

예상 학습 시간 18분 난이도 초급
개념 설명

묶은 뒤에 거릅니다

WHERE 는 행을 거릅니다(1.6). 그런데 2.2 처럼 묶고 나면 묶음을 조건으로 거르고 싶을 때가 있습니다. "글을 40개 이상 작성한 사람" 같은 것입니다. 그 자리가 HAVING 입니다.

SQL
SELECT U.nickname, COUNT(*) AS 글수
FROM Board.POSTS AS P
    INNER JOIN Member.USERS AS U ON U.num = P.user_num
GROUP BY U.nickname
HAVING COUNT(*) >= 40
ORDER BY 글수 DESC;
결과
nickname글수
한소망44
홍길동43
(2개 행이 영향을 받음)

20명 가운데 둘만 남았습니다. 행이 아니라 묶음이 걸러진 것입니다.

개념 설명

절이 실행되는 차례

적는 차례와 실행되는 차례가 다릅니다. 이 차례 하나를 알면 이 단원의 나머지가 모두 설명됩니다.

-- 적는 차례
SELECT … FROM … JOIN … WHERE … GROUP BY … HAVING … ORDER BY …

-- 실행되는 차례
FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
차례절그 시점에 있는 것
1FROM · JOIN테이블을 이어 행을 만듭니다
2WHERE행이 있습니다. 묶음은 아직 없습니다
3GROUP BY여기서 묶음이 생깁니다
4HAVING묶음이 있습니다. 집계값을 알 수 있습니다
5SELECT여기서 별칭이 붙습니다
6ORDER BY별칭이 이미 있으므로 사용할 수 있습니다
상세 사용법

WHERE 에는 집계 함수를 사용할 수 없습니다

WHERE 가 실행될 때는 아직 묶음이 없습니다. "이 사람의 글이 몇 개인지" 를 알 수 없으므로 조건으로 삼을 수도 없습니다.

SQL
SELECT U.nickname, COUNT(*) AS 글수
FROM Board.POSTS AS P
    INNER JOIN Member.USERS AS U ON U.num = P.user_num
WHERE COUNT(*) >= 40
GROUP BY U.nickname;
오류
메시지 147 An aggregate may not appear in the WHERE clause unless it is in a subquery contained in a HAVING clause or a select list, and the column being aggregated is an outer reference.

HAVING 에는 별칭을 사용할 수 없습니다

HAVING 은 SELECT 보다 먼저 실행됩니다. 별칭은 SELECT 에서 붙으므로 그 시점에는 아직 없습니다.

SQL
SELECT U.nickname, COUNT(*) AS 글수
FROM Board.POSTS AS P
    INNER JOIN Member.USERS AS U ON U.num = P.user_num
GROUP BY U.nickname
HAVING 글수 >= 40;
오류
메시지 207 Invalid column name '글수'.

집계식을 그대로 다시 적어야 합니다 — HAVING COUNT(*) >= 40 입니다. 길어져도 어쩔 수 없습니다.

ORDER BY 에서는 됩니다. SELECT 가 이미 끝나 별칭이 붙어 있기 때문입니다. 1.7 에서 "별칭을 사용할 수 있는 절은 ORDER BY 뿐" 이라고 한 까닭이 이것입니다.

두 오류 모두 문장을 해석하는 단계에서 나므로 TRY…CATCH 로 잡히지 않습니다(2.2 · 4.5).

상세 사용법

둘을 함께 사용하기

WHERE 로 어떤 행을 셈에 넣을지 정하고, HAVING 으로 어떤 묶음을 남길지 정합니다.

SQL
SELECT U.nickname, COUNT(*) AS 자유게시판글수
FROM Board.POSTS AS P
    INNER JOIN Member.USERS AS U ON U.num = P.user_num
WHERE P.board_num = 2
GROUP BY U.nickname
HAVING COUNT(*) >= 20
ORDER BY 자유게시판글수 DESC, U.nickname;
결과
nickname자유게시판글수
강바다24
박민수24
정하늘24
문시우23
백나윤23
서예린23
이영희23
신우진22
(8개 행이 영향을 받음)

자유게시판 글만 세었고, 그중 20개 이상인 사람만 남았습니다. 공지사항과 질문답변에 작성한 글은 세지 않았습니다 — WHERE 가 묶기 전에 걸러 냈기 때문입니다.

상세 사용법

어디에 두느냐로 질문이 달라집니다

같은 숫자를 조건에 넣어도 WHERE 에 두느냐 HAVING 에 두느냐에 따라 묻는 것이 완전히 달라집니다.

SQL
-- 조회수 400 이상인 "글"이 4개 이상인 사람
SELECT U.nickname, COUNT(*) AS 인기글수
FROM Board.POSTS AS P
    INNER JOIN Member.USERS AS U ON U.num = P.user_num
WHERE P.hit_count >= 400
GROUP BY U.nickname
HAVING COUNT(*) >= 4
ORDER BY U.nickname;
결과
nickname인기글수
김철수4
박민수4
이영희4
임도윤4
정하늘4
최지우4
한소망4
홍길동4
(8개 행이 영향을 받음)
SQL
-- "작성한 글 전체의 평균" 조회수가 220 이상인 사람
SELECT U.nickname, COUNT(*) AS 글수, AVG(P.hit_count) AS 평균조회
FROM Board.POSTS AS P
    INNER JOIN Member.USERS AS U ON U.num = P.user_num
GROUP BY U.nickname
HAVING AVG(P.hit_count) >= 220
ORDER BY 평균조회 DESC;
결과
nickname글수평균조회
이영희23229
오준혁22227
임도윤24221
(3개 행이 영향을 받음)

앞은 잘 된 글이 몇 개인지를 묻고, 뒤는 전반적으로 잘 작성하는지를 묻습니다. 뒤엣것은 WHERE 로는 적을 수 없습니다. 평균은 묶은 뒤에야 알 수 있기 때문입니다.

먼저 걸러 낼 수 있는 조건은 WHERE 에 두십시오. 묶기 전에 행이 줄어들면 그만큼 일이 적어집니다. 집계값을 봐야만 판단할 수 있는 조건만 HAVING 에 남깁니다.

실습 문제

직접 해보기

1. 댓글이 많이 달린 글 난이도 하

댓글이 4개 이상 달린 글을 번호·제목·댓글 수와 함께 다섯 건만 가져오세요.

SELECT TOP 5 P.num, P.title, COUNT(C.num) AS 댓글수 FROM Board.POSTS AS P LEFT JOIN Board.COMMENTS AS C ON C.post_num = P.num GROUP BY P.num, P.title HAVING COUNT(C.num) >= 4 ORDER BY P.num; -- HAVING 에도 COUNT(C.num) 을 그대로 다시 적습니다. -- 별칭 "댓글수" 는 여기서 아직 없습니다.
2. 두 질문을 갈라 적기 난이도 중

다음 두 질문을 각각 문장으로 적어 보세요. 조건을 어디에 두어야 하는지가 다릅니다.

  • 답글(depth 가 0 이 아닌 글)을 10개 이상 작성한 사람
  • 글을 20개 이상 작성한 사람 가운데 최고 조회수가 490 이상인 사람
-- 1) 셈에 넣을 행을 먼저 고릅니다 → WHERE SELECT U.nickname, COUNT(*) AS 답글수 FROM Board.POSTS AS P INNER JOIN Member.USERS AS U ON U.num = P.user_num WHERE P.depth > 0 GROUP BY U.nickname HAVING COUNT(*) >= 10 ORDER BY 답글수 DESC; -- 2) 둘 다 묶은 뒤에야 알 수 있습니다 → HAVING 에 나란히 SELECT U.nickname, COUNT(*) AS 글수, MAX(P.hit_count) AS 최고조회 FROM Board.POSTS AS P INNER JOIN Member.USERS AS U ON U.num = P.user_num GROUP BY U.nickname HAVING COUNT(*) >= 20 AND MAX(P.hit_count) >= 490 ORDER BY 최고조회 DESC;
요약
  • WHERE 는 행을, HAVING 은 묶음을 거릅니다.
  • 실행 차례는 FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY 입니다.
  • WHERE 에 집계 함수를 사용할 수 없습니다. 그때는 아직 묶음이 없습니다(오류 147).
  • HAVING 에 별칭을 사용할 수 없습니다. 별칭은 SELECT 에서 붙습니다(오류 207).
  • ORDER BY 에서만 별칭이 됩니다. 가장 나중에 실행되기 때문입니다.
  • 먼저 걸러 낼 수 있는 조건은 WHERE 에 두십시오. 묶을 행이 줄어듭니다.