많은 SQL 초보자들이 SELECT로 쿼리를 시작하지만, 실제 실행 순서는 전혀 다릅니다. 이 글에서는 SELECT 쿼리가 내부적으로 어떤 순서로 처리되는지 살펴보겠습니다.
어제 윈도우 함수에 대한 설명을 준비하다가 "윈도우 함수 결과를 WHERE나 HAVING에서 필터링할 수 있을까?"라는 질문에 직면했습니다. 결론적으로 윈도우 함수는 WHERE와 GROUP BY 이후에 실행되므로 불가능하다는 것을 알게 되었습니다. 이 과정에서 SQL 쿼리의 실제 실행 순서에 대한 깊은 이해가 필요함을 느꼈습니다.
SQL 쿼리의 논리적 처리 순서
수많은 SQL 쿼리를 작성했지만, 정작 이 순서를 명확히 설명하지 못한다는 사실에 충격을 받았습니다. 다음은 표준 SQL의 논리적 처리 순서입니다:
- FROM/JOIN - 테이블 결합 및 ON 조건 처리
- WHERE - 행 단위 필터링
- GROUP BY - 그룹화
- HAVING - 그룹 조건 필터링
- SELECT - 컬럼 선택 및 윈도우 함수 실행
- ORDER BY - 정렬
- LIMIT - 결과 제한
이 순서는 SQL 쿼리의 의미론적 이해를 돕습니다. 다음과 같은 질문에 답할 수 있습니다:
- GROUP BY 결과를 WHERE에서 사용할 수 있나? (아니요! WHERE가 GROUP BY보다 먼저 실행됩니다)
- 윈도우 함수 결과로 필터링할 수 있나? (아니요! 윈도우 함수는 SELECT 단계에서 실행되며, WHERE와 GROUP BY 이후입니다)
- GROUP BY 컬럼으로 ORDER BY 할 수 있나? (네! ORDER BY는 마지막 단계이므로 가능합니다)
- LIMIT은 언제 적용되나? (가장 마지막에 적용됩니다)
컬럼 별칭의 함정
일부 SQL 구현에서는 GROUP BY에서 SELECT의 별칭을 참조할 수 있습니다:
SELECT CONCAT(first_name, ' ', last_name) AS full_name, COUNT(*)
FROM users
GROUP BY full_name
이 구문이 GROUP BY가 SELECT 이후에 실행되는 것처럼 보이지만, 실제로는 데이터베이스 엔진이 내부적으로 다음과 같이 재작성합니다:
SELECT CONCAT(first_name, ' ', last_name) AS full_name, COUNT(*)
FROM users
GROUP BY CONCAT(first_name, ' ', last_name)
따라서 GROUP BY는 여전히 SELECT보다 먼저 실행됩니다. 데이터베이스는 실행 계획을 수립하기 전에 전체 쿼리를 검증합니다.
실제 실행 계획과의 차이
현실의 데이터베이스 엔진은 성능 최적화를 위해 이 순서를 따르지 않을 수 있습니다. 예를 들어:
SELECT *
FROM owners
LEFT JOIN cats ON owners.id = cats.owner_id
WHERE cats.name = 'mr darcy'
이 쿼리에서 LEFT JOIN 전체를 수행하는 대신, 먼저 'mr darcy'라는 고양이만 필터링하는 것이 효율적입니다. 결과가 변하지 않는다면 데이터베이스는 자유롭게 실행 순서를 변경합니다.
다른 언어에서의 유사 패턴
C#의 LINQ는 FROM → WHERE → SELECT 순서를 따릅니다:
var results = from student in studentList
where student.Age > 12 && student.Age < 20
select student;
Python pandas에서도 유사한 패턴이 자주 사용됩니다:
df = data.join(other_data) # JOIN
df = df[df.created_at > 1000] # WHERE
df = df.groupby('category').agg({'value': 'sum'}) # GROUP BY
df = df[df.value > 10] # HAVING
df = df[['category', 'value']] # SELECT
df = df.sort_values('value', ascending=False)[:30] # ORDER BY + LIMIT
R의 dplyr도 비슷한 논리적 순서를 제공합니다.
이 순서를 이해하면 SQL 쿼리가 왜 특정 방식으로 동작하는지 직관적으로 파악할 수 있습니다. WHERE와 HAVING의 차이, 윈도우 함수의 제약 조건, GROUP BY와 SELECT의 관계 등이 명확해집니다.