[SQL] GROUP BY에 여러 개의 컬럼을 지정한다면? w.집계 함수

jul ee·2025년 4월 5일

데이터 성장기

목록 보기
19/139

프로그래머스에서 문제를 풀다가

GROUP BY 와 관련해 정리하고 넘어가면 좋을 부분을 발견해서 공유하려고 한다.


해당 문제를 통해서
다음의 궁금증을 해결할 수 있었다.

💡  GROUP BY 에 여러 개의 컬럼을 지정할 수 있을까?
      가능하다면 어떻게 동작하게 될까?

💡  GROUP BY 를 왜 집계 함수와 함께 사용하는 것일까?

💡  GROUP BY 를 사용할 때 SELECT 절에 아무 컬럼이나 지정할 수 없는 이유는 무엇일까?

자세한 조건과 데이터 테이블은 링크를 참고하기 바란다 :)
[프로그래머스] 대여 횟수가 많은 자동차들의 월별 대여 횟수 구하기

최종 쿼리는 글 하단에 작성해두었다.



[ 대여 횟수가 많은 자동차들의 월별 대여 횟수 구하기 ]

CAR_RENTAL_COMPANY_RENTAL_HISTORY 테이블에서
대여 시작일을 기준으로 2022년 8월부터 2022년 10월까지 총 대여 횟수가 5회 이상인 자동차들에 대해서
해당 기간 동안의 월별 자동차 ID별 총 대여 횟수(컬럼명: RECORDS) 리스트를 출력하는 SQL문을 작성해주세요.
결과는 월을 기준으로 오름차순 정렬하고, 월이 같다면 자동차 ID를 기준으로 내림차순 정렬해주세요.
특정 월의 총 대여 횟수가 0인 경우에는 결과에서 제외해주세요.

일단

대여 시작일을 기준으로 2022년 8월부터 2022년 10월까지 총 대여 횟수가 5회 이상인 자동차들을 추출한 후,
해당 자동차에 대해서 메인 쿼리가 동작해야 하기 때문에

서브쿼리로 해당하는 자동차들을 먼저 추출해 보겠다.

SELECT CAR_ID
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
WHERE START_DATE BETWEEN '2022-08-01' AND '2022-10-31'
GROUP BY CAR_ID
HAVING COUNT(*) >= 5

작성한 서브쿼리의 동작을 생각해 보자.

각 자동차를 식별할 수 있는 컬럼은 CAR_ID 이다.

  1.  CAR_RENTAL_COMPANY_RENTAL_HISTORY 테이블에서

  2.  대여 시작일(START_DATE)이 2022년 8월부터 2022년 10월 사이에 해당하는(BETWEEN ~ AND ~)

  3.  자동차(CAR_ID)를 그룹화(GROUP BY)하였다.

  4.  그리고 그룹화한 각각의 자동차(CAR_ID) 중에서
     총 대여 횟수(COUNT(*))가 5회 이상인

  5.  CAR_ID 를 조회(SELECT)하게 된다.

작성한 서브쿼리를 실행하면

CAR_ID 컬럼에서 대여 시작일을 기준으로 2022년 8월부터 2022년 10월까지 총 대여 횟수가 5회 이상인 CAR_ID 목록을 확인할 수 있다.



이제 메인쿼리를 작성해 보자.

SELECT MONTH(START_DATE) AS MONTH,
       CAR_ID,
       COUNT(*) AS RECORDS
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
WHERE CAR_ID IN (
                 ... 작성한 서브 쿼리 ...
                 )
      AND START_DATE BETWEEN '2022-08-01' AND '2022-10-31'
GROUP BY MONTH(START_DATE),
         CAR_ID
ORDER BY MONTH(START_DATE),
         CAR_ID DESC
  1.  마찬가지로 CAR_RENTAL_COMPANY_RENTAL_HISTORY 테이블에서

  2.  WHERE 절에 작성해야 할 메인쿼리의 조건은 아래와 같다.

          2-1.  서브쿼리에서 추출한 자동차 목록에 해당해야 하고
                   ->   WHERE  CAR_ID  IN 서브쿼리
         AND
          2-2.  대여 시작일이 해당 기간 동안(BETWEEN ~ AND ~)에 속해야 한다.

                     🧐  해당 조건은 서브쿼리에도 등장했는데 왜 두 번이나 사용하는 걸까?
                               두 조건의 역할이 다르기 때문이다.
                          ✓  서브쿼리 ->  5회 이상 대여한 CAR_ID 를 선별하기 위해
                          ✓  메인쿼리 ->  선별된 차들의 8~10월 대여 기록을 카운팅하기 위해

     3.  월별(GROUP BY), 자동차 ID별(GROUP BY)로 그룹화하여

     4.  MONTH(START_DATE), CAR_ID, 총 대여 횟수(COUNT(*))를 조회한다.

     5.  결과는 월을 기준으로 오름차순 정렬하고,
          월이 같다면 자동차 ID를 기준으로 내림차순 정렬(ORDER BY)

  1. 특정 월의 총 대여 횟수가 0인 경우에는 결과에서 제외한다.

메인쿼리의 흐름은 이렇다.



여기서 궁금증이 생겼다.

문제를 해결하기 위해 월별, 자동차 ID별로 그룹화를 해야 한다는 건 알겠는데
그룹화의 결과가 어떤 흐름으로 출력되는 거지?

즉, GROUP BY 에 여러 개의 컬럼을 지정하면 어떻게 동작하는 것인지 이해가 잘 되지 않아 예제를 통해 비교하면서 정리해 보았다.


🖇  GROUP BY에 컬럼 여러 개 지정하기

일단, GROUP BY 에 한 개의 컬럼을 지정하는 경우부터 살펴보자.

SELECT CAR_ID,
       COUNT(*)
FROM 테이블
GROUP BY CAR_ID

"자동차 ID별" 로 그룹화하기 때문에, 각 자동차별로 총 몇 번 대여되었는지의 횟수를 카운트하게 된다.


이제 문제에서 작성한
GROUP BY 에 두 개의 컬럼을 지정하는 경우를 살펴보자.

SELECT MONTH(START_DATE) AS MONTH,
       CAR_ID,
       COUNT(*)
FROM 테이블
GROUP BY MONTH(START_DATE),
         CAR_ID

해당 쿼리의의 경우에는
“월별 + 자동차 ID별” 로 그룹화를 하는데, 각 컬럼의 조합이 같은 행끼리 묶인다.

즉, MONTH + CAR_ID 가 일치하는 레코드끼리 그룹으로 묶이게 된다.

    예를 들어 생각해 보면

    MONTH 가 8이고, CARD_ID 가 1인 행끼리는 하나의 그룹으로 묶이게 되는 것이다.
    따라서 이 그룹에서 COUNT(*) 를 하게 되면
    8월에 1번 자동차가 총 몇 번 대여되었는지의 횟수를 카운트하게 된다.

이렇게 MONTHCAR_ID 를 그룹화함으로써

문제에서 요구하는 월별, 자동차별 총 대여 횟수를 구할 수 있다.


컬럼이 2개 이상 오는 경우에도 마찬가지다.

GROUP BY 컬럼1, 컬럼2, 컬럼3, ...

컬럼1 + 컬럼2 + 컬럼3 + ... 의 조합이 완전히 같은 행끼리 그룹으로 묶인다.

 
💡  즉, GROUP BY 에 여러 개의 컬럼 지정하면

각 컬럼의 조합이 같은 행끼리 그룹으로 묶이게 되고
결과값은 GROUP BY 절에 기술된 컬럼 항목들의 행의 개수에 의해 결정된다.





🖇  GROUP BY를 집계 함수와 함께 사용하는 이유

일단 짚고 넘어가야 하는 부분은

    집계 함수 없이 GROUP BY 를 사용해도 에러가 발생하는 것은 아니라는 것이다.

그럼에도 GROUP BY 를 집계 함수와 함께 사용하는 이유를 살펴보자.


GROUP BY의 핵심은 "동일한 값들끼리 그룹으로 묶는 것" 이다.

그리고 그 그룹들에 대해 요약(집계)된 정보를 알고 싶을 때 집계 함수가 등장한다.


집계 함수 없이 사용하는 예제 함께 살펴보면
GROUP BY 와 집계 함수를 함께 사용해야 하는 이유가 와닿을 것이다.

-- 집계 함수 없이 GROUP BY 만 쓰는 경우

SELECT MONTH(START_DATE) AS MONTH,
       CAR_ID
FROM 테이블
GROUP BY MONTH(START_DATE),
         CAR_ID

이렇게 사용하면
(MONTH, CAR_ID) 조합에 대해 임의의 한 행의 값이 반환될 수 있지만
이는 DBMS마다 동작이 다르고 어떤 행의 값이 선택될지 예측할 수 없다.


해당 예제에서뿐만 아니라,

만약 그룹화를 했는데 집계하지 않은 컬럼을 SELECT 절에 그냥 지정하면
SQL에서는 "이 그룹 안에서 어느 값을 가져올지 모르겠는데?" 라며
어떤 값을 보여줘야 할지 판단할 수 없기 때문에 문제가 발생하게 된다.

즉, 예측이 불가능하다는 것이다.
그룹화는 되지만, 각 그룹에서 어떤 행을 보여줄지 알 수 없어 비결정적이게 된다.

그래서 실제 값으로 의미 있는 요약을 하려면 반드시 집계 함수를 써야 한다.


    또, 이렇게까지 생각하지 않아도

    만약 MONTH가 8 이고, CAR_ID 가 1인 행들을 그룹화하고 나서
    집계 함수를 사용하지 않는다면

    에러가 발생하지는 않지만 기껏 그룹화를 한 의미가 없다는 것도 생각해볼 수 있다 😊


다시 위로 올라가서

GROUP BY 와 집계 함수를 함께 사용한 경우를 살펴보면

"차량별로 월별 대여 횟수"를 정확히 집계하는
(MONTH, CAR_ID) 조합별로 정확한 요약값이 나온다는 것을 확인할 수 있다.


💡  GROUP BY

집계 함수 없이도 사용할 수는 있지만,
집계 함수와 함께 사용할 때 의미가 크다는 것을 기억하면 된다 :)

[SQL] 데이터 그룹화하기: GROUP BY  해당 글에서
GROUP BY 를 사용할 때 꼭 알아야 할 규칙을 정리해 뒀으니 읽어보는 것도 좋겠다 :)





🖇  GROUP BY 를 사용할 때 SELECT 절에 아무 컬럼이나 지정할 수 없다

해당 주제는 앞에서 다룬 내용과 겹치는 부분이 있지만
SQL의 논리적 실행 순서와 관련된 중요한 개념이기 때문에 정리해 보았다.

GROUP BY 는 행들을 그룹으로 묶기 위한 문법이다.

그리고 SELECT 절에서는 "그룹마다 하나씩의" 결과를 보여주어야 한다.

따라서 SELECT 절에서는 집계 함수 또는 그룹 기준 컬럼만 사용 가능하다.

     SELECT 절에

     집계함수를 쓰는 건 OK
     → 그룹을 요약하는 것이니까!

     그룹 기준이 아닌 컬럼,
     즉 GROUP BY 에 지정되지 않은 컬럼을 쓰는 건 X
     → 그 컬럼의 값을 대표 하나만 보여줄 수 없기 때문이다.


     아래가 그 예시이다.
SELECT MONTH(START_DATE) AS MONTH,
	   CAR_ID
       COUNT(*)
FROM 테이블
GROUP BY MONTH(START_DATE)

    MONTH 로 그룹화를 했는데
    CAR_ID 은 집계함수가 아니고, GROUP BY 에도 없다.

    이때 CAR_ID 도 함께 조회(SELECT)하려고 하면 SQL은 이렇게 혼란스러워한다:

         “ MONTH 에는 각각의 월 데이터가 있는데,
           이 각각의 월에 해당하는 CAR_ID 는 어떤 자동차의 것을 보여줘야 하지? ”

    그래서 SQL은 CAR_ID 에 대해 정의할 수 없음으로 판단하고 오류를 발생시킨다.


💡  GROUP BY 를 사용하면

SELECT 절에서 자유롭게 아무 컬럼이나 가져올 수 없다는 것을 명심하면 된다:)






지금까지
GROUP BY 를 사용할 때 지켜야 할 사항을 알아보았고,

이에 유의하며 쿼리를 작성하여 문제에서 요구한 조건을 모두 만족시키는 결과를 출력할 수 있었다.

아래는 작성한 최종 쿼리이다.

SELECT MONTH(START_DATE) AS MONTH,
       CAR_ID,
       COUNT(*) AS RECORDS
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
WHERE CAR_ID IN (
                 SELECT CAR_ID
                 FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
                 WHERE START_DATE BETWEEN '2022-08-01' AND '2022-10-31'
                 GROUP BY CAR_ID
                 HAVING COUNT(*) >= 5
                 )
      AND START_DATE BETWEEN '2022-08-01' AND '2022-10-31'
GROUP BY MONTH(START_DATE),
         CAR_ID
ORDER BY MONTH(START_DATE), CAR_ID DESC

단일 테이블에서 데이터를 추출하기 때문에 크게 복잡한 문제는 아니었지만,

SQL에서 GROUP BY 를 사용할 때의 주의사항을 통해
GROUP BY 의 역할과 존재 이유를 다시 한 번 짚고 넘어가면서

데이터로 의미 있는 결과를 뽑아내기 위한 인사이트를 얻을 수 있었다 :)


해당 문제를 이렇게 해결했지만

혹시 더 효율적으로 풀 수 있는 방법이 있다면 언제든 편하게 알려주길 바란다 😊

profile
AI에 관심을 가지고, 데이터로 가치를 만들어 나가는 과정을 기록합니다.

0개의 댓글