SELECT member_id, COUNT(*) AS 주문횟수
FROM orders
WHERE status = 'paid'
GROUP BY member_id
HAVING COUNT(*) >= 2
ORDER BY 주문횟수 DESC;
SQL을 처음 배울 때 한 번쯤 이런 의문이 생긴다. SELECT가 제일 앞에 있는데 왜 WHERE에서는 SELECT에서 정한 별칭을 못 쓰지? HAVING은 WHERE랑 뭐가 다른 거지? 이 의문들은 SQL이 작성 순서대로 실행되지 않는다는 걸 알면 한 번에 풀린다.
작성할 때는 SELECT가 맨 앞에 오지만, 실제로 실행될 때는 FROM부터 시작한다.
작성 순서 실행 순서
----------- -----------
1. SELECT → 1. FROM
2. FROM → 2. WHERE
3. WHERE → 3. GROUP BY
4. GROUP BY → 4. HAVING
5. HAVING → 5. SELECT
6. ORDER BY → 6. ORDER BY
7. LIMIT → 7. LIMIT
SELECT는 작성할 때 1번이지만 실행은 5번째다. WHERE는 3번째로 작성하지만 2번째로 실행된다. 이 순서 차이가 별칭 오류와 WHERE vs HAVING 혼동의 원인이다.

어느 테이블에서 데이터를 가져올지 결정한다. JOIN이 있다면 이 단계에서 테이블을 합친다. 이후 모든 단계는 여기서 만들어진 데이터를 대상으로 동작한다.
행 단위로 조건을 검사해서 조건에 맞지 않는 행을 제거한다. GROUP BY 이전에 실행되기 때문에 아직 그룹이 없다. 집계 함수(COUNT, SUM 등)는 그룹이 있어야 계산할 수 있으므로 WHERE에서는 쓸 수 없다.
-- 오류: WHERE에서 집계 함수 사용 불가
WHERE COUNT(*) >= 2
WHERE를 통과한 행들을 지정한 컬럼 기준으로 묶는다. 이 단계부터 개별 행이 아니라 그룹 단위로 데이터가 처리된다.
그룹을 대상으로 조건을 검사한다. GROUP BY 이후에 실행되기 때문에 집계 함수를 조건으로 쓸 수 있다.
-- 가능: HAVING에서 집계 함수 사용
HAVING COUNT(*) >= 2
출력할 컬럼을 결정하고 별칭을 적용한다. 실행 순서상 여기서 처음으로 별칭이 만들어지기 때문에, 이전 단계인 WHERE나 HAVING에서는 SELECT의 별칭을 쓸 수 없다.
-- 오류: HAVING에서 SELECT 별칭 사용 불가
SELECT COUNT(*) AS 주문횟수
...
HAVING 주문횟수 >= 2; -- 아직 '주문횟수'라는 별칭이 없다
-- 올바른 방법
HAVING COUNT(*) >= 2;
SELECT 이후에 실행되기 때문에 별칭을 쓸 수 있다. 결과를 정렬한다.
-- 가능: ORDER BY에서 SELECT 별칭 사용
SELECT COUNT(*) AS 주문횟수
...
ORDER BY 주문횟수 DESC;
최종적으로 반환할 행의 수를 제한한다. 모든 처리가 끝난 뒤 마지막에 잘라낸다.
실행 순서를 알면 어디서 별칭을 쓸 수 있는지 바로 판단할 수 있다.
| 절 | SELECT 별칭 사용 가능 여부 |
|---|---|
WHERE | 불가 — SELECT보다 먼저 실행 |
GROUP BY | 불가 — SELECT보다 먼저 실행 |
HAVING | 불가 — SELECT보다 먼저 실행 |
ORDER BY | 가능 — SELECT보다 나중에 실행 |
LIMIT | 가능 — SELECT보다 나중에 실행 |
SQL이 작성 순서와 실행 순서가 다르다는 게 처음엔 헷갈리지만, 한 번 순서를 머릿속에 박아두면 별칭 오류나 WHERE vs HAVING 혼동 같은 실수가 눈에 띄게 줄어든다.