카테고리 없음

실무 SQL 패턴 시리즈 ① - A에는 있고 B에는 없는 데이터 찾기

babytomato 2026. 2. 18. 08:05

A에는 있고 B에는 없는 데이터 찾기 (NOT EXISTS vs LEFT JOIN)

실무에서 정말 자주 나오는 요구사항이 있습니다.

👉 “A 테이블에는 있는데 B 테이블에는 없는 데이터 찾아주세요.”

예를 들면:

  • 회원 테이블에는 있는데 주문 없는 회원 찾기
  • 기준 테이블에는 있는데 운영 테이블에 없는 데이터 찾기
  • 동기화 안 된 데이터 찾기

이건 단순 조회가 아니라
👉 데이터 정합성 체크의 핵심 패턴입니다.


기본 예제 상황

-- 회원 테이블
MEMBER (member_id, name)

-- 주문 테이블
ORDERS (order_id, member_id)

👉 목표:

주문이 한 번도 없는 회원 찾기


패턴 1: NOT EXISTS (가장 추천)

SELECT *
FROM MEMBER m
WHERE NOT EXISTS (
    SELECT 1
    FROM ORDERS o
    WHERE o.member_id = m.member_id
);

특징

  • 인덱스 있으면 매우 빠름
  • 옵티마이저가 최적화 잘함
  • NULL 문제 없음
  • 실무에서 가장 안정적인 방법

👉 Anti Join의 정석


패턴 2: LEFT JOIN + IS NULL

SELECT m.*
FROM MEMBER m
LEFT JOIN ORDERS o
  ON m.member_id = o.member_id
WHERE o.member_id IS NULL;

특징

  • 직관적이라 이해하기 쉬움
  • 실행계획이 JOIN 기반
  • 데이터 많으면 느려질 수 있음

👉 결과는 같지만 실행 방식이 다름


피해야 할 패턴: NOT IN

SELECT *
FROM MEMBER
WHERE member_id NOT IN (
    SELECT member_id FROM ORDERS
);

문제점

  • 서브쿼리에 NULL 있으면 결과 이상해짐
  • 옵티마이저가 최적화 못하는 경우 많음
  • 실무에서는 거의 안 씀

👉 NOT IN은 함정 카드


실행계획 관점에서 보면

좋은 인덱스가 있으면:

MEMBER → INDEX RANGE SCAN
ORDERS → INDEX LOOKUP

👉 NOT EXISTS는 보통 Nested Loop Anti Join으로 동작합니다.

즉,

회원 한 명씩 보면서
주문 있는지 인덱스로 빠르게 확인

그래서 성능이 좋습니다.


실무 팁

인덱스 필수

CREATE INDEX idx_orders_member
ON ORDERS(member_id);

👉 이 인덱스 하나로 성능이 10배 이상 차이 날 수 있음


대용량 데이터일 때

  • EXISTS 계열이 보통 가장 안정적
  • LEFT JOIN은 해시 조인으로 바뀔 수 있음
  • 실행계획 꼭 확인

언제 쓰는 패턴인가?

이 패턴은 거의 모든 시스템에서 등장합니다.

  • 데이터 동기화 체크
  • 배치 누락 데이터 찾기
  • 무결성 검증
  • 장애 분석

👉 실무 필수 SQL 패턴


핵심 정리

👉 A에는 있고 B에는 없는 데이터

우선순위:

  1. ✅ NOT EXISTS (추천)
  2. ⭕ LEFT JOIN + IS NULL
  3. ❌ NOT IN (지양)