ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Database] SELECT 문 실행 순서
    업무 자동화/Database 2025. 11. 16. 19:15

    SELECT 문 실행 순서 정리

    FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY

    1. SELECT 문 기본 구조와 실행 순서

    1-1. SELECT 문 기본 형태

    • 관계형 데이터베이스에서 가장 많이 사용하는 조회 쿼리
    • 여러 절(Clause)이 조합되어
      어떤 행을, 어떻게 묶고, 무엇을, 어떤 순서로 보여줄지 결정
    SELECT [DISTINCT] 컬럼/표현식
    FROM   테이블/뷰
    [WHERE 조건]
    [GROUP BY 그룹기준]
    [HAVING 그룹조건]
    [ORDER BY 정렬기준]
    [LIMIT 행수 [OFFSET 시작행]]
    • 작성 순서
      SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT


    1-2. 논리적 실행 순서

    실제로 DB가 해석하는 논리적 실행 순서

    1) FROM : 어떤 테이블/뷰에서 데이터를 읽을지 결정 (JOIN 포함)
    2) WHERE : 행 단위 필터링
    3) GROUP BY : 행들을 그룹으로 묶기
    4) 집계 함수 : SUM, COUNT, AVG 등 그룹별 집계 계산
    5) HAVING : 그룹 단위 필터링
    6) SELECT : 최종으로 보여줄 컬럼·표현식 선택, 별칭 생성
    7) DISTINCT : 필요하다면 중복 행 제거
    8) ORDER BY : 결과 정렬
    9) LIMIT : 최종 행 수 제한, OFFSET으로 시작 위치 이동

     

    2. 절(Clause)별 역할과 특징

    2-1. FROM 절: 기준 데이터 + JOIN

    특징

    • 어떤 테이블 / 뷰 / 인라인 뷰에서 데이터를 읽을지 결정
    • 여러 테이블을 JOIN 할 경우, 이 단계에서 관계(ON 조건)까지 함께 처리
    SELECT *
    FROM orders o
    JOIN customer c
      ON o.customer_id = c.customer_id;
    • 분석의 대상이 되는 원본 데이터를 정하는 단계


    2-2. WHERE 절: 행(ROW) 필터링

    특징

    • FROM 결과에서 조건을 만족하는 행만 남기는 단계
    • 아직 GROUP BY/집계 이전이므로 행 단위 조건만 가능
    SELECT *
    FROM orders
    WHERE status = 'PAID'
      AND order_date >= DATE '2025-01-01';

    주의

    • SUM(amount) 같은 집계 함수 결과는 아직 존재하지 않기 때문에 WHERE에서 사용할 수 없습니다.


    2-3. GROUP BY 절: 그룹 묶기

    특징

    • WHERE를 통과한 행들을 특정 컬럼 기준으로 그룹화
    • 이후 집계 함수(SUM, COUNT, AVG …)가 그룹마다 계산됨
    SELECT customer_id,
           SUM(amount) AS total_amount
    FROM orders
    WHERE status = 'PAID'
    GROUP BY customer_id;
    • GROUP BY 이후부터는 그룹당 한 행 관점


    2-4. HAVING 절: 그룹 필터링

    특징

    • GROUP BY와 집계 함수 계산이 끝난 그룹 결과에 조건을 거는 단계
    • 집계 함수 사용 가능
    -- 결제 금액 합계가 10만 이상인 고객만
    SELECT customer_id,
           SUM(amount) AS total_amount
    FROM orders
    WHERE status = 'PAID'
    GROUP BY customer_id
    HAVING SUM(amount) >= 100000;
    • 집계 결과(total_amount 등)를 기준으로 필터링할 때는 WHERE가 아니라 HAVING 또는 서브쿼리에서 처리합니다.


    2-5. SELECT 절: 최종 컬럼 선택·별칭 생성

    특징

    • 보여줄 컬럼/표현식을 선택하고, AS 별칭을 부여하는 단계
    • DISTINCT가 있다면 이 단계 결과에서 중복을 제거
    SELECT customer_id,
           COUNT(*)      AS order_cnt,
           SUM(amount)   AS total_amount
    FROM orders
    WHERE status = 'PAID'
    GROUP BY customer_id;

    주의

    • 실행 순서상 SELECT는 HAVING 뒤이므로
      • HAVING에서 SELECT 별칭을 쓰는 것은 표준 SQL 기준으로는 불가(일부 DB는 허용)
      • WHERE에서는 SELECT 별칭 사용 불가


    2-6. ORDER BY / LIMIT 절: 정렬·행 제한

    특징

    • SELECT 결과를 정렬하고, 일부만 잘라서 보여주는 단계
    • SELECT에서 정의한 별칭 또는 컬럼 번호를 사용할 수 있음
    SELECT customer_id,
           SUM(amount) AS total_amount
    FROM orders
    WHERE status = 'PAID'
    GROUP BY customer_id
    HAVING SUM(amount) >= 100000
    ORDER BY total_amount DESC
    LIMIT 10 OFFSET 0;
    • ORDER BY는 마지막 단계이기 때문에 ORDER BY total_amount DESC처럼 별칭 사용

     

    3. 실행 순서 때문에 생기는 대표적인 차이

    3-1. WHERE에서 집계 함수를 쓸 수 없는 이유

    -- 잘못된 예시
    SELECT customer_id, SUM(amount)
    FROM orders
    WHERE SUM(amount) >= 100000
    GROUP BY customer_id;

    이유

    • 논리적으로 WHERE가 GROUP BY / 집계보다 먼저 실행되기 때문에 이 시점에는 SUM(amount) 값이 존재하지 않습니다.

    해결 1) HAVING 사용

    SELECT customer_id,
           SUM(amount) AS total_amount
    FROM orders
    GROUP BY customer_id
    HAVING SUM(amount) >= 100000;

    해결 2) 인라인 뷰/서브쿼리 사용

    SELECT *
    FROM (
        SELECT customer_id,
               SUM(amount) AS total_amount
        FROM orders
        GROUP BY customer_id
    ) t
    WHERE t.total_amount >= 100000;


    3-2. SELECT 별칭을 WHERE에서 못 쓰는 이유

    -- 잘못된 예시
    SELECT amount * 1.1 AS amount_vat
    FROM orders
    WHERE amount_vat >= 100000;

    이유

    • SELECT가 WHERE보다 나중에 실행되므로
      WHERE 시점에는 amount_vat 별칭이 아직 없다.

    해결 1) 표현식을 그대로 사용

    SELECT amount * 1.1 AS amount_vat
    FROM orders
    WHERE amount * 1.1 >= 100000;

    해결 2) 인라인 뷰로 한 번 감싸기

    SELECT *
    FROM (
        SELECT amount * 1.1 AS amount_vat
        FROM orders
    ) t
    WHERE t.amount_vat >= 100000;
    • ORDER BY에서는 amount_vat 사용 가능
      (ORDER BY amount_vat DESC)

     

    4. 서브쿼리 없는 경우 vs 있는 경우

    4-1. 서브쿼리 없이 단일 SELECT 흐름

    SELECT customer_id,
           COUNT(*)    AS order_cnt,
           SUM(amount) AS total_amount
    FROM orders
    WHERE status = 'PAID'
    GROUP BY customer_id
    HAVING SUM(amount) >= 100000
    ORDER BY total_amount DESC;

    실행 흐름 요약

    1) FROM orders
    2) WHERE status = 'PAID'
    3) GROUP BY customer_id
    4) 집계(COUNT, SUM)
    5) HAVING SUM(amount) >= 100000
    6) SELECT 컬럼·별칭 구성
    7) ORDER BY total_amount DESC


    4-2. FROM 절 인라인 뷰(서브쿼리 포함)

    특징

    • 복잡한 집계/필터를 먼저 처리하고, 그 결과를 테이블처럼 JOIN/조회하고 싶을 때
    -- 부서별 평균 급여를 먼저 계산 후, 평균보다 높은 직원만 조회
    SELECT e.emp_id,
           e.emp_name,
           e.salary,
           d.dept_avg_salary
    FROM emp e
    JOIN (
        SELECT dept_id,
               AVG(salary) AS dept_avg_salary
        FROM emp
        GROUP BY dept_id
    ) d
      ON e.dept_id = d.dept_id
    WHERE e.salary > d.dept_avg_salary;

    장점

    • 복잡한 로직을 블록으로 분리하여 가독성과 재사용성이 좋음
    • 집계 후 JOIN으로 바깥 쿼리는 비교만 수행하므로, 상관 서브쿼리보다 성능이 유리한 경우가 많음


    4-3. WHERE 절 서브쿼리 (IN / EXISTS)

    특징

    • 다른 테이블의 결과를 조건으로만 사용하고 싶을 때
    -- 주문이 한 번이라도 있는 고객만
    SELECT *
    FROM customer c
    WHERE c.customer_id IN (
        SELECT DISTINCT customer_id
        FROM orders
    );
    -- EXISTS 버전 (행 존재 여부 확인)
    SELECT *
    FROM customer c
    WHERE EXISTS (
        SELECT 1
        FROM orders o
        WHERE o.customer_id = c.customer_id
    );

    해석

    • 안쪽 서브쿼리가 먼저 실행되어 집합/존재 여부를 만들고, 바깥 WHERE 절에서 그 결과를 이용해 행을 필터링합니다.


    4-4. SELECT 절 스칼라 서브쿼리

    특징

    • 행마다 계산한 값을 컬럼처럼 붙이고 싶을 때
    -- 각 고객별 주문 개수를 함께 표시
    SELECT c.customer_id,
           c.customer_name,
           (
               SELECT COUNT(*)
               FROM orders o
               WHERE o.customer_id = c.customer_id
           ) AS order_cnt
    FROM customer c;

    해석

    • FROM customer로 고객 행을 읽고, 각 행마다 안쪽 서브쿼리를 실행해 order_cnt를 계산한 후,
      SELECT 단계에서 결과를 한 줄로 합칩니다.
Designed by Tistory.