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에는 없는 데이터
우선순위:
- ✅ NOT EXISTS (추천)
- ⭕ LEFT JOIN + IS NULL
- ❌ NOT IN (지양)