DB·SQL 실무 가이드 · Part 4

서브쿼리와 집합 연산, MERGE

복잡한 요구사항을 단계로 쪼개 표현하는 도구들

작성 기준2026년 7월버전과 지원 현황은 이후 달라질 수 있으니 공식 문서를 함께 확인하세요.

이 파트에서 다루는 내용

서브쿼리의 세 위치스칼라 서브쿼리집합 연산WITH 절MERGE
01

서브쿼리는 놓이는 위치로 구분합니다

서브쿼리를 종류별로 외우기보다, 어디에 놓였는지로 보면 역할이 분명해집니다. 세 자리가 있고 각각 하는 일이 다릅니다.

WHERE 절
조건으로

다른 테이블의 결과를 조건으로 씁니다. IN, EXISTS, 비교 연산자와 함께 쓰입니다. 가장 흔한 형태입니다.

FROM 절 (인라인뷰)
가상 테이블로

서브쿼리 결과를 테이블처럼 씁니다. 집계한 결과를 다시 조인하거나 필터할 때 필수적입니다.

SELECT 절 (스칼라)
값 하나로

행마다 값 하나를 가져옵니다. 편하지만 성능 함정이 있어 다음 섹션에서 따로 다룹니다.

인라인뷰: 집계한 뒤 필터하기sql
-- 주문 금액 합계가 100만 원을 넘는 고객만
SELECT c.customer_nm, s.total_amount
FROM   customer c
JOIN   (SELECT customer_id, SUM(amount) AS total_amount
        FROM   orders
        WHERE  status_cd = 'DONE'
        GROUP BY customer_id) s
  ON   s.customer_id = c.customer_id
WHERE  s.total_amount > 1000000;

집계 결과를 조건으로 쓰려면 인라인뷰나 HAVING이 필요합니다. 집계 전에는 존재하지 않는 값이기 때문입니다.

02

스칼라 서브쿼리는 편한 만큼 위험합니다

SELECT 절에 서브쿼리를 넣으면 조인 없이 다른 테이블 값을 가져올 수 있어 편합니다. 하지만 원칙적으로 결과 행마다 한 번씩 실행됩니다.

결과가 10건이면 문제없지만 10만 건이면 서브쿼리가 10만 번 실행됩니다. 조회 화면이 갑자기 느려졌을 때 흔한 원인입니다.

Oracle은 같은 입력값에 대한 결과를 캐싱해서 실행 횟수를 줄여 줍니다. 그래서 입력값의 종류가 적으면 빠르고, 값이 다양하면 캐시가 안 먹혀 느려집니다. 이 차이 때문에 개발 환경에서는 빠르다가 운영에서 느려지는 일이 생깁니다.

스칼라 서브쿼리를 조인으로 바꾸기sql
-- 행마다 실행될 수 있는 형태
SELECT o.order_id,
       o.amount,
       (SELECT c.customer_nm FROM customer c
        WHERE  c.customer_id = o.customer_id) AS customer_nm
FROM   orders o
WHERE  o.order_dt >= TRUNC(SYSDATE) - 7;

-- 조인으로 바꾸면 한 번에 처리됩니다
SELECT o.order_id, o.amount, c.customer_nm
FROM   orders o
LEFT JOIN customer c ON c.customer_id = o.customer_id
WHERE  o.order_dt >= TRUNC(SYSDATE) - 7;

아우터 조인으로 바꿔야 결과가 같습니다. 스칼라 서브쿼리는 짝이 없으면 NULL을 반환하므로 LEFT JOIN에 대응합니다. INNER JOIN으로 바꾸면 행이 사라질 수 있습니다.

판단 기준

결과 행이 적고 서브쿼리 입력값 종류가 적으면 스칼라 서브쿼리가 읽기 좋습니다. 결과 행이 많거나 값이 다양하면 조인으로 바꿉니다. 두 행 이상이 반환되면 ORA-01427 오류가 나는 점도 기억합니다.

03

집합 연산은 UNION ALL을 기본으로 씁니다

UNION ALL
합치기

두 결과를 그냥 이어 붙입니다. 중복 제거를 안 하므로 빠릅니다. 기본으로 이걸 쓰고, 중복 제거가 정말 필요할 때만 UNION으로 바꿉니다.

UNION
합치고 중복 제거

정렬해서 중복을 제거합니다. 그만큼 비용이 큽니다. 애초에 중복이 없는 데이터라면 낭비입니다.

MINUS
빼기

앞의 결과에서 뒤의 결과를 뺍니다. 두 시스템 데이터를 대사할 때 유용합니다. 표준 SQL의 EXCEPT에 해당하는 Oracle 문법입니다.

INTERSECT
교집합

양쪽에 다 있는 것만 남깁니다. MINUS와 함께 데이터 검증에 자주 쓰입니다.

데이터 대사에 쓰는 패턴sql
-- 이관 전후 데이터가 같은지 양방향으로 확인
SELECT customer_id, customer_nm FROM customer_old
MINUS
SELECT customer_id, customer_nm FROM customer_new;

SELECT customer_id, customer_nm FROM customer_new
MINUS
SELECT customer_id, customer_nm FROM customer_old;

양쪽 모두 0건이어야 완전히 같습니다. 한 방향만 확인하면 신규 시스템에만 있는 데이터를 놓칩니다. 이관 검증에서 실제로 쓰는 방법입니다.

04

WITH 절로 쿼리를 단계로 나눕니다

인라인뷰가 중첩되면 읽기 어려워집니다. WITH 절을 쓰면 각 단계에 이름을 붙여 위에서 아래로 읽히게 만들 수 있습니다.

같은 서브쿼리를 여러 번 참조할 때도 유용합니다. Oracle은 필요하다고 판단하면 결과를 임시 저장해 재사용합니다.

단계별로 이름 붙이기sql
WITH recent_orders AS (
  SELECT customer_id, amount
  FROM   orders
  WHERE  order_dt >= ADD_MONTHS(TRUNC(SYSDATE), -3)
  AND    status_cd = 'DONE'
),
customer_summary AS (
  SELECT customer_id,
         COUNT(*)    AS order_cnt,
         SUM(amount) AS total_amount
  FROM   recent_orders
  GROUP BY customer_id
)
SELECT c.customer_nm,
       c.grade_cd,
       s.order_cnt,
       s.total_amount
FROM   customer_summary s
JOIN   customer c ON c.customer_id = s.customer_id
WHERE  s.total_amount > 1000000
ORDER BY s.total_amount DESC;

같은 내용을 인라인뷰 중첩으로 쓰면 안쪽부터 읽어야 해서 이해가 어렵습니다. 단계가 둘 이상이면 WITH 절을 권합니다.

05

MERGE로 있으면 수정, 없으면 입력을 한 번에

배치에서 가장 흔한 요구사항입니다. 기존 데이터가 있으면 갱신하고 없으면 새로 넣는 처리입니다.

애플리케이션에서 조회 후 분기하면 건수만큼 왕복이 생기고, 동시 실행 시 중복 입력 문제도 생깁니다. MERGE는 한 문장으로 처리합니다.

MERGE 기본형sql
MERGE INTO customer_summary t
USING (
  SELECT customer_id,
         COUNT(*)    AS order_cnt,
         SUM(amount) AS total_amount
  FROM   orders
  WHERE  order_dt >= TRUNC(SYSDATE) - 1
  GROUP BY customer_id
) s
ON (t.customer_id = s.customer_id)
WHEN MATCHED THEN
  UPDATE SET t.order_cnt    = s.order_cnt,
             t.total_amount = s.total_amount,
             t.upd_dt       = SYSDATE
WHEN NOT MATCHED THEN
  INSERT (customer_id, order_cnt, total_amount, upd_dt)
  VALUES (s.customer_id, s.order_cnt, s.total_amount, SYSDATE);

ON 절의 컬럼은 UPDATE에서 바꿀 수 없습니다. 또 ON 조건에 맞는 대상 행이 여러 개면 ORA-30926 이 납니다. USING 쪽 결과에 중복 키가 없는지 먼저 확인합니다.

이관 시 주의

MERGE는 Oracle과 MSSQL에는 있지만 MySQL은 INSERT ... ON DUPLICATE KEY UPDATE, PostgreSQL은 INSERT ... ON CONFLICT 로 문법이 완전히 다릅니다. 부록 대조표에 정리해 두었습니다.

체크

이 파트 완료 기준