[sql] GROUP BY, HAVING 정리

[sql] GROUP BY, HAVING 정리

·

5 min read

How to use GROUP BY clause in SQL

  • country를 기준으로 GROUP BY 한 예

GROUP BY 예제

예제 데이터

customer_idamount
1100
1200
2150
2250
2100
3300
SELECT customer_id, SUM(amount)
FROM payment
GROUP BY customer_id;

동작 과정

  1. GROUP BY customer_idcustomer_id가 같은 행들을 그룹으로 묶음

  2. SUM(amount) → 각 그룹의 amount 값을 합산

  3. customer_id별 결제 총액을 출력

SQL 실행 결과

customer_idSUM(amount)
1300
2500
3300

해석:

  • customer_id = 1인 고객은 100 + 200 = 300원을 결제

  • customer_id = 2인 고객은 150 + 250 + 100 = 500원을 결제

  • customer_id = 3인 고객은 300원을 결제


GROUP BY – 동일한 값을 가진 데이터를 그룹화

SELECT department, COUNT(*) FROM employees GROUP BY department;
  • employees 테이블에서 부서별 직원 수 조회

  • 일반적으로 집계 함수 (SUM, COUNT, AVG 등)와 함께 사용

  • GROUP BY 문을 사용하고 특정 열만 선택하는 경우, GROUP BY에 그 열을 반드시 포함시켜야 함.

      SELECT category_col FROM table GROUP BY category_col
    
    • 열에 집계 함수를 적용시킨 경우에만 예외

        SELECT category_col, AGG(data_col) FROM table GROUP BY category_col
      
  • 원한다면 GROUP BY를 하기 전에 WHERE 문을 실행할 수 있음

      SELECT category_col, AGG(data_col) FROM table WHERE category_col != 'A' GROUP BY category_col
    
    • 카테고리 A를 전부 무시하고, GROUP BY 실행
  • GROUP BY는 FROM 문 바로 뒤나, WHERE 문 바로 뒤에 와야 함

  • GROUP BY에서 순서는 크게 중요하지 않음(대부분의 경우 SELECT와 순서를 맞춤)

  • 이런 상황에 사용:

    • 총액 기준으로 가장 많은 금액을 사용한 고객을 찾을 때

        SELECT customer_id, SUM(amount) FROM payment
        GROUP BY customer_id -- 고객 ID '별(per)'
        ORDER BY SUM(amount) DESC -- 고객 ID 별 금액의 총 합을 나타내는 열
      
      • ORDER BY는 질문이 무언인가에 따라 정하면 됨
    • 마케팅 팀에서 월별 매출 추이를 분석하고 싶을 때

        SELECT DATE_FORMAT(order_date, '%Y-%m') AS month, SUM(total_price) AS monthly_sales
        FROM orders
        GROUP BY DATE_FORMAT(order_date, '%Y-%m');
        /* order_date의 연-월(%Y-%m)을 기준으로 그룹화하고, 각 월별 총 매출(SUM(total_price))을 계산 */
      
    • 거래 총액이 가장 적은 날짜를 확인하고 싶을 때

        SELECT DATE(payment_date), SUM(amount) FROM payment -- DATE는 타임 스탬프 정보 중 '날짜 부분'만 추출
        GROUP BY DATE(payment_date)
        ORDER BY SUM(amount) -- (DESC)
      

HAVING – 그룹화된 데이터에 조건 적용

SELECT company, SUM(sales)
FROM finance_table
WHERE company != 'Google' -- (2) 개별 행에서 'Google' 제외
GROUP BY company;  -- (3) 그룹화 수행 (Google은 이미 없음)
  • WHERE그룹화 전에 개별 행을 필터링

  • 즉, 'Google' 데이터를 미리 제거한 후 남은 데이터로 그룹화를 수행

  • 집계 결과(SUM)를 기준으로 필터링할 수 없음!

    • 왜냐하면 집계는 아직 수행되지 않았기 때문

      • 참고: SQL 실행 순서

        1. FROM → 테이블 선택

        2. WHERE → 개별 행 필터링

        3. GROUP BY → 그룹화 수행

        4. 집계 함수 실행 (SUM, AVG 등)

        5. HAVING → 그룹화된 데이터 필터링

        6. SELECT → 최종 결과 선택

        7. ORDER BY → 정렬 수행

    • 즉, GROUP BY를 실행하고 회사별 판매액 총계를 계산한 후에 그 결과를 추가적으로 필터링해야함 → HAVING 사용

        SELECT company, SUM(sales)
        FROM finance_table
        WHERE company != 'Google'
        GROUP BY company; -- (3) 회사별 그룹화
        HAVING SUM(sales) > 1000 -- (5) 그룹화된 결과를 필터링
      
      • SUM(sales)GROUP BY 후에 계산되므로, 집계된 결과를 기준으로 필터링하려면 HAVING을 사용해야 함
SELECT department, COUNT(*) 
FROM employees 
GROUP BY department 
HAVING COUNT(*) > 5;
  • 부서별 직원 수를 구한 후, 직원이 5명 이상인 부서만 조회

  • HAVING은 집계(aggregation)가 이미 수행된 이후에 자료를 필터링함

  • WHERE은 개별 행을 필터링, HAVING은 그룹화된 데이터 필터링

WHERE vs HAVING

WHERE와 .HAVING은 비슷해 보인다. 실제로 WHERE가 하는 역할을 HAVING이 대신할 수도 있다.

ex) 특정 회사만 필터링하고 싶다면:

SELECT company
FROM finance_table
GROUP BY company
HAVING company != 'Google';
  • 이렇게 사용할 수 있지만, WHERE로 대체할 수 있기 때문에 실용적인 의미는 적다.
    → 같은 결과를 WHERE을 사용해서 더 효율적으로 만들 수 있기 때문
SELECT company
FROM finance_table
WHERE company != 'Google'
GROUP BY company;

⚠️ 언제 HAVING을 쓰는 게 적절할까?

  • HAVING을 집계 함수 없이 사용하면, 단순한 그룹 필터링을 수행하는데, 대부분 WHERE으로 대체할 수 있음

  • 하지만 그룹화된 데이터에 대한 필터링이 필요한 경우에는 HAVING이 필수!

집계 함수가 있는 HAVING (일반적인 경우)

대부분 HAVING은 집계 함수와 함께 사용된다.

SELECT company, SUM(sales) AS total_sales
FROM finance_table
GROUP BY company
HAVING SUM(sales) > 10000;
  • 이 경우 WHERE로는 SUM(sales) > 10000을 필터링할 수 없기 때문에 HAVING을 반드시 사용해야 함

In English:

  • GROUP BY groups rows that have the same values in specified columns.

  • HAVING filters grouped data based on conditions.


📝 오늘 배운 점:

  • GROUP BY는 집계 함수와 함께 사용해야 의미가 있다.

  • HAVINGWHERE처럼 필터링하지만, 개별 행이 아니라 그룹화된 데이터에 적용된다.

  • GROUP BY에서 여러 컬럼을 지정하면 다중 레벨의 그룹화를 할 수 있다.

  • HAVING 조건은 GROUP BY 이후에 적용되므로, 필터링 기준을 정확히 이해해야 한다.

  • SQL에서 데이터를 효과적으로 분석하려면 GROUP BYHAVING을 적절히 활용하는 것이 중요하다.


연습 문제

연습 문제 1

Staff ID가 1과 2인 두 명의 직원 중 가장 많은 결제를 처리한 직원에게 보너스를 주려고 한다.

각 직원이 처리한 결제건수는 몇 건이며 누가 보너스를 받게 될까?

SELECT staff_id,COUNT(amount) FROM payment
GROUP BY staff_id

연습 문제 2

본사에서 교체 비용과 영화의 MPAA 등급(G, PG, R 등) 사이의 관계에 관한 연구를 수행하고 있다.

MPAA 등급 별 평균 replacement cost는 얼마(소수점 2자리수까지)일까?

SELECT rating,ROUND(AVG(replacement_cost),2) FROM film
GROUP BY rating

연습 문제 3

상위 5명의 고객에게 쿠폰을 증정하는 행사를 진행 중이다.

총 지출액 또는 총 사용을 기준으로 상위 고객 5명의 고객 ID는 무엇일까?

SELECT customer_id, ROUND(SUM(amount),2) FROM payment
GROUP BY customer_id
ORDER BY SUM(amount) DESC
LIMIT 5

연습 문제 4

충성도가 가장 높은 고객을 위한 플래티넘 서비스를 시작하려고 한다.

결제 거래 건수가 40건 이상인 고객에게 플래티넘 멤버십을 부여하려고 하는데, 플래티넘 자격이 있는 고객 ID는 무엇인가?

SELECT customer_id, COUNT(amount) FROM payment
GROUP BY customer_id
HAVING COUNT(amount) >= 40

연습 문제 5

직원ID 2와의 결제 거래에서 100달러를 초과해 사용한 고객의 고객 ID는 무엇인가?

첫 시도:

SELECT customer_id, SUM(amount) FROM payment
GROUP BY customer_id, staff_id = 2
HAVING SUM(amount) > 100
  • GROUP BY에 staff_id까지 넣어버림

    • GROUP BY에는 열(column)만 들어가야 하고, 조건문을 넣을 수 없음!

    • staff_id = 2그룹핑 대상이 아니라 필터링 조건이므로 WHERE에서 처리해야 함

정답:

SELECT customer_id, SUM(amount) FROM payment
WHERE staff_id = 2
GROUP BY customer_id
HAVING SUM(amount) > 100