본문 바로가기

Data/SQL

SQL 고급 : 서브쿼리(Sub-query)

1. 서브쿼리

1) 개요

  • 하나의 SQL문 안에 포함되어 있는 또 다른 SQL문
  • 메인쿼리 기준으로 여러 개의 서브 쿼리 사용 가능
  • 서브쿼리는 하나의 SELECT 문장으로, 괄호로 둘러쌓인 형태

2) 분류(메인쿼리와 연관성 여부)

  • Un-Correlated(비연관) 서브쿼리
  • Correlated(연관) 서브쿼리

3) 종류

  • 스칼라 서브쿼리
  • 인라인 뷰
  • 중첩 서브쿼리(Nested Subquery)

4) 사용 시 주의사항

  • 서브쿼리 사용 시 괄호로 감싸서 사용
  • 서브쿼리는 단일 행(Single Row) 또는 복수 행(Multiple Row) 비교 연산자와 함께 사용 가능
  • 단일 행 비교 연산자는 서브쿼리의 결과가 반드시 1건 이하이여야 하고 복수 행 비교 연산자는 서브쿼리의 결과 건수와 상관 없음
  • 서브쿼리에서는 ORDER BY를 사용하지 못함

2. 스칼라 서브쿼리(SELECT 절)

1) 개요

  • 메인쿼리의 SELECT 절에 위치한 서브 쿼리
  • SELECT 절에서 마치 하나의 컬럼이나 표현식 처럼 사용함
  • 서브쿼리에 별칭(Alias)를 주는 것이 일반적(하나의 컬럼 역할을 하기 때문)
  • 한 행, 한 컬럼(1 Row, 1 Column)만을 반환
  • 서브쿼리 내에서 메인쿼리와 조인 가능
  • 성능 상 좋지 않기 때문에 과도한 사용은 자제

2) 예시

  • 부서명 가져오기
SELECT a.employee_id, a.first_name || a.last_name employee_name
      ,a.department_id
      ,(SELECT b.department_name
        FROM departments b
        WHERE a.department_id = b.department_id) department_name
FROM employees a
ORDER BY 1;

 

  • 오류 구문
SELECT a.employee_id, a.first_name || a.last_name employee_name
      ,a.department_id
      ,(SELECT **b.department_name, b.location_id**
        FROM departments b
        WHERE a.department_id = b.department_id) department_name
FROM employees a
ORDER BY 1;

-- 2개의 컬럼 값을 가져오므로 오류가 발생함

 

  • 스칼라 서브쿼리 vs. 조인
-. 스칼라 서브쿼리
SELECT a.employee_id, a.first_name || a.last_name emp_name, a.department_id
      ,(SELECT department_name
        FROM departments b
        WHERE a.department_id = b.department_id) dept_name
FROM employees a
ORDER BY 1;

-- 사번 178번 킴벌리 그랜트 조회 가능
-. 내부조인(INNER JOIN)
SELECT a.employee_id, a.first_name || a.last_name emp_name, a.department_id
      ,b.department_name
FROM employees a, departments b
WHERE a.department_id = b.department_id
ORDER BY 1;

-- 사번 178번 킴벌리 그랜트 조회 불가
-- (WHERE 절에서 INNER JOIN으로 실행 되었기 때문)
-. 외부조인(LEFT JOIN)
SELECT a.employee_id, a.first_name || a.last_name emp_name, a.department_id
      ,b.department_name
FROM employees a
LEFT JOIN departments b
     ON a.department_id = b.department_id
ORDER BY 1;

-- 사번 178번 킴벌리 그랜트 조회 가능(LEFT JOIN으로 null 값 조회 가능)

 

3. 인라인 뷰(FROM 절)

1) 개요

  • 메인쿼리의 FROM 절에 위치
  • 서브쿼리 자체가 마치 하나의 테이블 처럼 동작
  • 서브쿼리가 최종 반환하는 로우와 컬럼, 표현식 수는 1개 이상 가능
  • 서브쿼리에 대한 별칭(Alias)은 반드시 명시
  • 메인쿼리와 조인 조건은 메인쿼리의 WHERE 절에서 처리가 일반적

2) 예시

SELECT a.employee_id, a.first_name || a.last_name employee_name
      ,a.department_id, c.dept_name
FROM employees a
   ,(SELECT b.department_id, b.department_name dept_name
     FROM departments b) c
WHERE a.department_id = c.department_id
ORDER BY 1;

3) LATERAL

  • 서브쿼리 내에서 조인이 불가하였으나 LATERAL을 이용하면 조인 가능
  • -. AS-IS (LATERAL 미사용)
SELECT a.employee_id, a.first_name || a.last_name employee_name
      ,a.department_id, c.dept_name
FROM employees a
    ,**(SELECT b.department_id, b.department_name dept_name
      FROM departments b
      WHERE a.department_id = b.department_id) c**
ORDER BY 1;

-- 서브쿼리 내에서 조인 조건 불가(오류 발생)
**-. TO-BE (LATERAL 사용)**
SELECT a.employee_id, a.first_name || a.last_name employee_name
      ,a.department_id, c.dept_name
FROM employees a
    ,**LATERAL 
    (SELECT b.department_id, b.department_name dept_name
     FROM departments b
     WHERE a.department_id = b.department_id) c**
ORDER BY 1;

-- 오라클 12c 이후 버전에서 LATERAL을 사용해 서브쿼리 내 조인 가능

 

4. 중첩 서브쿼리 (Nested Subquery)

1) 개요

  • 메인쿼리의 WHERE 절에 위치
  • 서브쿼리가 조건절의 일부로 사용됨
  • 서브쿼리 최종 반환 값과 메인쿼리 테이블의 특정 컬럼 값을 비교 시 사용
  • 서브쿼리가 최종 반환하는 로우와 컬럼
  • 조건절의 일부이므로 서브쿼리에 대한 별칭(Alias) 사용 불가
  • 서브쿼리 내에서 메인쿼리와 조인 가능

2) 예시

SELECT *
FROM departments
WHERE department_id IN 
     (SELECT department_id
      FROM employees);
SELECT *
FROM departments a
WHERE EXISTS
     (SELECT 1
      FROM employees b
      WHERE a.department_id = b.department_id);