Fullstack 52

heo4·2026년 6월 29일

Fullstack

목록 보기
7/70

풀스택

집계함수와 GROUP BY / HAVING

1. 집계함수란?

집계함수는 여러 행의 값을 하나로 합쳐서 계산하는 함수로, “전체 게시글 수”, “평균 조회수”처럼 통계성 정보를 뽑을 때 사용합니다.

함수의미
COUNT(컬럼 또는 *)행의 개수
SUM(컬럼)합계
AVG(컬럼)평균
MAX(컬럼)최댓값
MIN(컬럼)최솟값

1-1. COUNT

-- 전체 게시글 수
SELECT COUNT(*) FROM board;

-- content가 NULL이 아닌 게시글 수 (컬럼명을 넣으면 NULL은 제외하고 셈)
SELECT COUNT(content) FROM board;

1-2. SUM, AVG

-- 전체 게시글의 조회수 합계
SELECT SUM(view_count) FROM board;

-- 전체 게시글의 평균 조회수
SELECT AVG(view_count) FROM board;

1-3. MAX, MIN

-- 조회수가 가장 높은 값
SELECT MAX(view_count) FROM board;

-- 조회수가 가장 낮은 값
SELECT MIN(view_count) FROM board;

1-4. 여러 집계함수 동시에 사용

select 
	count(*) as 전체게시글수,
	max(view_count) as 조회수최댓값, 
	min(view_count) as 조회수최솟값, 
	avg(view_count) as 조회수평균, 
	sum(view_count) as 조회수총합
from board

AS로 결과 컬럼에 별명(alias)을 붙이면 결과를 읽기 쉬워집니다.

2. GROUP BY — 그룹별로 묶기

집계함수를 전체 테이블이 아니라 그룹별로 적용하고 싶을 때 사용합니다.

-- 카테고리별 게시글 수
SELECT category_id, COUNT(*) AS board_count
FROM board
GROUP BY category_id;

결과 예시:

category_idboard_count
16
23
34
47

category_id가 같은 행끼리 묶어서, 그 안에서 COUNT를 계산합니다.

-- 회원별 작성한 게시글 수
SELECT member_id, COUNT(*) AS board_count
FROM board
GROUP BY member_id;
-- 카테고리별 평균 조회수
SELECT category_id, AVG(view_count) AS avg_view
FROM board
GROUP BY category_id;

2-1. ⚠️ GROUP BY를 쓸 때 주의할 점

SELECT에 나열하는 컬럼은 GROUP BY에 포함된 컬럼 또는 집계함수로 감싼 컬럼이어야 합니다.

3. HAVING — 그룹화된 결과에 조건 걸기

WHERE는 그룹화하기 전 행 단위로 조건을 거는 것이고, HAVING은 그룹화한 이후 그룹 단위로 조건을 거는 것입니다.

-- 게시글이 5개 이상인 카테고리만 조회
SELECT category_id, COUNT(*) AS board_count
FROM board
GROUP BY category_id
HAVING COUNT(*) >= 5;

select category_id, count(*) as board_count
from board
group by category_id
having board_count >= 5
-- 평균 조회수가 0보다 큰 카테고리만 조회
SELECT category_id, AVG(view_count) AS avg_view
FROM board
GROUP BY category_id
HAVING AVG(view_count) > 0;

3-1. WHERE와 HAVING을 함께 사용

-- member_id가 1~5인 게시글만 대상으로, 카테고리별 게시글 수를 구하고,
-- 그중 게시글 수가 2개 이상인 카테고리만 조회
SELECT category_id, COUNT(*) AS board_count
FROM board
WHERE member_id BETWEEN 1 AND 5
GROUP BY category_id
HAVING COUNT(*) >= 2;

실행 순서: WHERE(행 필터링) → GROUP BY(그룹화) → HAVING(그룹 필터링) → ORDER BY(정렬)

💡 WHERE는 집계함수를 사용할 수 없습니다 (WHERE COUNT(*) >= 2는 에러). 집계 결과에 조건을 걸려면 반드시 HAVING을 사용해야 합니다.

4. 실전 활용 예시

4-1. 게시글을 가장 많이 쓴 회원 TOP 3

SELECT member_id, COUNT(*) AS board_count
FROM board
GROUP BY member_id
ORDER BY board_count DESC
LIMIT 3;

4-2. 댓글이 가장 많이 달린 게시글

SELECT board_id, COUNT(*) AS comment_count
FROM comments
GROUP BY board_id
ORDER BY comment_count DESC
LIMIT 1;

4-3. 각 게시글의 좋아요 수 확인

SELECT board_id, COUNT(*) AS like_count
FROM board_like
GROUP BY board_id
ORDER BY like_count DESC;

정리

  • COUNT, SUM, AVG, MAX, MIN으로 여러 행의 값을 하나로 집계할 수 있다.
  • GROUP BY는 특정 컬럼을 기준으로 데이터를 그룹화하며, SELECT에는 그룹화 기준 컬럼 또는 집계함수만 올 수 있다.
  • HAVING은 그룹화된 결과에 조건을 걸 때 사용하며, WHERE는 집계함수를 조건으로 쓸 수 없다.
  • 쿼리 실행 순서는 WHERE → GROUP BY → HAVING → ORDER BY이다.

통계성 쿼리 실습

시나리오 1. 게시판 전체 현황 한눈에 보기

관리자 화면에 흔히 나오는 “오늘의 통계” 같은 형태입니다.

SELECT
    (SELECT COUNT(*) FROM member) AS total_member,
    (SELECT COUNT(*) FROM board) AS total_board,
    (SELECT COUNT(*) FROM comment) AS total_comment,
    (SELECT COUNT(*) FROM board_like) AS total_like;

서브쿼리를 SELECT 안에 여러 개 넣으면, 서로 다른 테이블의 집계를 한 줄의 결과로 모을 수 있습니다.


시나리오 2. 카테고리별 활동 현황

-- 카테고리별 게시글 수, 평균 조회수, 최고 조회수
SELECT
    category_id,
    COUNT(*) AS board_count,
    AVG(view_count) AS avg_view,
    MAX(view_count) AS max_view
FROM board
GROUP BY category_id
ORDER BY board_count DESC;
-- 게시글이 5개 이상이면서 평균 조회수가 0보다 큰 카테고리만
SELECT
    category_id,
    COUNT(*) AS board_count,
    AVG(view_count) AS avg_view
FROM board
GROUP BY category_id
HAVING COUNT(*) >= 5 AND AVG(view_count) > 0;

시나리오 3. 회원별 활동 점수 매기기

“게시글 작성 수 + 댓글 작성 수”를 기준으로 활동량이 많은 회원을 가려내고 싶을 때, 우선 각각 따로 집계해봅니다.

-- 회원별 게시글 수
SELECT member_id, COUNT(*) AS board_count
FROM board
GROUP BY member_id;
-- 회원별 댓글 수
SELECT member_id, COUNT(*) AS comment_count
FROM comment
GROUP BY member_id;

두 결과를 합쳐서 보는 방법은 다음 단계(JOIN)에서 본격적으로 다룹니다. 지금은 각각 따로 집계하는 연습에 집중합니다.

시나리오 4. CASE WHEN으로 조건별 집계하기

게시글을 “인기글(조회수 10 이상)”과 “일반글”로 나누어 각각 몇 개인지 한 번에 보고 싶다면 CASE WHEN을 사용합니다.

SELECT
    SUM(CASE WHEN view_count >= 10 THEN 1 ELSE 0 END) AS popular_count,
    SUM(CASE WHEN view_count < 10 THEN 1 ELSE 0 END) AS normal_count
FROM board;

동작 원리: CASE WHEN은 행 하나하나를 조건에 맞게 1 또는 0으로 바꿔주고, SUM이 그 값들을 더해서 개수처럼 사용하는 방식입니다.

-- 카테고리별로 인기글/일반글 개수를 나누어 집계
SELECT
    category_id,
    SUM(CASE WHEN view_count >= 10 THEN 1 ELSE 0 END) AS popular_count,
    SUM(CASE WHEN view_count < 10 THEN 1 ELSE 0 END) AS normal_count
FROM board
GROUP BY category_id;

시나리오 5. 좋아요 랭킹 만들기

-- 좋아요를 가장 많이 받은 게시글 TOP 5 (board_id와 좋아요 수만)
SELECT board_id, COUNT(*) AS like_count
FROM board_like
GROUP BY board_id
ORDER BY like_count DESC
LIMIT 5;
-- 좋아요를 가장 많이 누른 회원(활동적인 회원) TOP 5
SELECT member_id, COUNT(*) AS like_given_count
FROM board_like
GROUP BY member_id
ORDER BY like_given_count DESC
LIMIT 5;

시나리오 6. 날짜 기준 통계 — 일별/월별 게시글 수

H2에서는 날짜 함수로 날짜 단위를 추출할 수 있습니다.

-- 날짜별 게시글 작성 수 (시간은 빼고 날짜만 추출)
SELECT CAST(created_at AS DATE) AS write_date, COUNT(*) AS board_count
FROM board
GROUP BY CAST(created_at AS DATE)
ORDER BY write_date DESC;
-- 월별 게시글 작성 수
SELECT FORMATDATETIME(created_at, 'yyyy-MM') AS write_month, COUNT(*) AS board_count
FROM board
GROUP BY FORMATDATETIME(created_at, 'yyyy-MM')
ORDER BY write_month DESC;

날짜 함수는 DBMS마다 문법이 조금씩 다릅니다 (FORMATDATETIME은 H2 문법). 추후 MySQL로 옮길 때는 DATE_FORMAT(created_at, '%Y-%m')처럼 바뀐다는 점을 알아두면 좋습니다.


정리

  • 서브쿼리를 SELECT 절에 여러 개 넣으면 서로 다른 테이블의 집계를 한 줄로 모을 수 있다
  • GROUP BY + HAVING을 조합하면 “조건을 만족하는 그룹만” 뽑아낼 수 있다
  • CASE WHEN + SUM을 조합하면 조건별로 분류한 개수를 한 번에 집계할 수 있다
  • 날짜 함수를 활용하면 일별/월별 통계도 만들 수 있으며, DBMS마다 문법이 다르다는 점을 기억해야 한다

0개의 댓글