이미지 로딩 중...

GROUP BY로 그룹화 및 집계 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 11. 23. · 3 Views

GROUP BY로 그룹화 및 집계 완벽 가이드

SQL의 GROUP BY 문법을 사용하여 데이터를 그룹으로 묶고 집계하는 방법을 배웁니다. 실무에서 가장 많이 사용되는 그룹화 기법과 집계 함수, HAVING 절을 활용한 필터링까지 완벽하게 마스터할 수 있습니다.


목차

  1. GROUP BY 기본 개념
  2. 단일 컬럼 그룹화
  3. 다중 컬럼 그룹화
  4. 집계 함수와 함께 사용하기
  5. HAVING으로 그룹 필터링
  6. GROUP BY vs WHERE 차이점

1. GROUP BY 기본 개념

시작하며

여러분이 쇼핑몰 데이터베이스에서 각 카테고리별로 상품이 몇 개나 있는지 확인하고 싶을 때 어떻게 하시나요? 일일이 하나씩 세어볼 수는 없겠죠.

이런 문제는 실제 데이터 분석 업무에서 매일 발생합니다. 수천, 수만 개의 데이터를 특정 기준으로 묶어서 통계를 내야 하는 상황이 정말 많거든요.

예를 들어 지역별 매출 합계, 월별 주문 건수, 브랜드별 평균 가격 같은 것들이요. 바로 이럴 때 필요한 것이 GROUP BY입니다.

마치 학교에서 학생들을 반별로 나누듯이, 데이터를 우리가 원하는 기준으로 묶어서 각 그룹의 통계를 한 번에 볼 수 있게 해줍니다.

개요

간단히 말해서, GROUP BY는 데이터를 같은 값을 가진 그룹으로 묶어주는 SQL 문법입니다. 왜 이 개념이 필요한지 실무 관점에서 설명하면, 데이터베이스에는 엄청나게 많은 개별 데이터가 저장되어 있지만 우리는 대부분 요약된 정보가 필요합니다.

예를 들어, 100만 건의 주문 데이터가 있다면 각각의 주문보다는 "이번 달 총 매출이 얼마인지", "어느 지역에서 가장 많이 팔렸는지" 같은 요약 정보가 훨씬 유용하죠. 기존에는 데이터를 하나씩 읽어서 프로그램 코드로 그룹을 나누고 계산했다면, 이제는 GROUP BY 한 줄로 데이터베이스가 자동으로 그룹화하고 집계까지 해줍니다.

GROUP BY의 핵심 특징은 첫째, 지정한 컬럼의 값이 같은 행들을 하나의 그룹으로 만들고, 둘째, 각 그룹에 대해 집계 함수(COUNT, SUM, AVG 등)를 실행할 수 있으며, 셋째, 결과는 그룹당 하나의 행으로 표시된다는 점입니다. 이러한 특징들이 대용량 데이터 분석을 빠르고 효율적으로 만들어줍니다.

코드 예제

-- 카테고리별 상품 개수를 확인하는 기본 예제
SELECT category, COUNT(*) as product_count
FROM products
GROUP BY category;

-- 결과 예시:
-- category    | product_count
-- 의류        | 150
-- 전자제품    | 89
-- 식품        | 234

설명

이것이 하는 일: GROUP BY는 테이블의 행들을 특정 컬럼 값을 기준으로 그룹으로 나누고, 각 그룹에 대한 집계 결과를 반환합니다. 첫 번째로, SELECT 절에서 그룹화할 컬럼(category)과 집계 함수(COUNT)를 지정합니다.

category는 그룹을 나누는 기준이 되고, COUNT(*)는 각 그룹의 행 개수를 세는 역할입니다. 왜 이렇게 하냐면, 우리는 각 카테고리마다 몇 개의 상품이 있는지 알고 싶기 때문입니다.

그 다음으로, FROM 절에서 데이터를 가져올 테이블을 지정하고, GROUP BY 절에서 category를 명시합니다. 이 순간 데이터베이스는 products 테이블의 모든 행을 읽으면서 category 값이 같은 것들끼리 내부적으로 묶어줍니다.

예를 들어 '의류' 카테고리를 가진 모든 상품들이 하나의 그룹이 되는 거죠. 마지막으로, 각 그룹에 대해 COUNT(*) 함수가 실행되어 그룹 내 행의 개수를 세고, 최종적으로 각 카테고리와 그 카테고리의 상품 개수가 하나의 행으로 표시됩니다.

전체 상품이 473개여도 카테고리가 3개라면 결과는 3행만 나옵니다. 여러분이 이 코드를 사용하면 수천 개의 데이터를 일일이 확인하지 않고도 즉시 카테고리별 분포를 파악할 수 있습니다.

실무에서는 재고 관리, 매출 분석, 고객 세분화 등 거의 모든 데이터 분석 작업에 활용됩니다. 또한 데이터베이스 엔진이 최적화된 방식으로 처리하기 때문에 직접 코드로 반복문을 돌리는 것보다 수십 배 빠릅니다.

실전 팁

💡 GROUP BY에 명시한 컬럼은 반드시 SELECT 절에도 포함해야 합니다. 그렇지 않으면 어떤 그룹의 결과인지 알 수 없으니까요.

💡 집계 함수 없이 GROUP BY만 사용하면 DISTINCT와 비슷한 결과가 나오지만, 일반적으로 DISTINCT가 더 명확하므로 집계가 필요할 때만 GROUP BY를 사용하세요.

💡 큰 테이블에서 GROUP BY를 사용할 때는 그룹화할 컬럼에 인덱스가 있는지 확인하세요. 인덱스가 있으면 성능이 크게 향상됩니다.

💡 NULL 값들도 하나의 그룹으로 묶입니다. category가 NULL인 상품들이 있다면 그것들끼리 하나의 그룹이 되어 결과에 표시됩니다.


2. 단일 컬럼 그룹화

시작하며

여러분이 온라인 쇼핑몰을 운영하는데, 어느 지역에서 주문이 가장 많이 들어오는지 알고 싶다면 어떻게 하시겠어요? 수천 건의 주문 데이터를 지역별로 묶어서 확인해야 하는 상황입니다.

이런 문제는 마케팅 전략을 세우거나 물류 센터 위치를 결정할 때 정말 중요합니다. 데이터는 많은데 어떤 패턴이 있는지 한눈에 보이지 않아서 답답하죠.

바로 이럴 때 단일 컬럼 그룹화가 완벽한 해결책입니다. 하나의 기준(예: 지역)으로 데이터를 묶어서 각 그룹의 통계를 명확하게 보여줍니다.

개요

간단히 말해서, 단일 컬럼 그룹화는 테이블의 하나의 컬럼을 기준으로 데이터를 그룹으로 나누는 가장 기본적인 GROUP BY 사용법입니다. 왜 이것이 실무에서 중요한지 설명하면, 대부분의 비즈니스 질문이 "카테고리별로", "지역별로", "기간별로" 같은 단일 기준 분석을 요구하기 때문입니다.

예를 들어, "각 브랜드별 평균 가격은 얼마인가?", "요일별 방문자 수는?" 같은 질문들이 모두 단일 컬럼 그룹화로 해결됩니다. 기존에는 스프레드시트를 열어서 수동으로 필터링하고 계산했다면, 이제는 SQL 한 줄로 즉시 결과를 얻을 수 있습니다.

데이터가 실시간으로 변해도 쿼리만 다시 실행하면 최신 통계가 나옵니다. 단일 컬럼 그룹화의 핵심은 첫째, 하나의 컬럼만 GROUP BY에 지정하여 명확한 분류 기준을 제공하고, 둘째, 다양한 집계 함수(COUNT, SUM, AVG, MAX, MIN)를 함께 사용할 수 있으며, 셋째, 결과가 간단하고 이해하기 쉽다는 점입니다.

이러한 특징 덕분에 초보자도 쉽게 데이터 분석을 시작할 수 있습니다.

코드 예제

-- 지역별 주문 건수와 총 매출액을 조회
SELECT
    region,                          -- 그룹화 기준
    COUNT(*) as order_count,         -- 주문 건수
    SUM(amount) as total_sales,      -- 총 매출
    AVG(amount) as avg_order_value   -- 평균 주문 금액
FROM orders
GROUP BY region
ORDER BY total_sales DESC;           -- 매출 높은 순으로 정렬

-- 결과 예시:
-- region | order_count | total_sales | avg_order_value
-- 서울   | 1250        | 45000000    | 36000
-- 부산   | 680         | 22500000    | 33088

설명

이것이 하는 일: 단일 컬럼 그룹화는 테이블의 모든 행을 하나의 컬럼 값을 기준으로 그룹으로 나누고, 각 그룹에 대해 여러 집계 함수를 동시에 실행하여 종합적인 통계를 제공합니다. 첫 번째로, SELECT 절에서 그룹화 기준인 region과 함께 세 가지 집계 함수를 지정합니다.

COUNT(*)는 각 지역의 주문 건수를, SUM(amount)는 총 매출을, AVG(amount)는 평균 주문 금액을 계산합니다. 이렇게 여러 집계를 한 번에 조회하면 각 지역의 성과를 다각도로 분석할 수 있습니다.

그 다음으로, GROUP BY region이 실행되면서 데이터베이스는 orders 테이블의 모든 주문을 지역별로 분류합니다. 서울 지역의 모든 주문이 하나의 그룹, 부산의 모든 주문이 또 다른 그룹이 되는 식이죠.

이 과정에서 데이터베이스는 내부적으로 정렬이나 해시 테이블 같은 최적화 기법을 사용합니다. 마지막으로, 각 그룹에 대해 지정한 집계 함수들이 실행되고, ORDER BY 절로 매출이 높은 지역부터 보기 좋게 정렬됩니다.

최종적으로 각 지역당 하나의 행으로 표시되어 전체 지역의 성과를 한눈에 비교할 수 있게 됩니다. 여러분이 이 코드를 사용하면 어느 지역에 마케팅 리소스를 집중해야 할지, 어느 지역의 객단가가 높은지 즉시 파악할 수 있습니다.

실무에서는 대시보드 구성, 보고서 생성, A/B 테스트 분석 등에 활용되며, 의사결정의 핵심 근거가 됩니다. 또한 ORDER BY와 LIMIT을 함께 사용하면 "상위 10개 지역" 같은 랭킹 분석도 쉽게 할 수 있습니다.

실전 팁

💡 여러 집계 함수를 동시에 사용할 때는 각각에 명확한 별칭(as)을 붙여서 결과를 읽기 쉽게 만드세요. order_count, total_sales 같은 이름이 좋습니다.

💡 ORDER BY는 GROUP BY 이후에 실행되므로 집계 결과를 기준으로 정렬할 수 있습니다. 매출 순, 건수 순 등 상황에 맞게 정렬하세요.

💡 COUNT()와 COUNT(column_name)은 다릅니다. COUNT()는 모든 행을 세지만, COUNT(column_name)은 해당 컬럼이 NULL이 아닌 행만 셉니다.

💡 결과에 총계(grand total)를 추가하고 싶다면 WITH ROLLUP(MySQL) 또는 GROUPING SETS(PostgreSQL)를 활용하세요.

💡 단일 컬럼 그룹화는 차트로 시각화하기 가장 좋은 형태입니다. 막대 그래프나 파이 차트에 바로 적용할 수 있습니다.


3. 다중 컬럼 그룹화

시작하며

여러분이 전국 체인점을 운영하는데, 각 지역의 각 매장별로 실적을 확인하고 싶다면? 지역만으로는 부족하고, 지역 내에서도 매장을 구분해야 하는 상황입니다.

이런 문제는 데이터를 여러 단계로 세분화해야 할 때 항상 발생합니다. 예를 들어 연도별-월별 매출, 카테고리별-브랜드별 재고, 부서별-직급별 인원 같은 계층적 분석이 필요한 경우가 정말 많죠.

바로 이럴 때 필요한 것이 다중 컬럼 그룹화입니다. 여러 기준을 동시에 적용하여 데이터를 더 세밀하게 나누고 분석할 수 있게 해줍니다.

개요

간단히 말해서, 다중 컬럼 그룹화는 두 개 이상의 컬럼을 GROUP BY에 지정하여 데이터를 계층적으로 그룹화하는 방법입니다. 왜 이것이 실무에서 필수적인지 설명하면, 비즈니스 질문은 대부분 단일 차원이 아니라 복합적입니다.

"서울에서 얼마나 팔렸나?"보다는 "서울의 각 매장에서 각 카테고리별로 얼마나 팔렸나?"가 실제 질문이거든요. 예를 들어, 계절별-지역별 수요 패턴을 분석하거나, 연령대별-성별 구매 성향을 파악할 때 다중 컬럼 그룹화가 꼭 필요합니다.

기존에는 첫 번째 기준으로 그룹화한 후 각 그룹에서 다시 쿼리를 실행하거나 프로그램 코드로 복잡하게 처리했다면, 이제는 GROUP BY에 여러 컬럼을 나열하는 것만으로 한 번에 해결됩니다. 다중 컬럼 그룹화의 핵심 특징은 첫째, GROUP BY에 나열한 컬럼들의 조합이 유일한 그룹을 형성한다는 점입니다.

(지역='서울', 매장='강남점')과 (지역='서울', 매장='홍대점')은 다른 그룹이죠. 둘째, 컬럼의 순서는 결과에 영향을 주지 않지만 가독성을 위해 계층 구조대로 나열하는 게 좋습니다.

셋째, 결과 행 수는 모든 컬럼 조합의 개수만큼 나옵니다. 이러한 특징들이 복잡한 비즈니스 분석을 가능하게 합니다.

코드 예제

-- 지역별, 카테고리별 매출 분석
SELECT
    region,                          -- 첫 번째 그룹화 기준
    category,                        -- 두 번째 그룹화 기준
    COUNT(*) as product_count,       -- 상품 수
    SUM(sales) as total_sales,       -- 총 매출
    AVG(price) as avg_price          -- 평균 가격
FROM products
GROUP BY region, category
ORDER BY region, total_sales DESC;   -- 지역별로 묶고, 매출 순으로

-- 결과 예시:
-- region | category | product_count | total_sales | avg_price
-- 서울   | 전자제품 | 150           | 12000000    | 80000
-- 서울   | 의류     | 320           | 8500000     | 26563
-- 부산   | 전자제품 | 89            | 6800000     | 76404

설명

이것이 하는 일: 다중 컬럼 그룹화는 여러 컬럼의 값 조합을 기준으로 데이터를 그룹으로 나누어, 각 세부 그룹에 대한 집계를 수행합니다. 첫 번째로, GROUP BY region, category를 지정하면 데이터베이스는 region과 category의 모든 조합을 찾아냅니다.

예를 들어 (서울, 전자제품), (서울, 의류), (부산, 전자제품) 같은 조합들이 각각 하나의 그룹이 됩니다. 왜 이렇게 하냐면, 우리는 서울 전체가 아니라 서울에서 전자제품과 의류가 각각 얼마나 팔렸는지 구분해서 알고 싶기 때문입니다.

그 다음으로, 각 조합에 해당하는 상품들이 하나의 그룹으로 묶이고, 그 그룹에 대해 COUNT, SUM, AVG 같은 집계 함수가 실행됩니다. 서울 지역의 전자제품들끼리 묶여서 그 수량과 매출이 계산되고, 서울의 의류는 또 따로 계산되는 식이죠.

이 과정에서 데이터베이스는 효율적인 정렬이나 해싱 알고리즘을 사용합니다. 마지막으로, ORDER BY 절에서 먼저 region으로 정렬한 후 같은 지역 내에서는 total_sales가 높은 순으로 정렬합니다.

최종적으로 각 지역-카테고리 조합마다 하나의 행으로 표시되어, 어느 지역의 어느 카테고리가 강한지 명확하게 보입니다. 여러분이 이 코드를 사용하면 지역별 강점 카테고리를 파악하여 지역 특화 전략을 세울 수 있습니다.

실무에서는 교차 분석(cross-tabulation), 피벗 테이블 생성, 세그먼트별 성과 추적 등에 활용됩니다. 또한 3개, 4개 이상의 컬럼으로도 확장 가능하여 매우 복잡한 다차원 분석도 수행할 수 있습니다.

실전 팁

💡 다중 컬럼 그룹화의 결과 행 수는 각 컬럼의 고유값 조합 개수입니다. 너무 많은 컬럼을 그룹화하면 결과가 방대해지니 주의하세요.

💡 SELECT 절에 포함된 모든 비집계 컬럼은 반드시 GROUP BY에도 포함되어야 합니다. 그렇지 않으면 에러가 발생하거나 예상치 못한 결과가 나옵니다.

💡 시간 데이터를 그룹화할 때는 YEAR(date), MONTH(date) 같은 함수를 사용하여 연도별-월별 그룹화를 할 수 있습니다.

💡 결과를 스프레드시트처럼 보고 싶다면 피벗(PIVOT) 기능을 사용하거나, BI 도구에서 다중 컬럼 그룹화 결과를 시각화하세요.

💡 WITH ROLLUP을 추가하면 각 계층의 소계와 총계를 자동으로 계산할 수 있어 보고서 작성에 유용합니다.


4. 집계 함수와 함께 사용하기

시작하며

여러분이 상품 데이터를 그룹화했는데 각 그룹에 몇 개의 상품이 있는지, 평균 가격은 얼마인지, 가장 비싼 상품은 얼마인지 알고 싶다면 어떻게 하시겠어요? 그룹만 나누는 것으로는 부족하고 각 그룹의 통계가 필요한 상황입니다.

이런 문제는 데이터 분석의 핵심입니다. 그룹화는 분류일 뿐, 실제로 우리가 원하는 건 각 그룹의 합계, 평균, 최댓값, 최솟값 같은 구체적인 숫자니까요.

이런 통계 없이는 의사결정을 할 수 없습니다. 바로 이럴 때 필요한 것이 집계 함수입니다.

GROUP BY와 함께 사용하면 각 그룹의 다양한 통계를 한 번에 계산할 수 있습니다.

개요

간단히 말해서, 집계 함수는 여러 행의 데이터를 하나의 값으로 요약해주는 SQL 함수이며, GROUP BY와 함께 사용하면 각 그룹별로 집계를 수행합니다. 왜 이것이 실무에서 필수적인지 설명하면, 비즈니스 의사결정은 항상 숫자에 기반하기 때문입니다.

"어떤 카테고리가 인기 있나요?"라는 질문에 대한 답은 COUNT(상품 개수), SUM(총 매출), AVG(평균 평점) 같은 집계 결과로 나와야 합니다. 예를 들어, 재고 관리를 위해 카테고리별 총 재고량(SUM), 가격 전략을 위해 브랜드별 평균 가격(AVG), 이상치 탐지를 위해 최댓값(MAX)과 최솟값(MIN)을 확인하는 게 일상적인 업무죠.

기존에는 각 그룹의 데이터를 따로 추출해서 엑셀로 계산했다면, 이제는 SQL 쿼리 하나로 모든 그룹의 모든 통계를 동시에 얻을 수 있습니다. 주요 집계 함수로는 COUNT(개수 세기), SUM(합계), AVG(평균), MAX(최댓값), MIN(최솟값)이 있으며, 각각 고유한 용도가 있습니다.

COUNT는 행 개수나 NULL이 아닌 값의 개수를, SUM은 숫자의 총합을, AVG는 평균을, MAX와 MIN은 최댓값과 최솟값을 반환합니다. 이러한 함수들을 조합하면 데이터의 거의 모든 통계적 특성을 파악할 수 있습니다.

코드 예제

-- 카테고리별 다양한 집계 통계 조회
SELECT
    category,
    COUNT(*) as total_products,          -- 총 상품 수
    COUNT(DISTINCT brand) as brand_count,-- 브랜드 수 (중복 제거)
    SUM(stock) as total_stock,           -- 총 재고량
    AVG(price) as avg_price,             -- 평균 가격
    MAX(price) as highest_price,         -- 최고 가격
    MIN(price) as lowest_price,          -- 최저 가격
    MAX(price) - MIN(price) as price_range -- 가격 범위
FROM products
WHERE stock > 0                          -- 재고가 있는 상품만
GROUP BY category
HAVING COUNT(*) >= 10;                   -- 상품이 10개 이상인 카테고리만

설명

이것이 하는 일: 집계 함수는 GROUP BY로 나뉜 각 그룹의 행들을 분석하여 개수, 합계, 평균, 최댓값, 최솟값 등 다양한 통계값을 계산합니다. 첫 번째로, SELECT 절에서 7개의 서로 다른 집계를 지정합니다.

COUNT(*)는 각 카테고리의 전체 상품 수를, COUNT(DISTINCT brand)는 중복을 제외한 브랜드 수를 셉니다. DISTINCT 키워드를 사용하면 같은 브랜드가 여러 번 나와도 한 번만 카운트됩니다.

SUM(stock)은 각 카테고리의 모든 상품 재고를 더하고, AVG(price)는 평균 가격을 계산합니다. 왜 이렇게 많은 집계를 한 번에 하냐면, 각 카테고리를 종합적으로 이해하기 위함입니다.

그 다음으로, WHERE 절에서 재고가 있는 상품만 필터링한 후 GROUP BY category가 실행됩니다. 각 카테고리 그룹에 대해 지정한 모든 집계 함수가 동시에 실행되는데, 데이터베이스는 한 번의 스캔으로 모든 집계를 효율적으로 처리합니다.

MAX(price)와 MIN(price)를 찾으면서 동시에 SUM과 AVG도 계산하는 거죠. 마지막으로, HAVING 절에서 상품이 10개 미만인 카테고리는 제외합니다.

MAX(price) - MIN(price)처럼 집계 결과를 가지고 계산식을 만들 수도 있어서, 가격 범위 같은 파생 통계도 쉽게 구할 수 있습니다. 최종적으로 각 카테고리의 모든 통계가 한 행에 표시되어 어떤 카테고리가 상품도 많고 재고도 풍부한지 한눈에 파악할 수 있습니다.

여러분이 이 코드를 사용하면 카테고리별 상품 포트폴리오를 완전히 이해할 수 있습니다. 실무에서는 재고 최적화(총 재고량), 가격 전략(평균, 최고, 최저 가격), 제품 다양성 평가(브랜드 수), 이상치 탐지(가격 범위) 등에 활용됩니다.

또한 ROUND(AVG(price), 2)처럼 집계 결과를 포맷팅하거나, 여러 집계를 조합하여 복잡한 KPI를 계산할 수도 있습니다.

실전 팁

💡 COUNT(*)는 NULL을 포함한 모든 행을 세지만, COUNT(column)은 해당 컬럼이 NULL인 행은 제외합니다. 상황에 맞게 선택하세요.

💡 AVG 함수는 NULL 값을 무시하고 계산합니다. NULL을 0으로 처리하고 싶다면 COALESCE(column, 0)를 사용하세요.

💡 집계 결과에서 다시 계산(MAX - MIN 같은)을 할 수 있지만, 너무 복잡해지면 서브쿼리나 CTE를 사용하는 게 가독성이 좋습니다.

💡 SUM과 AVG는 숫자 타입에만 사용 가능합니다. 문자열을 합치려면 GROUP_CONCAT(MySQL) 또는 STRING_AGG(PostgreSQL)를 사용하세요.

💡 대용량 데이터에서 COUNT(DISTINCT column)은 느릴 수 있습니다. 정확도가 크게 중요하지 않다면 근사 알고리즘(HyperLogLog 등)을 고려하세요.


5. HAVING으로 그룹 필터링

시작하며

여러분이 카테고리별 매출을 조회했는데, 매출이 일정 금액 이상인 카테고리만 보고 싶다면 어떻게 하시겠어요? WHERE 절로는 집계 결과를 필터링할 수 없어서 막막한 상황입니다.

이런 문제는 그룹화 후 통계 기반 필터링이 필요할 때 항상 발생합니다. 예를 들어 "평균 평점이 4.0 이상인 카테고리만", "주문이 100건 이상인 고객만", "재고가 1000개 이상인 지역만" 같은 조건은 집계 함수의 결과를 기준으로 필터링해야 하는데, WHERE는 집계 전에 실행되기 때문에 사용할 수 없죠.

바로 이럴 때 필요한 것이 HAVING 절입니다. GROUP BY 후의 집계 결과를 기준으로 그룹을 필터링할 수 있게 해줍니다.

개요

간단히 말해서, HAVING은 GROUP BY로 생성된 그룹들 중에서 특정 조건을 만족하는 그룹만 결과에 포함시키는 필터링 절입니다. 왜 이것이 실무에서 중요한지 설명하면, 데이터 분석 시 모든 그룹을 보는 게 아니라 의미 있는 그룹만 보고 싶을 때가 많기 때문입니다.

예를 들어, 전체 1000개 카테고리 중 매출이 높은 상위 카테고리만 분석하거나, 주문 건수가 일정 이상인 충성 고객만 추출하거나, 통계적으로 유의미한 크기의 그룹만 포함시키는 경우가 일상적입니다. WHERE와 HAVING의 핵심 차이는 실행 시점입니다.

WHERE는 그룹화 전에 개별 행을 필터링하지만, HAVING은 그룹화와 집계 후에 그룹을 필터링합니다. 따라서 WHERE에는 집계 함수를 사용할 수 없지만, HAVING에는 사용할 수 있습니다.

HAVING의 핵심 특징은 첫째, 집계 함수의 결과를 조건으로 사용할 수 있다는 점입니다(COUNT(*) > 100, AVG(price) >= 50000 등). 둘째, GROUP BY 없이는 사용할 수 없으며, 셋째, WHERE와 함께 사용할 수 있어서 행 필터링과 그룹 필터링을 동시에 수행할 수 있습니다.

이러한 특징들이 정교한 데이터 분석을 가능하게 합니다.

코드 예제

-- 매출이 1천만원 이상이고 상품이 50개 이상인 카테고리만 조회
SELECT
    category,
    COUNT(*) as product_count,
    SUM(sales) as total_sales,
    AVG(price) as avg_price
FROM products
WHERE status = 'active'              -- 행 필터링: 활성 상품만
GROUP BY category
HAVING SUM(sales) >= 10000000        -- 그룹 필터링: 매출 1천만원 이상
   AND COUNT(*) >= 50                -- 그룹 필터링: 상품 50개 이상
ORDER BY total_sales DESC;

-- 결과에는 조건을 만족하는 카테고리만 포함됨
-- category  | product_count | total_sales | avg_price
-- 전자제품  | 150           | 45000000    | 300000
-- 의류      | 380           | 22000000    | 57894

설명

이것이 하는 일: HAVING은 GROUP BY와 집계 함수 실행이 완료된 후, 집계 결과를 조건으로 사용하여 특정 그룹만 최종 결과에 포함시킵니다. 첫 번째로, WHERE status = 'active'가 실행되어 비활성 상품들이 먼저 제거됩니다.

이것은 그룹화 전에 일어나므로 비활성 상품은 집계에도 포함되지 않습니다. 왜 WHERE를 먼저 쓰냐면, 불필요한 데이터를 미리 제거하면 그 후의 GROUP BY와 집계가 더 빠르게 실행되기 때문입니다.

그 다음으로, GROUP BY category가 실행되고 각 카테고리별로 COUNT, SUM, AVG가 계산됩니다. 예를 들어 '전자제품' 카테고리의 활성 상품들이 모두 묶여서 상품 수, 총 매출, 평균 가격이 계산되는 거죠.

이 시점에서는 아직 모든 카테고리가 결과에 포함되어 있습니다. 마지막으로, HAVING 절이 실행되면서 각 그룹(카테고리)의 집계 결과를 검사합니다.

SUM(sales) >= 10000000 조건을 만족하지 못하는 카테고리, 즉 매출이 1천만원 미만인 카테고리는 결과에서 제외됩니다. COUNT(*) >= 50 조건도 동시에 검사되어 상품이 50개 미만인 카테고리도 제외되죠.

최종적으로 두 조건을 모두 만족하는 카테고리만 남아서 매출 순으로 정렬되어 표시됩니다. 여러분이 이 코드를 사용하면 의미 있는 대형 카테고리만 집중해서 분석할 수 있습니다.

실무에서는 노이즈 제거(샘플 수가 적은 그룹 제외), 이상치 탐지(평균에서 크게 벗어난 그룹 찾기), 성과 분석(KPI를 초과한 세그먼트만 추출), 보고서 간소화(주요 그룹만 표시) 등에 활용됩니다. 또한 HAVING 절에서 복잡한 조건식(AVG(price) > (SELECT AVG(price) FROM products) 같은 서브쿼리)도 사용 가능합니다.

실전 팁

💡 WHERE와 HAVING을 함께 사용하면 성능이 좋아집니다. WHERE로 먼저 행을 줄이고 HAVING으로 그룹을 필터링하세요.

💡 HAVING 절에는 SELECT에 없는 집계도 사용할 수 있습니다. 예: SELECT category, COUNT(*) ... HAVING SUM(sales) > 1000000 (SUM이 SELECT에 없어도 됨)

💡 복잡한 HAVING 조건은 가독성을 위해 서브쿼리나 CTE로 분리하는 것도 좋습니다. 특히 평균과 비교하는 경우 유용합니다.

💡 HAVING COUNT(*) > 1을 사용하면 중복된 조합만 찾을 수 있어서 데이터 품질 검증에 유용합니다.

💡 일부 데이터베이스는 HAVING 절에서 SELECT의 별칭(alias)을 사용할 수 있지만, 표준 SQL은 아니므로 집계 함수를 직접 쓰는 게 호환성이 좋습니다.


6. GROUP BY vs WHERE 차이점

시작하며

여러분이 SQL 쿼리를 작성할 때 "이 조건은 WHERE에 써야 하나, HAVING에 써야 하나?" 하고 고민해 본 적 있으신가요? 또는 WHERE 절에 집계 함수를 써서 에러가 난 경험이 있으신가요?

이런 문제는 SQL을 배우는 모든 사람이 겪는 혼란입니다. WHERE, GROUP BY, HAVING이 모두 필터링과 관련되어 보이지만 실제로는 완전히 다른 시점에 다른 목적으로 실행되기 때문이죠.

이 차이를 정확히 이해하지 못하면 에러가 나거나 원하는 결과를 얻지 못합니다. 바로 이 섹션에서는 GROUP BY, WHERE, HAVING의 실행 순서와 각각의 역할을 명확히 이해하여 어떤 상황에서 무엇을 써야 할지 확실하게 알려드립니다.

개요

간단히 말해서, WHERE는 행을 필터링하고, GROUP BY는 행을 그룹화하며, HAVING은 그룹을 필터링합니다. 이 세 가지는 실행 순서가 정해져 있어서 각자의 역할이 명확히 구분됩니다.

왜 이 차이를 이해하는 게 중요한지 설명하면, 잘못된 위치에 조건을 넣으면 쿼리가 아예 실행되지 않거나, 실행되더라도 완전히 다른 결과가 나오기 때문입니다. 예를 들어, WHERE COUNT(*) > 10처럼 쓰면 에러가 나고, 상품 가격 조건을 HAVING에 넣으면 쿼리는 실행되지만 성능이 크게 떨어집니다.

실무에서 쿼리 성능 문제의 상당수가 WHERE와 HAVING을 잘못 사용해서 발생합니다. SQL 쿼리의 실행 순서는 FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY입니다.

이 순서가 핵심인데, WHERE는 GROUP BY 전에 실행되므로 개별 행의 컬럼 값만 조건으로 쓸 수 있고, HAVING은 GROUP BY 후에 실행되므로 집계 함수 결과를 조건으로 쓸 수 있습니다. WHERE와 HAVING의 핵심 차이는 첫째, WHERE는 그룹화 전 개별 행을 필터링하고 HAVING은 그룹화 후 그룹을 필터링합니다.

둘째, WHERE에는 집계 함수를 사용할 수 없지만 HAVING에는 사용할 수 있습니다. 셋째, WHERE로 행을 미리 줄이면 GROUP BY 성능이 좋아지므로 가능하면 WHERE를 우선 사용해야 합니다.

이러한 차이를 이해하면 효율적이고 정확한 쿼리를 작성할 수 있습니다.

코드 예제

-- WHERE vs HAVING 비교 예제
-- 예제 1: WHERE 사용 (올바름) - 행 필터링
SELECT category, COUNT(*) as product_count, AVG(price) as avg_price
FROM products
WHERE price >= 10000              -- 개별 상품의 가격 조건
GROUP BY category;

-- 예제 2: HAVING 사용 (올바름) - 그룹 필터링
SELECT category, COUNT(*) as product_count, AVG(price) as avg_price
FROM products
GROUP BY category
HAVING AVG(price) >= 10000;       -- 카테고리 평균 가격 조건

-- 예제 3: WHERE와 HAVING 함께 사용 (가장 효율적)
SELECT category, COUNT(*) as product_count, AVG(price) as avg_price
FROM products
WHERE status = 'active'           -- 먼저 행 필터링
GROUP BY category
HAVING COUNT(*) >= 50;            -- 그 후 그룹 필터링

설명

이것이 하는 일: WHERE와 HAVING은 서로 다른 시점에 서로 다른 대상(행 vs 그룹)을 필터링하며, 이 차이를 이해하는 것이 GROUP BY를 제대로 사용하는 핵심입니다. 첫 번째 예제에서 WHERE price >= 10000은 각 상품 행의 가격을 검사합니다.

가격이 10000원 미만인 상품들은 GROUP BY 전에 이미 제거되므로, 카테고리별 집계에 포함되지 않습니다. 결과적으로 "10000원 이상 상품들만 가지고 카테고리별 통계를 낸다"는 의미죠.

왜 WHERE를 쓰냐면, price는 개별 상품의 속성이고 집계 결과가 아니기 때문입니다. 두 번째 예제에서 HAVING AVG(price) >= 10000은 각 카테고리 그룹의 평균 가격을 검사합니다.

모든 상품이 GROUP BY로 카테고리별로 묶이고 평균이 계산된 후, 그 평균이 10000원 미만인 카테고리가 결과에서 제외됩니다. 결과적으로 "모든 상품을 가지고 카테고리별 통계를 낸 후, 평균 가격이 10000원 이상인 카테고리만 보여준다"는 의미입니다.

왜 HAVING을 쓰냐면, AVG(price)는 집계 함수의 결과이기 때문입니다. 세 번째 예제는 WHERE와 HAVING을 함께 사용하는 가장 효율적인 패턴입니다.

먼저 WHERE status = 'active'로 비활성 상품을 제거하고(행 필터링), 남은 활성 상품들만 GROUP BY로 카테고리별로 묶습니다. 그 후 HAVING COUNT(*) >= 50으로 상품이 50개 미만인 카테고리를 제외합니다(그룹 필터링).

이렇게 하면 불필요한 데이터를 미리 줄여서 그룹화와 집계가 더 빠르게 실행됩니다. 여러분이 이 차이를 이해하면 "이 조건은 어디에 써야 하나?" 하는 고민이 사라집니다.

실무에서는 개별 행의 속성 조건(가격, 상태, 날짜 등)은 WHERE에, 집계 결과 조건(평균, 합계, 개수 등)은 HAVING에 작성하면 됩니다. 또한 성능 최적화를 위해 가능한 한 WHERE로 먼저 데이터를 줄이는 습관을 들이면, 대용량 데이터에서도 빠른 쿼리를 작성할 수 있습니다.

실전 팁

💡 개별 행의 컬럼 값 조건은 항상 WHERE에 작성하세요. WHERE가 먼저 실행되어 처리할 데이터를 줄이므로 성능이 훨씬 좋습니다.

💡 집계 함수(COUNT, SUM, AVG, MAX, MIN) 결과를 조건으로 사용할 때만 HAVING을 쓰세요. 그 외에는 WHERE를 사용하는 게 원칙입니다.

💡 SQL 실행 순서를 외우세요: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY. 이 순서를 알면 어디에 무엇을 써야 할지 자동으로 알 수 있습니다.

💡 WHERE에 집계 함수를 쓰면 에러가 나는 이유는 WHERE가 GROUP BY보다 먼저 실행되어 아직 집계 결과가 없기 때문입니다. 이럴 땐 HAVING을 사용하세요.

💡 성능이 중요한 쿼리에서는 EXPLAIN(MySQL) 또는 EXPLAIN ANALYZE(PostgreSQL)로 실행 계획을 확인하여 WHERE와 HAVING이 제대로 작동하는지 검증하세요.


#SQL#GROUP BY#집계함수#HAVING#데이터분석#SQL,Database,데이터베이스

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스

SQL 실전 종합 프로젝트 완벽 가이드

전자상거래 시스템을 직접 구축하면서 배우는 SQL 실전 프로젝트입니다. DB 설계부터 성능 최적화까지, 실무에서 필요한 모든 SQL 기술을 단계별로 마스터할 수 있습니다. 초급 개발자도 따라하기 쉬운 친절한 가이드로 구성되어 있습니다.

실무 데이터 분석 SQL 완벽 가이드

실제 업무에서 자주 사용하는 SQL 데이터 분석 기법을 단계별로 학습합니다. 매출 집계부터 고객 세분화까지, 실전 대시보드 쿼리 작성 방법을 배워보세요.

데이터 모델링과 정규화 완벽 가이드

데이터베이스 설계의 핵심인 데이터 모델링과 정규화를 초급 개발자 눈높이에서 쉽게 설명합니다. ERD 작성부터 제1~3정규형, 정규화의 장단점, 비정규화 전략, 실무 설계 패턴까지 실전에서 바로 활용할 수 있는 노하우를 담았습니다.

트랜잭션과 ACID 원칙 완벽 가이드

데이터베이스의 핵심 개념인 트랜잭션과 ACID 원칙을 초급 개발자도 쉽게 이해할 수 있도록 실무 예제와 함께 설명합니다. 안전한 데이터 처리를 위한 필수 지식을 친근하게 배워보세요.

인덱스와 쿼리 성능 최적화 완벽 가이드

데이터베이스 성능의 핵심인 인덱스를 처음부터 끝까지 배워봅니다. B-Tree 구조부터 실행 계획 분석까지, 실무에서 바로 사용할 수 있는 인덱스 최적화 전략을 초급자도 이해할 수 있게 설명합니다.