테마
SELECT 작성과 처리 순서
SELECT 문은 적힌 순서(SELECT → FROM → WHERE …)와 의미상 처리되는 순서가 다르다. 이 순서를 알면 "WHERE에서 별칭(alias)을 왜 못 쓰지?", "GROUP BY 뒤에 왜 HAVING이 따로 있지?" 같은 의문이 한 번에 풀린다. 이 문서는 그 순서와 함께, 결과를 조용히 틀리게 하거나 인덱스를 못 쓰게 만드는 비교 조건을 다룬다.
논리적 처리 순서
논리적 처리 순서(logical processing order)는 "각 절이 어떤 결과를 입력으로 받는가"를 정한 약속이다. 실제 실행은 옵티마이저(optimizer)가 인덱스 순서를 이용하거나 LIMIT에서 일찍 멈추는 식으로 바꿀 수 있지만, 결과는 항상 이 순서대로 처리한 것과 같아야 한다.
다이어그램 로딩…
이 순서에서 바로 나오는 규칙이 몇 가지 있다.
- WHERE에서는 SELECT 별칭을 쓸 수 없다. WHERE를 처리할 때는 SELECT 목록의 값이 아직 계산되지 않았기 때문이다. MySQL은 GROUP BY·HAVING·ORDER BY에서는 별칭을 허용한다. 이때 별칭을
'a'처럼 작은따옴표로 감싸면 안 된다. 칼럼이 아니라 문자열 상수로 읽혀 엉뚱하게 묶이기 때문이다. 따옴표가 필요하면 백틱(backtick,`)을 쓴다. - 집계 결과로 거르려면 HAVING을 쓴다.
COUNT(*) > 10같은 조건은 묶은 뒤에야 값이 생기므로 WHERE에 둘 수 없다. 반대로 묶기 전에 거를 수 있는 조건을 HAVING에 두면, 버릴 행까지 다 묶은 뒤에 버리게 되어 일만 늘어난다. - 윈도우 함수(window function)는 WHERE에 쓸 수 없다. 윈도우 함수는 WHERE·GROUP BY·HAVING이 끝난 뒤 계산되고, SELECT 목록과 ORDER BY에만 올 수 있다. 순위로 거르려면 한 번 감싼 바깥 쿼리에서 거른다(서브쿼리·CTE·윈도우 함수 3).
비교 조건의 함정
1. 타입이 다른 비교 — 문자열 칼럼과 숫자. MySQL은 타입이 다른 두 값을 비교할 때 한쪽을 바꾸는 타입 변환(type conversion)을 한다. 문자열과 숫자를 비교하면 둘 다 부동 소수점(floating point) 수로 바꿔 비교한다. 그래서 문자열 칼럼 code에 인덱스가 있어도 WHERE code = 1은 그 인덱스로 값을 바로 찾지 못한다. '1', ' 1', '1a'처럼 숫자 1로 바뀌는 문자열이 여러 가지라 인덱스의 한 지점을 정할 수 없기 때문이다. 결과도 틀어질 수 있다. 숫자로 바꿀 수 없는 문자열은 0이 되므로, WHERE code = 0은 'apple' 같은 행을 전부 돌려준다. 상수는 칼럼 타입에 맞춰 WHERE code = '1'로 쓴다.
2. NULL 비교 — = NULL은 참이 아니다. NULL은 "모르는 값"이라 NULL과의 비교 결과는 참도 거짓도 아닌 NULL이다. WHERE는 참인 행만 남기므로 WHERE col = NULL은 한 행도 돌려주지 않는다. NULL인지 확인할 때는 IS NULL·IS NOT NULL을 쓴다. 두 값이 모두 NULL일 때도 같다고 보려면 NULL 안전 비교 연산자(NULL-safe equal operator) <=>를 쓴다. 더 흔한 함정은 부정 조건이다. WHERE status <> 'DONE'은 status가 NULL인 행도 빼 버린다. NULL 행이 필요하면 OR status IS NULL을 붙인다.
3. 칼럼을 가공한 비교 — 인덱스가 있어도 못 쓴다.B-Tree 인덱스는 칼럼의 원래 값 순서로 정렬돼 있다. 그래서 조건에서 칼럼에 함수나 연산을 씌우면 비교할 값이 인덱스에 없는 값이 되어, 인덱스 레인지 스캔(index range scan)으로 범위를 좁히지 못하고 행마다 계산해 봐야 한다.
| 인덱스를 못 쓰는 형태 | 인덱스를 쓰는 형태로 바꾸기 |
|---|---|
WHERE DATE(created_at) = '2026-09-25' | WHERE created_at >= '2026-09-25' AND created_at < '2026-09-26' |
WHERE price * 1.1 > 1100 | WHERE price > 1000 |
WHERE name LIKE '%kim' | 같은 뜻으로는 못 바꾼다. 앞부분이 고정된 LIKE 'kim%'만 범위 탐색 가능 |
칼럼 쪽을 그대로 두고 상수 쪽을 계산하는 것이 원칙이다. 가공한 값으로 꼭 찾아야 하면 칼럼 대신 그 식의 결과로 인덱스를 만드는 함수 키 파트(functional key part)를 쓸 수 있다(특수 인덱스). 인덱스를 어떤 순서로 타는지는 B-Tree 활용에서 다룬다.
이어지는 내용은 SELECT 작성과 처리 순서 2에서 다룬다. DISTINCT·COUNT·LIMIT을 쓸 때의 함정과, 서버가 SQL을 얼마나 엄격하게 검사할지 정하는 SQL 모드(SQL mode), 이름의 대소문자·예약어 규칙을 본다.
면접 포인트
- "SELECT 처리 순서는?" → FROM·JOIN → WHERE → GROUP BY → HAVING → SELECT(윈도우 함수 포함) → DISTINCT → ORDER BY → LIMIT. 그래서 WHERE에 별칭·집계·윈도우 함수를 못 쓴다.
- "인덱스가 있는데 안 타요" → 칼럼 가공, 문자열 칼럼과 숫자 비교, 앞이
%인 LIKE부터 의심한다. 칼럼은 그대로 두고 상수를 바꾼다. - "
<> 'X'결과에 NULL 행이 없어요" → NULL 비교는 NULL이라 WHERE에서 빠진다.IS NULL을 따로 붙인다.
