1. 서브쿼리 위치에 따른 SQL 용어

  • 메인 쿼리 (main query) : 가장 바깥쪽 SELECT
  • 서브 쿼리 (subquery) : 쿼리 안의 보조퀄

SELECT (SELECT ... FROM ...)  -- 스칼라 서브쿼리
FROM (SELECT ... FROM ...)  -- 인라인 뷰
WHERE 컬럼명 IN (SELECT ... FROM ...)  -- 중첩 서브쿼리


1-1. 스칼라 서브쿼리 (scala subquery)

  • 메인쿼리의 SELECT 절에 있는 또 다른 SELECT
  • ⭐ 스칼라 서브쿼리는 결과는 단일값이며, 이를 위해 결과들을 하나로 집계해주는 함수(max, min, avg, sum, count 등)이 자주 쓰인다.

스칼라 서브쿼리 문제 상황

각 부서에서 가장 높은 연봉을 받는 직원들의 연봉을 각 직원의 정보와 함께 조회하기 위해 아래의 SQL문을 실행한다고 하자.

SELECT 
    employee_id,
    name,
    salary,
    (SELECT MAX(salary) FROM employees GROUP BY department_id) AS max_salary_in_dept
FROM 
    employees;
  • 스칼라 서브쿼리를 사용하여 부서별 최대 연봉을 조회하려고 하지만,
  • GROUP BY department_id 때문에 스칼라 서브쿼리는 각 부서별 MAX(salary) 값을 반환한다. 부서가 여러 개 이므로 MAX(salary)가 여러 개가 된다.

즉, 이 쿼리의 결과를 예상해본다면 다음과 같다.

employee_id name salary max_salary_in_dept
1000Ariel30005000, 3500 ... ➡️ 부서별 MAX(salary) 값이 전부 들어가게 된다.

스칼라 서브쿼리 해결 방법

  • 스칼라 서브쿼리 대신 서브쿼리를 조인으로 처리한다.
  • 부서별 최대 연봉을 먼저 계산한다.
  • 부서 아이디를 조인키로 각 부서의 최대 연봉&직원 정보를 연결한다.
SELECT 
  e.employee_id,
  e.name,
  e.department_id,
  e.salary,
  d.max_salary
FROM
  employees e
INNER JOIN (
  SELECT department_id, MAX(salary) AS max_salary
  FROM employees
  GROUP BY department_id
) d ON e.department_id = d.department_id
WHERE e.salary = d.max_salary;


1-2. 인라인 뷰 (inline view)

  • 메인쿼리의 FROM절에 있는 또 다른 SELECT
  • FROM절 내부에서 일시적으로 뷰를 생성한다.
  • 인라인 뷰의 결과는 내부적으로 메모리 OR 디스크에 임시 테이블을 생성하여 활용된다. ("뷰"라는 이름이 붙은 이유)
SELECT 학생A.학번, 학생A.이름
FROM (SELECT *
	  FROM 학생
      WHERE 성별 = '남') 학생A;


1-3. 중첩 서브쿼리 (nested subquery)

  • 메인쿼리의 WHERE절에 있는 또 다른 SELECT
  • 비교 연산자(=, <, >, <=, >=, <>, !=), IN, EXISTS, NOT IN, NOT EXISTS 등을 많이 사용한다.
SELECT *
FROM 학생
WHERE 학번 = (SELECT MAX(학번)
			 FROM 학생)


2. 메인쿼리와의 관계성에 따른 SQL 용어

2-1. 비상관 서브쿼리 (non correlated subquery)

  • 메인쿼리와 서브쿼리 간 관계성 無
  • 서브쿼리가 독자적으로 실행된 뒤 메인쿼리에게 결과를 준다.

비상관 서브쿼리에서 실행 순서
서브쿼리 실행 ➡️ 메인쿼리 실행 ➡️ 결과 출력

SELECT *
FROM 학생
WHERE 학번 IN (SELECT 학번
			  FROM 학생
              WEHRE 성별 = '남')
  • 성별 = '남' 조건으로 학생 테이블에서 데이터를 필터링해 가져온 뒤
  • 결과를 메인쿼리의 학생 테이블로 전달하여 최종 데이터를 출력한다.


2-2. 상관 서브쿼리 (correlated subquery)

  • 메인쿼리와 서브쿼리 간 관계성 有
  • 서브쿼리가 수행되기 위해서 메인쿼리의 값을 받아야 한다.
  • 다음의 서브쿼리일 때 상관 서브쿼리가 발생한다.
    • 스칼라 서브쿼리
    • 중첩 서브쿼리

상관 서브쿼리에서 실행 순서
메인쿼리 실행 ➡️ 서브쿼리 실행 ➡️ 메인쿼리 실행 ➡️ 결과 출력

SELECT *
FROM 학생
WHERE 학번 IN (SELECT 학번
			  FROM 지도교수
              WEHRE 성별 = 지도교수.학번 = 학생.학번)
  • 메인쿼리에서 학생 테이블의 학번 결과를 서브쿼리로 전달한다.
  • 서브쿼리에서 지도교수.학번 = 학생.학번 으로 학생 테이블과 지도교수 테이블의 학번을 비교한다.
  • 학생 테이블과 지도교수 테이블의 학번이 같을 때, 서브쿼리의 결과로 도출된다.
  • 서브쿼리의 결과에 대해 메인쿼리를 실행한 뒤, 결과를 출력한다.

cf) 2-1, 2-2 과정에서 'SQL 재작성'이 작동할 수 있다.
- DB 버전 및 옵티마이저에 따라,
- 서브쿼리가 제거되고, 하나의 메인쿼리로 통합되는 "뷰 병합 (view merging)" 발생 가능
- 이 과정을 "SQL 재작성 (rewrite)" 라고 한다.


3. 반환 결과에 따른 SQL 용어

3-1. 단일행 서브쿼리 (single row subquery)

  • 서브쿼리 결과가 1개의 행 (= 스칼라 서브쿼리)
  • 메인쿼리의 WHERE절에서 연산자(<, >, = 등)을 사용해 서브쿼리를 연결한다.
    WHERE 학번 = (SELECT MAX(학번)
    			  FROM 학생)

3-2. 다중행 서브쿼리 (multiple row subquery)

  • 서브쿼리 결과가 여러 개의 행
  • 메인쿼리의 WHERE절에서 IN 구문을 사용해 서브쿼리를 연결한다.
    WHERE 학번 IN (SELECT MAX(학번)
                   FROM 학생
                   GROUP BY 전공코드)

3-3. 다중열 서브쿼리 (multiple column subquery)

  • 서브쿼리 결과가 여러 개의 행 & 여러 개의 열
  • 메인쿼리의 WHERE절에서 IN 구문을 사용해 서브쿼리를 연결한다.
    WHERE (이름, 전공코드) IN (SELECT 이름, 전공코드
                             FROM 학생
                             WHERE 이름 LIKE '김%')


📌 Source

  • 양바른, 「업무에 바로 쓰는 SQL 튜닝」, 한빛미디어, 2021


💡 질문과 피드백은 댓글에 남겨주시기 바랍니다.
❤️ 도움이 되셨다면 공감 부탁드립니다.


profile
Data Analyst / Engineer

0개의 댓글