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명 가운데 둘만 남았습니다. 행이 아니라 묶음이 걸러진 것입니다.

개념 설명

절이 실행되는 차례

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

-- 적는 차례
SELECTFROMJOINWHEREGROUP BYHAVINGORDER BY-- 실행되는 차례
FROMJOINWHEREGROUP BYHAVINGSELECTORDER 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 에는 별칭을 사용할 수 없습니다

HAVINGSELECT 보다 먼저 실행됩니다. 별칭은 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 두십시오. 묶을 행이 줄어듭니다.