본문 바로가기
Infra & Security Eng/Database Engineering

그룹 함수와 GROUP BY 개념, 사용하는 이유, 종류, 문제

by 엔지니어 E 2026. 2. 5.
반응형

그룹 함수 개념과 사용하는 이유

그룹 함수(Group Functions)는 여러 개의 데이터(행)를 뭉쳐서 단 하나의 결과값으로 요약해 주는 함수

- 그룹 함수에서 NULL은 무시된다
- GROUP BY 절 없이 일반 컴럼과 같이 기술 될 수 없음 

 

 

그룹 함수를 이용한 검색

실습하기 

월간 지급 급여총액과 년간 지급 급여총액을 검색 한다
SQL> SELECT AVG(sal) 평균급여, TO_CHAR(AVG(sal),'$999,999') 평균급여
  2    FROM emp;

* '999.999' ---> 숫자가 아닌 문자로 나오는 것이다

평균급여 평균급여_포맷
---------- ------------------
3167.57143    $3,168

1. 데이터 준비 (FROM): 일단 emp 테이블에서 급여(sal) 데이터를 다 긁어모은다
2. 그룹화 및 계산 (AVG): 긁어모은 수많은 줄(다중행)을 뭉쳐서 평균값 딱 1줄로 만든다 - 이것이 그룹 함수의 핵심 역할
3. 가공 (TO_CHAR): 계산된 숫자를 그대로 보여줄지, $ 기호를 붙여서 예쁘게 보여줄지 결정한다
4. 출력 (SELECT): 최종적으로 '평균급여'라는 이름을 달아서 우리 눈에 보이게 표로 만들어준다


* AVG - 그룹 함수 

 

그룹 함수와 NULL

사원에게 지급된 보너스 지급 총액과 보너스 평균을 검색 한다
SELECT 
    SUM(comm) 총액, 
    TO_CHAR(AVG(comm), '$999,999') 보너스_평균
FROM emp;

  총액     보너스_평균
---------- ------------------
     14780      $869

[3순위] 최종 결과를 선택하여 출력
SELECT 
    /* [2-1] 여러 행의 comm을 더해 하나의 숫자로 합친다 (그룹 함수)
    SUM(comm) 총액, 
    
    /* [2-2] 여러 행의 comm 평균을 낸 후, '$9,999' 문자로 변환한다 (그룹 함수 + 변환) */
    TO_CHAR(AVG(comm), '$999,999') 보너스_평균

/* [1순위] 가장 먼저 emp 테이블에서 데이터를 모두 가져온다 */
FROM emp;

* DB에 저장된 NULL은 무결성에 문제를 유발한다 

 

그룹 함수와 다중 행 연산자

10번 부서원들보다 급여가 높은 사원을 검색한다
SELECT eno 사번, ename 이름, dno 부서번호
FROM EMP
WHERE sal > (SELECT MAX(sal)
              FROM emp
              WHERE dno = '10');

사번     이름                           부서
-------- ------------------------------ ----
0001     안영희                         01

/* [4순위] 최종적으로 조건에 맞는 행의 컬럼들만 화면에 출력한다. */
SELECT eno 사번, ename 이름, dno 부서번호

/* [3순위] emp 테이블의 전체 데이터를 다시 훑으면서, 2단계에서 구한 '최댓값'보다 sal이 큰 행만 골라낸다. */
FROM EMP
  WHERE sal > (

      /* [2순위] 1단계에서 가져온 10번 부서원들의 급여(sal) 중 가장 높은 '최댓값' 하나를 뽑아낸다. (그룹 함수 MAX) */
      SELECT MAX(sal)

      /* [1순위] 가장 먼저 실행: emp 테이블에서 부서번호(dno)가 '10'인 사원들의 데이터를 먼저 가져온다. */
      FROM emp
      WHERE dno = '10'
    );

 

GROUP BY 개념과 기능

SELECT ...
FROM  
테이블 ...
WHERE 조건 ...
GROUP BY 컬럼
ORDER BY 정렬_대상 ... ;

1. 기능: 소그룹 통계 정보 검색
역할: 특정 컬럼에서 값이 같은 데이터들을 하나의 덩어리(소그룹)로 묶는다
* GROUP BY dno 실행 (덩어리로 묶기)
GROUP BY dno를 실행하면 컴퓨터는 메모리에서 다음과 같이 값이 같은 것끼리 모은다

10번 덩어리: {김철수(300), 박민수(500)} → 하나의 그룹
20번 덩어리: {이영희(400), 최보안(600)} → 하나의 그룹
결과: 묶인 그룹별로 합계(SUM), 평균(AVG), 개수(COUNT) 등의 통계치를 계산해서 보여준다

2. 카디널리티(Cardinality) 일치와 에러 방지
가장 중요한 규칙이며, 이를 어기면 ORA-00937 에러가 발생 한다

문제 상황: SELECT 절에 '부서명(여러 행)'과 '평균급여(그룹 함수 결과, 1행)'를 같이 적으면, 양쪽의 출력 줄 수(카디널리티)가 맞지 않는다
해결 방법: SELECT 절에 사용된 모든 일반 컬럼을 반드시 GROUP BY 절에 기술 한다
효과: GROUP BY에 적어준 컬럼을 기준으로 행들이 압축되면서, 일반 컬럼과 그룹 함수의 출력 줄 수(카디널리티)가 똑같이 1줄로 일치하게 된다

 

GROUP BY 절을 이용한 검색

업무별 평균 급여, 평균 연봉을 검색 한다 
SELECT job 업무, 
       TO_CHAR(AVG(sal), '999,999') 평균_급여,
       TO_CHAR(AVG(sal*12+NVL(comm,0)), '999,999') 평균_연봉
FROM emp
GROUP BY job;

/* [4순위] 그룹화가 끝난 데이터 중 필요한 항목을 화면에 출력한다. */
SELECT 
    /* [3-1] 업무명을 출력한다. (GROUP BY에 기술했으므로 출력 가능) */
    job 업무, 
    
    /* [3-2] 그룹 함수 계산: 뭉쳐진 업무별로 급여(sal)의 평균을 구하고 문자로 변환한다. */
    TO_CHAR(AVG(sal),'999,999') 평균_급여,
    
    /* [3-3] 그룹 함수 계산: 뭉쳐진 업무별로 (급여*12 + 보너스)의 평균을 구한다  
       - NVL(comm, 0): 보너스가 NULL이면 0으로 치환하여 계산에서 빠지지 않게 한다 */
    TO_CHAR(AVG(sal*12+NVL(comm,0)), '999,999') 평균_연봉

/* [1순위] 데이터를 읽을 대상 테이블(emp)을 지정하고 메모리에 올린다. */
FROM emp

/* [2순위] 업무(job) 컬럼의 값이 같은 행들을 찾아 하나의 덩어리(소그룹)로 뭉쳐진다. 
   - 이 과정에서 여러 줄이었던 데이터가 업무 종류별로 압축된다
   - 예: 'MANAGER'가 3명이면 1개의 'MANAGER 그룹'으로 묶인다. */
GROUP BY job;

 

 

카디널리티와 정렬

- 값의 개수(기수)를 의미 한다 
- SELECT 문의 각 컬럼은 반드시 카디널리티가 같아야 한다 
- 그룹 함수와 같이 검색되는 모든 컬럼은 반드시 GROUP BY절에 기술한다 

* 카디널리티가 일치하는 것이 중요하다 
* 정렬된 결과를 위해 반드시 ORDER BY절을 이용 한다

 

GROUP BY절을 이용한 검색 

1. 각 부서별 급여 평균이 가장 높은 값과 낮은 값을 검색 한다
SQL> SELECT dno 부서번호,
2        MAX(AVG(sal)) 최대평균, MIN(AVG(sal)) 최소평균
3        FROM emp
4        GROUP BY dno;

* Group BY 절에 dno를 기술하더라도 Max(sal) 은 하나이고 dno(부서번호) 여러개가 나오기 때문에 카디널리티가 일치하지 않아 출력되지 않는다

올바른 예시
SELECT MAX(AVG(sal)) 최대평균, MIN(AVG(sal)) 최소평균
FROM emp
GROUP BY dno;

2. 각 부서별 최소 급여를 받는 사원의 정보를 검색 한다 
SELECT d.dno, d.dname, e.eno, e.ename, e.sal
FROM emp e, dept d
WHERE d.dno = e.dno
AND (e.dno, e.sal) IN (SELECT dno, MIN(sal)
                              FROM emp
                              GROUP BY dno)
ORDER BY d.dno;

SELECT d.dno, d.dname, e.eno, e.ename, e.sal  -- [5] 최종적으로 테이블에 출력되는 사번, 이름, 급여
FROM emp e, dept d                            -- [2] 사원(e)과 부서(d) 테이블을 준비
WHERE d.dno = e.dno                           -- [3] 조인: 사원의 부서번호와 부서 테이블 번호를 일치시킴
  AND (e.dno, e.sal) IN (                     -- [4] 다중 컬럼 비교: (부서, 급여)가 아래 명단에 있는지 확인
      SELECT dno, MIN(sal)                    -- [1-2] 각 그룹 내에서 가장 낮은 급여를 추출
      FROM emp                                -- [1-0] 서브쿼리용 emp 테이블 로드
      GROUP BY dno                            -- [1-1] 부서별로 행들을 덩어리(소그룹)로 묶음
  )
ORDER BY d.dno;                               -- [6] 모든 결과를 부서번호(dno) 기준으로 정렬

 

실습 문제

6. 학과별 기말고사 평균을 성적순으로 검색한다.
SELECT student.major 학과, AVG(score.result) 기말_평균 -- [4] student 테이블의 학과와 score 테이블의 평균 점수를 출력
FROM student, score                                    -- [1] 학생 테이블과 성적 테이블 전체를 불러옴
WHERE student.sno = score.sno                          -- [2] 조인: 두 테이블에 공통으로 존재하는 '학번'을 기준으로 연결함
GROUP BY student.major                                 -- [3] 그룹화: student 테이블의 학과명이 같은 데이터끼리 덩어리로 묶음
ORDER BY 기말_평균 DESC;                                -- [5] 정렬: 계산된 평균값이 높은 학과부터 보여줌

학과                            기말_평균
------------------------------ ----------
식영                           69.9906367
유공                           69.5586592
화학                            69.444664
물리                           68.9195171
생물                           67.9259962

* 각 학생의 기말고사 평균을 물어보는게 아니라 학과별(그룹) 평균을 물어보는 것이므로 흩어져 있는 학생들을 '학과'라는 이름표를 기준으로 한데 모으기 위해 GROUP BY가 반드시 필요하다 

7. 30번 부서의 업무별 연봉의 평균을 검색한다.
SELECT emp.job 업무, 
       TO_CHAR(AVG(emp.sal), '999,999.99') 연봉_평균 -- [4] 평균을 구하고(AVG), 소수점 두 자리 양식(TO_CHAR)으로 변환함
FROM emp                                             -- [1] 사원(emp) 테이블의 데이터를 메모리에 로드함
WHERE emp.dno = '30'                                 -- [2] 필터링: 전체 사원 중 30번 부서에 속한 사원만 골라냄
GROUP BY emp.job;                                    -- [3] 그룹화: 30번 부서 내에서 같은 '업무(job)'끼리 덩어리로 뭉침
* 각 사원의 연봉을 물어본게 아니라 업무별 연봉 평균을 물어본 것이므로 GROUP BY를 쓴다 

8. 물리학과 학생 중에 학년별로 성적이 가장 우수한 학생의 평점을 검색 한다 
SELECT student.syear 학년,  MAX(student.avr) 최고_평점  -- [4] 학년 그룹 안에서 가장 높은 평점(MAX) 하나만 골라냄
FROM student                       -- [1] 학생(student) 테이블의 데이터를 메모리에 로드함
WHERE student.major = '물리'       -- [2] 필터링: 모든 학과 중 '물리학과' 학생들만 먼저 골라냄
GROUP BY student.syear;            -- [3] 그룹화: 물리학과 학생들을 1학년, 2학년, 3학년, 4학년별로 각각 뭉침