집계와 GROUP BY
COUNT · SUM · AVG 로 셈하고 무엇을 기준으로 묶을지 정합니다. 정수 평균과 LEFT JOIN 뒤의 COUNT 함정도 봅니다.
여러 행을 하나로 줄입니다
지금까지는 행을 하나씩 냈습니다. 집계 함수는 여러 행을 탐색해 값 하나로 줄입니다. 몇 개인지, 모두 합하면 얼마인지, 평균이 얼마인지 같은 것입니다.
| 함수 | 하는 일 |
|---|---|
| COUNT | 개수를 셉니다 |
| SUM | 값을 모두 합합니다 |
| AVG | 평균을 냅니다 |
| MIN · MAX | 가장 작은 값 · 가장 큰 값 |
SELECT COUNT(*) AS 글수, SUM(hit_count) AS 조회수합, AVG(hit_count) AS 평균, MIN(hit_count) AS 최소, MAX(hit_count) AS 최대 FROM Board.POSTS;
| 글수 | 조회수합 | 평균 | 최소 | 최대 |
|---|---|---|---|---|
| 507 | 98521 | 194 | 0 | 498 |
507행이 한 행으로 줄었습니다. 집계 함수를 적으면
GROUP BY 가 없어도 테이블 전체가 한 덩어리가 됩니다.
정수의 평균은 정수로 나옵니다
위 결과에서 평균이 194 였습니다. 98521 ÷ 507 은 194.32… 인데 소수점 아래가 없습니다.
SELECT AVG(hit_count) AS 정수평균, AVG(CAST(hit_count AS FLOAT)) AS 실수평균 FROM Board.POSTS;
| 정수평균 | 실수평균 |
|---|---|
| 194 | 194.32149901380672 |
AVG 는 넣은 값의 형식으로 결과를 냅니다.
int 를 넣으면 int 가 나오면서
소수점 아래가 버려집니다. 오류가 나지 않으므로 알아채기 어렵습니다.
비율이나 평점처럼 소수가 뜻을 갖는 자리에서는 형 변환을 하십시오.
돈이라면 DECIMAL 로 바꿉니다(1.4) —
FLOAT 은 오차가 있습니다.
묶어서 세기 — GROUP BY
GROUP BY 에 적은 열의 값이 같은 행끼리 묶고,
묶음마다 집계합니다.
SELECT board_num, COUNT(*) AS 글수, AVG(hit_count) AS 평균조회 FROM Board.POSTS GROUP BY board_num ORDER BY board_num;
| board_num | 글수 | 평균조회 |
|---|---|---|
| 1 | 35 | 260 |
| 2 | 315 | 188 |
| 3 | 157 | 190 |
번호 대신 이름을 내려면 조인합니다(2.1).
SELECT B.name AS 게시판, COUNT(*) AS 글수 FROM Board.POSTS AS P INNER JOIN Board.BOARDS AS B ON B.num = P.board_num GROUP BY B.name ORDER BY 글수 DESC;
| 게시판 | 글수 |
|---|---|
| 자유게시판 | 315 |
| 질문답변 | 157 |
| 공지사항 | 35 |
묶지 않은 열은 낼 수 없습니다
묶음 하나에 여러 행이 들어 있으므로, 그 묶음의 제목이 무엇인지는
답이 없습니다. 그런 열을 SELECT 에 적으면 오류입니다.
SELECT board_num, title, COUNT(*) AS c FROM Board.POSTS GROUP BY board_num;
SELECT 에 적을 수 있는 것은 둘뿐입니다.
GROUP BY에 적은 열- 집계 함수로 감싼 것 —
MAX(title)처럼
제목까지 보고 싶다면 묶는 기준을 바꾸거나(GROUP BY board_num, title),
묶지 않고 순위를 매기는 방법을 사용해야 합니다. 윈도 함수(2.7)가
그 일을 합니다.
이 오류는 문장을 해석하는 단계에서 나므로 TRY…CATCH 로
잡히지 않습니다. 4.5 에서 다시 다룹니다.
COUNT 는 세 가지입니다
SELECT COUNT(*) AS 전체행, COUNT(email) AS 값있는행, COUNT(DISTINCT user_id) AS 서로다른아이디 FROM Member.USERS;
| 전체행 | 값있는행 | 서로다른아이디 |
|---|---|---|
| 20 | 16 | 20 |
| 적는 법 | 세는 것 |
|---|---|
| COUNT(*) | 행을 셉니다. NULL 과 무관합니다 |
| COUNT(열) | 그 열에 값이 있는 행만 셉니다 |
| COUNT(DISTINCT 열) | 서로 다른 값이 몇 가지인지 셉니다 |
SUM · AVG · MIN ·
MAX 도 NULL 을 빼고 셈합니다.
평균에서 특히 중요합니다. 값이 없는 행을 0 으로 계산하려면
AVG(COALESCE(열, 0)) 으로 채워 넣어야 합니다.
LEFT JOIN 뒤의 COUNT(*) 는 1 을 셉니다
이 단원에서 가장 자주 어긋나는 자리입니다. 글마다 댓글이 몇 개인지
세려고 LEFT JOIN 하고 COUNT(*)
를 적으면, 댓글이 하나도 없는 글이 1 로 나옵니다.
SELECT TOP 5 P.num, P.title, COUNT(*) AS count_star, COUNT(C.num) AS count_col FROM Board.POSTS AS P LEFT JOIN Board.COMMENTS AS C ON C.post_num = P.num GROUP BY P.num, P.title ORDER BY P.num;
| num | title | count_star | count_col |
|---|---|---|---|
| 1 | 조인 질문드립니다 | 1 | 1 |
| 2 | 트랜잭션 질문드립니다 | 2 | 2 |
| 3 | 페이징 질문드립니다 | 3 | 3 |
| 4 | 백업 질문드립니다 | 4 | 4 |
| 5 | 실행 계획 질문드립니다 | 1 | 0 |
다섯째 글을 보십시오. 댓글이 없는데 COUNT(*) 가
1 입니다.
LEFT JOIN 이 짝을 못 찾으면 그 자리를
NULL 로 채운 행 하나를 만듭니다(2.1).
COUNT(*) 는 행을 세므로 그 행도 1 로 셉니다.
COUNT(C.num) 은 값이 있는 것만 세므로 0 이 나옵니다.
LEFT JOIN 뒤에 세는 것은 언제나
COUNT(오른쪽 테이블의 열) 입니다. 댓글 수가 모두
1 씩 부풀어 있는 목록 화면은 대개 이 실수입니다.
여러 열로 묶기
쉼표로 이으면 그 조합마다 묶습니다.
SELECT board_num, depth, COUNT(*) AS 건수 FROM Board.POSTS GROUP BY board_num, depth ORDER BY board_num, depth;
| board_num | depth | 건수 |
|---|---|---|
| 1 | 0 | 35 |
| 2 | 0 | 210 |
| 2 | 1 | 70 |
| 2 | 2 | 35 |
| 3 | 0 | 105 |
| 3 | 1 | 35 |
| 3 | 2 | 17 |
공지사항(1번)에는 depth 가 0 인 줄만 있습니다. 답글을 받지 않는 게시판이라 답글이 아예 없기 때문입니다(1.2).
없는 조합은 줄이 생기지 않습니다. 통계표에 빈칸 없이 0 을 채워야 한다면 날짜나 분류 목록을 따로 만들어 이어야 합니다(6.8).
직접 해보기
회원마다 글을 몇 개 작성했는지 닉네임과 함께 보고, 많이 작성한 사람부터 다섯 명만 가져오세요.
자유게시판 글마다 댓글이 몇 개인지 세되, 댓글이 없는 글도 0 으로 나오게 해보세요.
- 집계 함수는 여러 행을 값 하나로 줄입니다.
GROUP BY가 없으면 테이블 전체가 한 덩어리입니다. - 정수 열의 평균은 정수로 나옵니다. 소수가 필요하면 형을 바꾸십시오.
SELECT에는 묶은 열이나 집계 함수만 적을 수 있습니다(오류 8120).COUNT(*)는 행을,COUNT(열)은 값이 있는 행을,COUNT(DISTINCT 열)은 값의 가짓수를 셉니다.LEFT JOIN뒤에는COUNT(오른쪽 열)입니다.COUNT(*)는 짝이 없는 행도 1 로 셉니다.- 없는 조합은 줄 자체가 생기지 않습니다.