SQL

SQL: 실행 순서

dewstream 2025. 10. 24. 08:00
728x90

※ SQL: Order of Execution.

 

안녕하세요. 듀스트림입니다.

 

오늘은 초급 개발자분들이 보시면 많은 도움이 될 내용인 SQL 실행 순서에 대한 내용입니다.


1. 우리가 코드로 작성할 때 사용하는 문법적 순서

SELECT → FROM →JOIN→ON→WHERE → GROUP BY → HAVING → ORDER BY → LIMIT

2. 실행 순서 (논리적 평가 순서)

FROM → ON → JOIN → WHERE → GROUP BY → HAVING → SELECT → (WINDOW) → DISTINCT → ORDER BY → LIMIT

※ LATERAL은 FROM 평가 중에 실행됩니다.

3. 물리적 실행 순서(실행계획)

옵티마이저가 실제로 선택한 방식(NL/Hash/Sort 등).
→ 물리적 실행은 최적화를 위해 순서를 바꾸거나 병합할 수 있지만, 논리적 의미는 동일하게 보장됩니다.

4. SQL의 실행 순서 (논리적 평가 순서)

단계 절/개념 동작 주의
1 FROM 테이블(서브쿼리 포함)들을 가져오고 결합할 준비 이 시점에 LATERAL이 작동 (왼쪽→오른쪽)
2 ON 조인 조건 평가 → 매칭될 행 결정 LEFT JOIN에서 오른쪽 조건은 여기 넣어야 외부행 유지
3 JOIN 결과 생성 INNER/LEFT/RIGHT 등 조인 유형 반영 이 순간 하나의 "행 스트림"이 됨
4 WHERE 행 필터링(참/거짓) LEFT JOIN 후 WHERE right_col IS NOT NULL = 사실상 INNER화
5 GROUP BY 그룹 키로 묶음 묶인 뒤엔 각 그룹당 1행
6 HAVING 그룹 단위 필터링 WHERE는 행, HAVING은 그룹 대상
7 SELECT 표현식 계산, 별칭 생성 이때 윈도우 함수 계산 지점은 아래 참고
8 WINDOW 함수 … OVER (…) 계산 FROM/WHERE/GROUP BY/HAVING 뒤, DISTINCT/ORDER BY 전
9 DISTINCT 중복 제거 SELECT 결과에 적용 (윈도우 결과도 포함된 후 적용)
10 ORDER BY 결과 정렬 윈도우의 ORDER BY와는 전혀 다른 개념
11 LIMIT/OFFSET 결과 행 수 제한 최종 잘라내기 단계

5. FROM 단계에서의 LATERAL

LATERAL은 FROM 절 내부에서만 의미가 있고, 왼쪽 테이블의 각 행을 오른쪽 서브쿼리가 참조할 수 있게 합니다.
평가 흐름은 다음과 같습니다.

  1. 왼쪽(outer) 테이블에서 한 행을 꺼냄
  2. 그 행의 값을 들고 오른쪽(LATERAL) 서브쿼리를 실행
  3. 결과를 조인 규칙대로 합침
  4. 다음 왼쪽 행으로 반복

즉, FROM 안에서의 평가 순서는 "왼쪽 → 오른쪽"이며, LATERAL이 있으면 오른쪽이 왼쪽에 의존합니다.


LATERAL 조합별 의미

  • CROSS JOIN LATERAL (sub) = JOIN LATERAL (sub) ON TRUE
    INNER 계열: 오른쪽 결과가 없으면 그 왼쪽 행은 빠짐
  • LEFT JOIN LATERAL (sub) ON …
    LEFT OUTER 계열: 오른쪽이 없어도 왼쪽 행은 유지(오른쪽은 NULL)

6. ON vs WHERE (특히 LEFT JOIN에서의 함정)

같은 조건이라도 어디에 쓰느냐에 따라 결과가 달라집니다.

-- A. LEFT JOIN의 "오른쪽 필터"를 ON에 넣은 경우 (왼쪽 유지)
FROM a
LEFT JOIN b ON b.id = a.id AND b.score > 90


-- B. WHERE에 넣은 경우 (사실상 INNER로 바뀌기 쉬움)
FROM a
LEFT JOIN b ON b.id = a.id
WHERE b.score > 90
  • A: b.score > 90 매칭에 실패해도 a행은 살아있고, b 컬럼은 NULL처리
  • B: WHERE에서 b.score > 90을 검사하는 순간, NULL은 탈락 → 사실상 INNER JOIN처럼 작동
규칙: "조인 매칭 논리"는 ON에서, "최종 결과 필터링"은 WHERE에서.
특히, LEFT JOIN에서는 오른쪽 제약 조건을 ON에 두는 습관이 안전합니다. (그래야 왼쪽 행이 유지됩니다.)

7. 윈도우 함수의 평가 시점 (OVER 절)

  • 순서: FROM/ON/WHERE → GROUP BY → HAVING → (여기서) WINDOW(SELECT 계산 중) → DISTINCT → ORDER BY → LIMIT
  • 윈도우의 ORDER BY는 윈도우 프레임 내에서의 순서이지, 결과 전체의 정렬이 아님 → 결과를 진짜로 정렬하려면 "바깥 ORDER BY"가 필요합니다.
SELECT emp_id,
       SUM(sales) OVER (PARTITION BY dept ORDER BY day) AS running_sum
FROM t
ORDER BY dept, day;  -- ← 이게 있어야 화면상 정렬

8. DISTINCT, ORDER BY, LIMIT의 상호작용

  • DISTINCT는 SELECT 결과에 적용됩니다(윈도우 계산까지 끝난 뒤).
  • ORDER BY는 DISTINCT 이후에 적용됩니다.
  • LIMIT/OFFSET은 가장 마지막에 적용(정렬/중복제거 다 끝난 후 자름).
패턴상 "최신 1건" 같은 문제는 서브쿼리 내부에서 ORDER BY … LIMIT 1로 먼저 줄이고, 바깥에서 조인/집계하여 의도한 결과를 만듭니다. (바깥에서 ORDER BY … LIMIT만 쓰면 전역 상위 N건이 됩니다.)

9. 서브쿼리 vs 상관(연관) 서브쿼리 vs LATERAL

  • 일반 서브쿼리: 바깥과 독립적으로 평가(바깥 컬럼 참조 불가)
  • 상관 서브쿼리: WHERE/SELECT 등에서 바깥 컬럼을 참조, 보통 바깥 행마다 실행 (SubPlan)
  • LATERAL: FROM 절에서 바깥 컬럼을 참조하며 테이블(다중 컬럼·다중 행)을 반환 가능
LATERAL = FROM에서 쓰는 "행별 연관 서브쿼리" (바깥 행을 하나씩 받아 내부를 실행 → 조인으로 붙임)

10. CTE(WITH)의 평가와 최적화 포인트

  • 논리적으로는 WITH이 먼저 평가된 뒤 메인 쿼리에서 참조
  • PostgreSQL 12+: 기본적으로 인라인 최적화 가능(실행 전 병합) → 반드시 "먼저 만들어 둔다"는 보장은 없음
  • MATERIALIZED 키워드로 고정(materialize) 시킬 수 있고, 반대로 NOT MATERIALIZED로 인라인 힌트 가능
    → 성능/의도에 맞게 선택

+ TIP

  • LATERAL
    • FROM 내부에서 왼쪽 먼저, 오른쪽(LATERAL)은 왼쪽 행을 참조해 실행
  • ON vs WHERE
    • 조인 매칭/오른쪽 조건은 ON
    • 최종 결과 필터는 WHERE
    • LEFT JOIN 뒤 WHERE right_col IS NOT NULL = INNER화 주의
  • 윈도우 함수
    • FROM/WHERE/GROUP BY/HAVING 후, DISTINCT/ORDER BY/LIMIT 전
  • DISTINCT/ORDER BY/LIMIT
    • DISTINCT → ORDER BY → LIMIT 순으로 적용
  • 서브쿼리
    • 독립 서브쿼리: 바깥 참조 X
    • 상관 서브쿼리: 바깥 참조 O (행별 실행)
    • LATERAL: FROM에서 바깥 참조 O + 표 형태 반환
  • CTE
    • 논리상 WITH이 먼저지만, 최신 PG는 인라인 최적화로 실제 물리 실행은 달라질 수 있음

 

++ AND는 OR보다 높은 연산자 우선순위(precedence)를 가집니다.

-- 괄호가 없을 경우 이 구문은
WHERE a = 1 OR b = 2 AND c = 3
↓
-- 자동으로 이렇게 해석됨
WHERE a = 1 OR (b = 2 AND c = 3)
-- 논리적으로 b = 2 AND c = 3을 먼저 평가하고 그 결과를 a = 1 OR (...결과)와 비교



-- OR을 먼저 적용하려면 괄호를 반드시 사용
WHERE (a = 1 OR b = 2) AND c = 3
-- 이 경우에는 a = 2 OR b = 2를 먼저 평가하고 그 결과를 AND c = 3과 결합
-- 논리적으로 완전히 다릅니다.

 

+++ 논리 연산 우선순위

우선순위 연산자 의미
1 NOT 부정
2 AND 논리곱
3 OR 논리합

오늘은 여기까지~

 

728x90

'SQL' 카테고리의 다른 글

SQL: 행 스트림  (0) 2025.11.17
SQL: 상관관계(상관 서브쿼리)  (0) 2025.10.27
SQL: EXISTS / NOT EXISTS  (0) 2025.10.15
SQL: ASC, DESC  (0) 2025.10.13
SQL: ROLLUP  (0) 2025.09.29