[MySQL] 그룹화하여 데이터 조회하기
🎯 목표
GROUP BY 절에 대해 알아보고 그룹 함수를 사용하여 데이터를 추출해본다.
1. GROUP BY 절
특정 컬럼을 기준으로 그룹화를 하면 집계함수의 활용범위를 넓힐 수 있다. 그룹화 없이 집계함수를 사용한다면 집계 대상이 전체 데이터였다.(WHERE 절에 조건식이 존재한다면 WHERE절로 필터링된 후의 데이터) 그룹화를 하면 학년별 평균 점수, 연령대별 평균 몸무게 등을 계산할 수 있다.
(1) 기본 구조
SELECT 컬럼1, ...
FROM 테이블
GROUP BY 컬럼1
쿼리를 작성할 때, 여러 절을 사용하게 되는데 배치해야하는 순서는 다음과 같다.
SELECT - FROM - WHERE - GROUP BY - HAVING - ORDER BY - LIMIT
(2) 예제로 이해하기

위 이미지(Fig 1)를 보면 Products 테이블에서 카테고리를 기준으로 그룹화했을 때 중복된 항목이 하나의 값으로 대표되는 것을 볼 수 있다. GROUP BY를 지정하면 DISTINCT를 했을 때처럼 중복이 제거되는 효과가 있다.
🤔 집계되지 않은 컬럼 불러오기
만약, 집계함수를 사용하지 않으면 어떤 데이터가 불러와질까? 라는 의문이 생겨서 직접 MySQL Workbench를 이용해서 테이블을 만들고, 실험해 보기로 했다.
cf. 테이블을 생성할 때 작성한 쿼리는 하단에 위치한 댓글창에 작성해 두었다.

SELECT *
FROM test.Products
GROUP BY category
테이블을 먼저 생성하고 위 쿼리를 실행했을 때 ‘Error Code: 1055. Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column ‘test.Products.Product_name’ which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by’ 라는 에러 메시지가 떴다. 에러 메시지를 해석해보면 그룹화되지 않은 또는 집계함수가 사용되지 않은 컬럼을 불러올 수 없다는 것이다.
프로그래머스에서 MySQL 문제를 풀면 그룹화되지 않고(집계처리도 안된) 컬럼을 출력했을 때, 첫 번째 행이 대표로 출력되는 것을 확인했었다. 만약 집계처리되지 않은 컬럼도 출력하고 싶을 땐 sql_mode가 only_full_group_by 모드로 설정되어있는 것을 해제해주면 된다. 쿼리 앞에 SET sql_mode = '';
를 추가하면 그룹화된 컬럼만 출력해야하는 모드가 해제된다.

위 이미지(Fig 3)을 보면 그룹화되지 않은(또는 집계처리되지 않은) 컬럼은 그룹화된 컬럼 기준 첫 번째 행이 대표로 불러와진다.
2. 그룹 함수
MySQL에서 사용하는 대표적인 그룹 함수는 다음과 같다. 각 그룹 함수에 대해 위에서 생성한 테이블(Products)에 적용해 보았다.
(1) COUNT
COUNT 함수는 특정 필드에 대한 데이터의 건수를 출력한다. 전체 컬럼을 의미하는 *를 입력한다면 전체 행의 수를 출력하고, 특정 컬럼명을 입력하면 NULL값을 제외한 데이터의 수를 출력한다.
SELECT category, COUNT(price) AS cnt
FROM test.Products
GROUP BY category

(2) SUM
SUM 함수는 특정 필드에 대한 합을 출력한다. 전체 컬럼을 의미하는 *를 입력하면 에러사인이 뜬다. 만약 SUM 함수에 숫자가 아닌 자료형을 사용하면 값이 0으로 출력된다.
SELECT category, SUM(price) AS total_price
FROM test.Products
GROUP BY category

(3) AVG
AVG 함수는 특정 필드에 대한 평균값을 출력한다. SUM 함수와 동일하게 숫자가 아닌 자료형을 사용하면 값이 0으로 출력된다.
cf. SUM/COUNT 값과 동일한 값을 출력한다.
SELECT category, AVG(price) AS avg_price
FROM test.Products
GROUP BY category

(4) MIN, MAX
MIN, MAX 함수는 특정 필드의 최솟값과 최댓값을 출력한다. 이때 문자열이 담긴 필드를 함수에 입력하면 정의된 정렬 순서를 기준으로 값이 출력된다.
- 한글: 자음 < 모음 < 한글 (한글이 가장 큰 값)
- 영문: 대문자 < 소문자 (소문자가 가장 큰 값)
SELECT category, MIN(price) AS min_price, MAX(price) AS max_price
FROM test.Products
GROUP BY category

🤔 문자열의 최솟값과 최댓값
SELECT category, MIN(product_name), MAX(product_name)
FROM test.Products
GROUP BY category

(5) GROUP_CONCAT
GROUP_CONCAT 함수는 그룹화된 컬럼을 기준으로 특정 필드값의 요소를 한 줄로 표현할 수 있다. 이는 그룹화 후 문자열을 합칠 때 사용된다.
SELECT category, GROUP_CONCAT(product_name) AS product_list
FROM test.Products
GROUP BY category

GROUP_CONCAT은 따로 구분자를 지정하지 않으면 ‘,’를 기준으로 문자열이 합쳐진다. 구분자를 지정하는 방법은 필드명 뒤에 SEPARATOR '구분자'
를 추가하면 된다. 구분자를 공백으로 바꾸려면 아래와 같이 쿼리를 작성하면 된다.
SELECT category, GROUP_CONCAT(product_name SEPARATOR ' ') AS product_list
FROM test.Products
GROUP BY category

3. Reference
👩🏻💻개인 공부 기록용 블로그입니다
오류나 틀린 부분이 있을 경우 댓글 혹은 메일로 따끔하게 지적해주시면 감사하겠습니다.
댓글남기기