-
[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 단계에서 결과를 한 줄로 합칩니다.
'업무 자동화 > Database' 카테고리의 다른 글
[Database] RDB vs NoSQL - 1 (0) 2025.12.09 [Database] SQL WHERE 구문 정리: 정의와 활용 예시 (0) 2025.11.18 [Database] SQL 서브쿼리(Subquery) 정리: 개념, 위치별 활용 (0) 2025.11.15 [Database] SQL JOIN 총정리: INNER/OUTER/SEMI/ANTI/CROSS, NATURAL/USING, UNION (0) 2025.11.14 [Database] SQL 카테시안 곱: 원인, 증상, 재현 예시, 예방 체크리스트 (0) 2025.11.13