2. 핵심 원리
2.1 집계 함수 6종
| 함수 | 의미 | NULL 처리 |
|---|---|---|
COUNT(*) |
행 수(모든 행) | NULL 포함해서 셈 |
COUNT(컬럼) |
그 컬럼이 NULL 아닌 행 수 | NULL 인 행은 제외 |
COUNT(DISTINCT 컬럼) |
중복을 뺀 값의 개수 | NULL 인 행은 제외 |
SUM·AVG |
합계·평균 | NULL 인 행은 계산에서 제외 |
MIN·MAX |
최솟값·최댓값 | NULL 인 행은 계산에서 제외 |
COUNT(DISTINCT 컬럼) 은 그룹 안에서 서로 다른 값이 몇 종류인지 셀 때 씁니다. 예를 들어 직원별 주문 건수(COUNT(*))와 그 직원이 판매한 서로 다른 고객 수(COUNT(DISTINCT cust))는 다른 질문입니다.
2.2 평가 순서: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
SQL 문은 위에서 아래로 읽히지만, 실제 처리 순서는 다음과 같습니다.
| 순서 | 절 | 하는 일 |
|---|---|---|
| 1 | FROM |
테이블을 읽고 조인한다 |
| 2 | WHERE |
그룹으로 묶기 전에 개별 행을 거른다 |
| 3 | GROUP BY |
남은 행을 기준 컬럼으로 그룹 짓는다 |
| 4 | HAVING |
만들어진 그룹을 집계 값 기준으로 거른다 |
| 5 | SELECT |
출력할 컬럼·별칭을 계산한다 |
| 6 | ORDER BY |
최종 결과를 정렬한다 |
핵심
WHERE는 그룹이 만들어지기 전에 개별 행 단위로 거르므로 집계 함수를 쓸 수 없습니다.HAVING은 그룹이 만들어진 뒤에 그룹 단위로 거르므로 집계 함수를 써야 의미가 있습니다. "WHERE는 행,HAVING은 그룹" 이 기준입니다.
WHERE 로 걸러낸 행은 애초에 그룹 집계에 들어가지 않고, HAVING 으로 걸러낸 그룹은 집계까지 계산된 뒤 결과에서만 빠집니다. 같은 조건이라도 어느 단계에서 거르느냐에 따라 집계 값 자체가 달라질 수 있어, 3절에서 두 절을 각각·함께 쓴 결과를 비교합니다.
2.3 SELECT 의 비집계 컬럼은 GROUP BY 에 있어야 한다
GROUP BY dept 로 묶은 쿼리에서 SELECT id, dept, COUNT(*) 처럼 그룹 기준에 없는 id 를 그대로 쓰면 안 됩니다. 한 그룹 안에는 여러 id 값이 섞여 있어 어느 것을 보여줘야 할지 정해지지 않기 때문입니다.
주의Oracle 은
ORA-00979, MSSQL 은 오류 8120 으로 이 문장을 바로 거부합니다. MySQL 은 5.7.5 부터ONLY_FULL_GROUP_BY가 기본으로 켜져 있어 역시 오류가 나지만, 이 설정을 끈 예전 환경에서는 오류 없이 그룹 안의 임의의 행 값을 돌려주는 위험한 동작을 합니다.
2.4 GROUP BY 없는 HAVING, 그리고 빈 테이블
GROUP BY 없이 HAVING 만 쓰면 테이블 전체가 하나의 그룹이 됩니다. 조건을 만족하면 집계 결과 1행, 만족하지 않으면 0행이 나옵니다.
빈 테이블(행이 0개)의 집계는 GROUP BY 유무에 따라 결과 행 수가 달라집니다. GROUP BY 없이 COUNT(*)·SUM 을 조회하면 "그룹 1개(전체), 그 그룹에 행이 0개"로 취급되어 COUNT(*) 는 0, SUM 은 NULL 인 1행이 나옵니다. GROUP BY 가 있으면 애초에 나눌 행이 없으므로 0행입니다.
주의빈 테이블에서
GROUP BY없는SELECT COUNT(*) FROM t는 항상 1행(0)을 돌려주지만,GROUP BY 컬럼이 붙으면 0행입니다. "집계 쿼리는 결과가 항상 1행 이상"이라고 가정하고 코드를 짜면,GROUP BY가 섞인 리포트에서 결과가 없는 경우를 놓치기 쉽습니다.