2025-08-18 20:56

Tags:

SQL GROUP BY 완벽 정복 데이터 집계의 마법사 되기 🧙‍♂️

데이터의 시대, 우리는 매일 방대한 양의 정보와 마주합니다. 쇼핑몰의 수백만 건의 주문 내역, 회사의 수천 명의 직원 정보, 웹사이트의 수십억 개에 달하는 로그 데이터까지. 이 거대한 데이터 더미 속에서 의미 있는 인사이트를 발견하려면 어떻게 해야 할까요? 단순히 데이터를 나열하는 것만으로는 부족합니다. 데이터를 ‘요약’하고 ‘분석’하는 과정이 반드시 필요합니다.

바로 이때, SQL의 GROUP BY 절이 마법 같은 힘을 발휘합니다. GROUP BY는 흩어져 있는 개별 데이터를 특정 기준으로 묶어 그룹화하고, 각 그룹의 통계적 특성(합계, 평균, 개수 등)을 계산하여 데이터의 숨겨진 패턴과 의미를 찾아내는 핵심 도구입니다.

이 핸드북에서는 GROUP BY가 왜 필요한지, 그 탄생 배경부터 시작하여 기본 구조와 사용법, 그리고 실무에서 마주할 수 있는 고급 활용법과 주의사항까지, GROUP BY의 모든 것을 체계적으로 탐험해 보겠습니다. 이 글을 끝까지 읽으신다면, 여러분은 더 이상 GROUP BY를 두려워하지 않고, 데이터를 자유자재로 요리하는 ‘데이터 집계의 마법사’로 거듭나게 될 것입니다.

1. GROUP BY는 왜 만들어졌을까? (The ‘Why’)

GROUP BY의 필요성을 이해하기 위해 간단한 예시를 들어보겠습니다. 여기 온라인 서점의 orders (주문) 테이블이 있다고 상상해 보세요.

orders 테이블

| order_id | user_id | book_category | price | order_date | | :--- | :--- | :--- | :--- | :--- | | 101 | 25 | 소설 | 15000 | 2023-10-01 | | 102 | 11 | IT | 25000 | 2023-10-01 | | 103 | 25 | 소설 | 18000 | 2023-10-02 | | 104 | 42 | 경제 | 22000 | 2023-10-02 | | 105 | 11 | IT | 30000 | 2023-10-03 | | 106 | 25 | 경제 | 20000 | 2023-10-03 |

이 테이블에서 “책 카테고리별 총 판매액은 얼마일까?”라는 질문에 답을 찾아야 한다면 어떻게 해야 할까요?

SELECT * FROM orders; 로 모든 데이터를 가져와서 엑셀로 옮긴 다음, 카테고리별로 정렬하고, 각 그룹의 합계를 구하는 고된 수작업을 할 수도 있습니다. 하지만 데이터가 수백만 건이라면 이는 불가능에 가깝습니다.

데이터베이스는 바로 이런 ‘집계’ 요구사항을 효율적으로 처리하기 위해 탄생했습니다. 관계형 데이터베이스 이론의 핵심은 데이터를 집합(Set)으로 보고, 이 집합들을 조작하여 원하는 정보를 추출하는 것입니다. GROUP BY는 이 ‘조작’의 한 종류로, 특정 열(column)의 동일한 값을 가진 행(row)들을 하나의 그룹으로 묶어주는 연산을 수행합니다.

마치 우편배달부가 수많은 편지를 동네별로 분류하는 것과 같습니다. 개별 편지 하나하나는 단순한 정보(주소, 수신인)에 불과하지만, ‘강남구’, ‘서초구’ 등으로 분류하고 나면 “오늘 강남구로 가는 편지는 총 몇 통인가?” 와 같은 의미 있는 질문에 답할 수 있게 됩니다. GROUP BY는 데이터 세계의 우편배달부인 셈입니다.

2. GROUP BY의 기본 구조와 문법 (The ‘How’)

GROUP BYSELECT 문의 일부로, WHERE 절 뒤, ORDER BY 절 앞에 위치합니다. 기본 구조는 다음과 같습니다.

SELECT
    그룹화할_컬럼,
    집계함수(계산할_컬럼)
FROM
    테이블이름
WHERE
    조건 -- (선택 사항)
GROUP BY
    그룹화할_컬럼;

각 구성 요소를 자세히 살펴보겠습니다.

  • GROUP BY: 어떤 컬럼을 기준으로 데이터를 묶을지 지정합니다. 여기에 명시된 컬럼의 값이 같은 행들이 하나의 그룹이 됩니다.

  • SELECT:

    • 그룹화할 컬럼: GROUP BY 절에 사용된 컬럼은 SELECT 절에도 반드시 포함될 수 있습니다.

    • 집계 함수(Aggregate Function): GROUP BY의 핵심 파트너입니다. 그룹으로 묶인 데이터에 대해 통계적인 계산을 수행합니다. GROUP BY 없이 집계 함수만 단독으로 사용하면 테이블 전체에 대한 계산을 수행합니다.

꼭 알아야 할 집계 함수들

함수설명예시
COUNT()그룹에 포함된 행의 개수를 셉니다.COUNT(*): 그룹의 총 행 수
COUNT(column): 해당 컬럼이 NULL이 아닌 행 수
SUM()그룹 내 숫자 데이터의 합계를 구합니다.SUM(price): 총 판매액
AVG()그룹 내 숫자 데이터의 평균을 구합니다.AVG(score): 평균 점수
MAX()그룹 내 최댓값을 찾습니다.MAX(salary): 최고 연봉
MIN()그룹 내 최솟값을 찾습니다.MIN(order_date): 최초 주문일

⚠️ SELECT 절의 중요한 규칙

GROUP BY를 사용할 때 가장 흔하게 마주하는 오류는 SELECT 절과 관련이 있습니다. SELECT 절에는 GROUP BY 절에 명시된 컬럼과 집계 함수만 사용할 수 있습니다.

왜 그럴까요? 앞선 orders 테이블에서 GROUP BY book_category를 했다고 생각해봅시다. ‘소설’ 카테고리 그룹에는 2개의 행이 있습니다 (order_id 101, 103). 이때 SELECT book_category, price 라고 쿼리를 실행하면, 데이터베이스는 ‘소설’이라는 그룹 이름은 알지만, price 값으로 15000을 보여줘야 할지, 18000을 보여줘야 할지 결정할 수 없습니다. 하나의 그룹은 여러 행을 대표하기 때문입니다.

따라서 SUM(price) (15000 + 18000) 나 COUNT(*) (2개) 처럼 그룹 전체를 대표하는 하나의 값(집계 값)만을 SELECT 절에 사용할 수 있는 것입니다.

3. GROUP BY 실전 활용법 (The ‘Application’)

이제 실제 예제를 통해 GROUP BY를 어떻게 활용하는지 알아보겠습니다.

예제 1: 단일 컬럼으로 그룹화하기

질문: 책 카테고리별 총 판매액과 판매 건수는?

SELECT
    book_category,          -- 카테고리별로 그룹화했으므로 SELECT 가능
    SUM(price) AS total_sales, -- 각 카테고리 그룹의 price 합계
    COUNT(*) AS order_count    -- 각 카테고리 그룹의 행 개수
FROM
    orders
GROUP BY
    book_category;

결과: | book_category | total_sales | order_count | | :--- | :--- | :--- | | 소설 | 33000 | 2 | | IT | 55000 | 2 | | 경제 | 42000 | 2 |

이처럼 GROUP BY를 사용하면 단 몇 줄의 코드로 카테고리별 판매 현황을 명확하게 파악할 수 있습니다.

예제 2: 여러 컬럼으로 그룹화하기

질문: 사용자(user_id)별, 카테고리별 주문 건수는?

두 개 이상의 컬럼으로 그룹화하면 더 세분된 분석이 가능합니다.

SELECT
    user_id,
    book_category,
    COUNT(*) AS order_count
FROM
    orders
GROUP BY
    user_id, book_category -- user_id로 먼저 묶고, 그 안에서 book_category로 다시 묶음
ORDER BY
    user_id; -- 결과를 보기 좋게 정렬

결과: | user_id | book_category | order_count | | :--- | :--- | :--- | | 11 | IT | 2 | | 25 | 소설 | 2 | | 25 | 경제 | 1 | | 42 | 경제 | 1 |

user_idbook_category의 조합이 같은 행들이 하나의 그룹으로 묶여 각 사용자가 어떤 카테고리의 책을 몇 번 주문했는지 상세하게 분석할 수 있습니다.

예제 3: 그룹 결과 필터링하기 - HAVING

질문: 총 판매액이 40,000원 이상인 카테고리만 보고 싶다면?

WHERE 절은 그룹화하기 _전_의 원본 데이터 행을 필터링합니다. 반면, GROUP BY로 집계된 _결과_를 필터링하고 싶을 때는 HAVING 절을 사용해야 합니다.

  • WHERE: From a Big Pile, Take Some. (큰 더미에서 일부를 가져온다 - 그룹화 전)

  • HAVING: From Small Groups, Take Some. (작은 그룹들 중 일부를 가져온다 - 그룹화 후)

SELECT
    book_category,
    SUM(price) AS total_sales
FROM
    orders
GROUP BY
    book_category
HAVING
    SUM(price) >= 40000; -- 그룹별 집계 결과(SUM(price))에 대한 조건

결과: | book_category | total_sales | | :--- | :--- | | IT | 55000 | | 경제 | 42000 |

WHERE 절에 SUM(price) >= 40000 조건을 넣으면 오류가 발생합니다. WHERE 절이 실행되는 시점에는 아직 그룹화가 이루어지지 않아 SUM(price)라는 집계 값을 알 수 없기 때문입니다. HAVING은 이처럼 집계 함수에 대한 조건을 걸 때 반드시 필요합니다.

4. GROUP BY 심화 탐구 (The ‘Deep Dive’)

기본기를 다졌다면, 이제 GROUP BY를 한 단계 더 깊게 이해해 봅시다.

GROUP BY vs DISTINCT

SELECT DISTINCT book_category FROM orders;SELECT book_category FROM orders GROUP BY book_category; 와 결과적으로 동일한 카테고리 목록을 보여줍니다. 둘의 차이는 무엇일까요?

  • DISTINCT: 단순히 중복된 행을 제거하여 유일한 값의 목록을 보여주는 것이 주 목적입니다.

  • GROUP BY: 그룹을 만드는 것이 주 목적이며, 집계 함수를 사용하기 위한 전제 조건입니다.

따라서 단순히 중복 없는 목록만 필요하다면 DISTINCT가 더 직관적이고, 각 목록(그룹)에 대한 추가적인 계산(개수, 합계 등)이 필요하다면 GROUP BY를 사용해야 합니다.

NULL 값의 처리

GROUP BY 절에 사용된 컬럼에 NULL 값이 포함되어 있다면, 데이터베이스는 이 NULL 값들을 모두 하나의 별도 그룹으로 취급합니다.

고급 그룹핑: ROLLUPCUBE

때로는 단순히 그룹별 집계뿐만 아니라, 중간 합계나 총합계를 함께 보고 싶을 때가 있습니다. 이때 ROLLUPCUBE를 사용할 수 있습니다. (모든 데이터베이스 시스템에서 지원하는 기능은 아닙니다.)

  • ROLLUP: 계층적인 소계를 생성합니다. GROUP BY ROLLUP(A, B)(A, B) 그룹별 집계, (A) 그룹별 집계, 그리고 전체 총합계를 모두 보여줍니다.

    • 예: GROUP BY ROLLUP(user_id, book_category) (사용자, 카테고리)별 집계 + 사용자별 소계 + 전체 총계
  • CUBE: ROLLUP보다 한 단계 더 나아가, 명시된 컬럼들의 모든 가능한 조합에 대한 소계를 생성합니다.

    • 예: GROUP BY CUBE(user_id, book_category) (사용자, 카테고리)별 집계 + 사용자별 소계 + 카테고리별 소계 + 전체 총계

이 기능들은 복잡한 다차원 분석이나 보고서 작성 시 매우 유용하게 사용될 수 있습니다.

성능 고려사항

GROUP BY 연산은 데이터베이스에 상당한 부하를 줄 수 있습니다. 내부적으로 데이터베이스는 그룹화를 위해 데이터를 정렬하거나 해시 테이블을 만드는 작업을 수행하기 때문입니다. 데이터 양이 많을 경우, 쿼리 성능을 높이기 위해 다음을 고려해야 합니다.

  1. 인덱스(Index) 활용: GROUP BY에 사용되는 컬럼에 인덱스가 생성되어 있으면, 데이터베이스는 정렬 작업을 생략하고 인덱스를 순차적으로 읽는 것만으로 그룹화를 수행할 수 있어 성능이 크게 향상됩니다.

  2. 불필요한 컬럼 제외: GROUP BYSELECT 절에 꼭 필요하지 않은 컬럼은 포함하지 않아 처리할 데이터 양을 줄이는 것이 좋습니다.

5. 결론: 데이터를 요약하는 기술

지금까지 우리는 SQL GROUP BY의 세계를 깊이 있게 탐험했습니다. GROUP BY는 단순히 데이터를 묶는 기능을 넘어, 방대한 정보의 바다에서 의미 있는 진주를 건져 올리는 낚싯대와 같습니다.

  • **GROUP BY**는 흩어진 데이터를 특정 기준으로 **‘그룹’**으로 묶습니다.

  • **집계 함수 (COUNT, SUM, AVG 등)**는 각 그룹의 특징을 요약하는 **‘숫자’**를 계산합니다.

  • **HAVING**은 요약된 결과 중에서 우리가 원하는 **‘의미 있는 그룹’**만을 걸러냅니다.

이 세 가지 핵심 요소를 자유자재로 조합할 수 있게 되면, 여러분은 비로소 데이터를 ‘본다’에서 ‘이해한다’의 단계로 나아갈 수 있습니다. 처음에는 문법이 낯설고 오류 메시지가 두려울 수 있습니다. 하지만 오늘 배운 개념들을 바탕으로 실제 데이터를 가지고 직접 쿼리를 작성하고 실행하며 연습하는 것이 가장 빠른 성장 비결입니다.

이제 여러분의 손에는 데이터 집계의 마법 지팡이가 쥐어졌습니다. 이 강력한 도구를 사용하여 여러분의 데이터 속에서 놀라운 인사이트를 발견해 내시길 바랍니다!

레퍼런스(References)

GROUP BY