핵심 흐름

GROUP BY는 행을 먼저 고르고, 그다음 그룹을 만들고, 마지막에 그룹 결과만 남깁니다.

헷갈리는 지점은 대부분 WHERE와 HAVING의 위치, 그리고 집계 함수가 NULL을 어떻게 읽는지에서 나옵니다.

ORDER BY는 마지막 표시 순서만 담당

한 번의 실행 축으로 보면 역할이 분리됩니다.

행 필터 → 그룹화 → 그룹 필터
step 1 원본 행 준비

FROM으로 집계 대상 행을 모읍니다.

예: 주문 12행
step 2 WHERE로 먼저 거르기

그룹화 전에 개별 행을 제외합니다.

예: 취소 주문 제외 → 7행
step 3 GROUP BY로 묶기

같은 값끼리 한 그룹이 되고, NULL도 하나의 그룹 키가 됩니다.

예: 부서별 3그룹
step 4 HAVING + SELECT

집계가 끝난 그룹만 다시 걸러서 결과 행으로 꺼냅니다.

예: COUNT(*) > 1 인 2그룹
WHERE는 아직 그룹이 없을 때 쓰는 조건입니다. 그래서 집계값을 직접 비교하지 않습니다.
HAVING은 그룹이 만들어진 뒤의 조건입니다. 소계, 평균, 건수 같은 집계 결과를 기준으로 고릅니다.

그룹 결과 한 행의 규칙

모호하지 않게 읽기
SELECT의 비집계 컬럼은 GROUP BY에 포함 그룹 결과 한 행이 어떤 값을 대표하는지 분명해야 합니다.
GROUP BY만으로 정렬되지는 않음 표시 순서가 필요하면 마지막에 ORDER BY를 따로 적어야 합니다.
확장 집계는 같은 규칙 위에 붙음 ROLLUP은 소계, CUBE는 모든 조합, GROUPING SETS는 필요한 조합만 계산합니다.
ROLLUP: 소계 + 총계 CUBE: 가능한 조합 전체 GROUPING SETS: 필요한 조합만

집계 함수가 NULL을 읽는 방식

결과 해석의 기준
COUNT(*)

NULL 여부와 상관없이 행 수 전체를 셉니다.

COUNT(col)

해당 컬럼이 NULL인 행은 제외합니다.

SUM / AVG

NULL을 건너뜁니다. 전부 NULL이면 결과는 0이 아니라 NULL입니다.

MAX / MIN

NULL을 무시한 뒤 남은 값끼리 비교합니다.