1. 개요
SQL 을 처음 배울 때는 SELECT ... FROM ... WHERE ... 순서로 문장을 읽고 쓴다. 그런데 이 순서가 실제 실행 순서라고 생각하면 여기저기서 막힌다. WHERE 절에서 SELECT 에서 만든 별칭을 썼다가 “column does not exist” 에러를 본 적이 있을 것이다. 나도 처음엔 이유를 몰라서 그냥 서브쿼리로 감싸 우회했다. 원인은 하나다. SQL 문은 쓰는 순서와 DB 가 처리하는 논리적 순서가 다르다.
2. 핵심 내용
2-1. 쓰는 순서와 논리적 실행 순서
작성 순서는 이렇다.
SELECT column
FROM table
WHERE condition
GROUP BY column
HAVING condition
ORDER BY column;하지만 DB 가 이 문장을 해석하는 논리적 순서는 다음과 같다.
FROM(+JOIN) — 대상 테이블을 모으고 조인한다.WHERE— 행 단위로 걸러낸다.GROUP BY— 남은 행을 그룹으로 묶는다.HAVING— 그룹 단위로 걸러낸다.SELECT— 최종적으로 보여줄 컬럼을 계산한다.ORDER BY— 정렬한다.
여기서 “논리적” 이라는 말이 중요하다. 실제 DB 엔진이 물리적으로 이 순서 그대로 데이터를 처리하는 건 아니다. 옵티마이저는 결과가 같다면 얼마든지 순서를 재배치하고 인덱스를 골라 더 빠른 경로로 실행한다. 다만 우리가 쿼리의 의미를 해석할 때는 이 논리적 순서를 기준으로 생각해야 한다.
2-2. WHERE 에서 SELECT 별칭을 못 쓰는 이유
이제 이유가 보인다.
SELECT price * quantity AS total_price
FROM orders
WHERE total_price > 10000; -- 에러total_price 라는 별칭은 5번 단계인 SELECT 에서 만들어진다. 그런데 WHERE 는 2번 단계, SELECT 보다 먼저 실행된다. WHERE 가 실행되는 시점에는 아직 total_price 라는 이름이 존재하지 않는다. 그래서 원래 표현식을 그대로 다시 써야 한다.
SELECT price * quantity AS total_price
FROM orders
WHERE price * quantity > 10000; -- 정상반대로 ORDER BY 는 6번 단계, SELECT 보다 뒤에 실행되기 때문에 별칭을 그대로 쓸 수 있다.
SELECT price * quantity AS total_price
FROM orders
ORDER BY total_price DESC; -- 정상같은 이유로 GROUP BY 도 별칭 허용 여부가 DB 마다 갈린다(PostgreSQL, MySQL 은 허용, 표준을 엄격히 따르면 비허용). WHERE 와 HAVING 은 명확히 안 된다는 것만 기억해도 대부분의 혼란은 없어진다.
2-3. WHERE 와 HAVING 이 갈리는 지점
WHERE 는 GROUP BY 이전에 실행되므로 그룹 집계 함수(COUNT, SUM 등)를 조건으로 쓸 수 없다. 그룹이 아직 만들어지지 않았기 때문이다. 그룹 결과를 걸러내려면 HAVING 을 써야 한다.
SELECT department, COUNT(*) AS cnt
FROM employees
GROUP BY department
HAVING COUNT(*) > 10; -- 그룹별 인원수로 필터링WHERE 로 행을 먼저 최대한 줄이고, HAVING 은 그룹 집계 결과에만 쓰는 게 성능에도 유리하다. WHERE 조건을 HAVING 으로 옮기면 불필요하게 많은 행을 그룹으로 묶은 다음에야 걸러내게 된다.
3. 마무리
요약
- SQL 은 쓰는 순서(SELECT
ORDER BY)와 논리적 실행 순서(FROMORDER BY)가 다르다.- WHERE 는 SELECT 보다 먼저 실행되므로 SELECT 의 별칭을 참조할 수 없다.
- ORDER BY 는 SELECT 보다 나중에 실행되므로 별칭을 쓸 수 있다.
- WHERE 는 행 필터링, HAVING 은 그룹 필터링이라 역할이 다르다.
- 실제 물리적 실행 계획은 옵티마이저가 재배치하지만, 쿼리의 의미는 논리적 순서로 읽어야 한다.
다음은 인덱스는 왜 빠른가 이다.