조인·서브쿼리 심화 — 안티 조인 · 셀프 조인 · 집합 연산 · 상관 서브쿼리

없는 것을 찾는 안티 조인, 같은 테이블을 두 번 읽는 셀프 조인, 집합 연산, 상관 서브쿼리와 다중 조인을 다룹니다.

1. 없는 것 찾기 — NOT EXISTS 와 안티 조인

"한 번도 주문하지 않은 고객", "입고되지 않은 구매 품목"처럼 없음을 찾는 패턴입니다.

SELECT K.KUNNR, K.NAME1
FROM KNA1 K
WHERE NOT EXISTS (
  SELECT 1 FROM VBAK V WHERE V.KUNNR = K.KUNNR AND V.AUART = 'OR'
);
  • LEFT JOIN … WHERE 오른쪽키 IS NULL 도 같은 결과를 냅니다. 여러 조건이 있으면 NOT EXISTS 가 읽기 쉽습니다.
  • NOT IN (서브쿼리) 는 서브쿼리 결과에 NULL 이 하나라도 있으면 아무 행도 반환하지 않습니다. NULL 이 가능한 컬럼이면 NOT EXISTS 를 쓰세요.
  • 복합 키(문서 번호 + 품목 번호)는 EXISTS 안에서 두 조건을 모두 이어야 합니다. 한쪽만 이으면 다른 품목의 이력까지 인정됩니다.
  • 안티 조인 뒤에는 보통 마스터 조건(국가·삭제 표시)을 함께 겁니다.

문제

  1. 반품을 한 번도 접수하지 않은 한국 고객 (초급)
  2. 12월에 표준 오더가 없었던 고객(삭제 표시 제외) (중급)
  3. 입고 이력이 한 건도 없는 구매 품목 (중급)
  4. 구매도 판매도 한 적 없는 자재 (중급)
  5. 구매만 하고 판매한 적 없는 자재 (중급)
  6. 전량 입고됐지만 송장이 없는 구매 품목 (고급)

2. 셀프 조인 — 같은 테이블을 두 번

같은 테이블을 별칭만 다르게 두 번 읽어 행끼리 비교합니다.

SELECT A.LIFNR AS VENDOR_A, B.LIFNR AS VENDOR_B, A.MATNR
FROM EINA A
JOIN EINA B ON B.MATNR = A.MATNR AND A.LIFNR < B.LIFNR;
  • 공급업체 쌍처럼 대칭 관계는 A.키 < B.키 로 한 방향만 남겨 중복을 없앱니다.
  • 원전표와 역분개 전표, 청구와 취소 청구처럼 짝을 찾을 때도 셀프 조인을 씁니다.
  • 별칭을 반드시 붙이고, 어느 쪽 컬럼인지 명시하세요.
  • 셀프 조인은 행 수가 제곱으로 늘 수 있습니다. 조인 키와 범위 조건을 먼저 좁히세요.

문제

  1. 같은 도시에 있는 공급업체 쌍 (초급)
  2. 같은 자재를 공급하는 공급업체 쌍 (중급)
  3. 역분개된 원전표와 역분개 전표 짝짓기 (중급)
  4. 청구 후 취소된 청구 문서 찾기 (중급)
  5. 구매 정보 레코드가 있는 자재의 한국어·영어 내역 글자 수 비교 (중급)
  6. 원전표와 역분개 전표의 짝과 취소까지 걸린 일수 (고급)

3. 집합 연산 — UNION · INTERSECT · EXCEPT

여러 결과 집합을 세로로 합치거나 비교합니다.

SELECT KUNNR AS PARTNER, NAME1, 'CUSTOMER' AS ROLE FROM KNA1
UNION ALL
SELECT LIFNR, NAME1, 'VENDOR' FROM LFA1;
  • UNION ALL 은 그대로 이어 붙이고, UNION 은 중복을 없앱니다. 중복 제거는 정렬 비용이 있으니 필요할 때만 쓰세요.
  • INTERSECT 는 양쪽에 모두 있는 값, EXCEPT(또는 MINUS)는 앞쪽에만 있는 값을 돌려줍니다.
  • 두 쿼리의 컬럼 수와 형식이 같아야 하고, 열 이름은 첫 번째 쿼리 기준입니다.
  • ORDER BY 는 전체 집합 연산이 끝난 뒤 맨 마지막에 한 번만 씁니다.

문제

  1. 2025년에 판매 문서가 없는 고객 (초급)
  2. 정리 대상 거래처 통합 목록 (초급)
  3. 국가·도시별 거래처 수 (중급)
  4. 2024·2025년 모두 발주한 공급업체 (중급)
  5. 표준 오더와 반품이 모두 있는 고객 (중급)
  6. KRW 오더가 한 건도 없는 공급업체 (고급)

4. 상관 서브쿼리 — 행마다 자기 그룹과 비교

상관 서브쿼리는 안쪽 쿼리가 바깥 행의 값을 참조합니다. 바깥 행마다 조건이 달라집니다.

SELECT P.EBELN, P.EBELP, P.MATNR, P.NETWR
FROM EKPO P
WHERE P.NETWR > (
  SELECT AVG(Q.NETWR) FROM EKPO Q WHERE Q.MATNR = P.MATNR
);
  • "자재별 평균보다 큰 품목"처럼 그룹별 기준과 비교할 때 씁니다. 전체 평균과 비교하는 것과 다릅니다.
  • EXISTS 와 결합하면 "자신의 그룹에 어떤 조건을 만족하는 행이 있는가"를 행마다 검사합니다.
  • 가장 최근 행 찾기는 NOT EXISTS (더 최근 행) 또는 ROW_NUMBER() 로 씁니다. 후자가 보통 빠르고 읽기 쉽습니다.
  • 상관 서브쿼리는 행마다 다시 계산될 수 있어 대량 데이터에서는 윈도우 함수나 조인으로 바꾸는 것이 좋습니다.

문제

  1. 고객별 가장 최근 오더 (중급)
  2. 일부 품목만 거부된 2025년 판매 오더 (중급)
  3. 평가 영역 평균보다 재고 금액이 큰 자재 (중급)
  4. 고객 평소 수준을 넘은 2025년 대형 원화 오더 (고급)
  5. 2025년 12월 오더에서 가장 큰 유효 품목 (고급)
  6. 재구매 고객이 다른 고객보다 싸게 산 2025년 하반기 오더 품목 (고급)

5. 여러 테이블 잇기 — 업무 흐름 추적

네 개 이상의 테이블을 이어 주문 → 납품 → 청구, 구매 → 입고 → 송장 같은 흐름을 따라갑니다.

SELECT A.VBELN, A.KUNNR, L.VBELN AS DELIVERY, B.VBELN AS INVOICE
FROM VBAK A
JOIN LIPS L ON L.VGBEL = A.VBELN AND L.VGPOS <> '000000'
JOIN VBRP P ON P.VGBEL = L.VBELN
JOIN VBRK B ON B.VBELN = P.VBELN
WHERE A.ERDAT BETWEEN '20250101' AND '20251231';
  • 각 단계의 키를 정확히 이어야 합니다. 헤더 번호만 이으면 품목 단위가 섞입니다.
  • 행이 늘어나는 지점을 확인하세요. 한 오더에 납품이 여러 개, 한 납품에 청구가 여러 개면 최종 합계가 중복됩니다.
  • 집계는 원하는 단위(고객·문서·품목)에서 먼저 하고, 그 결과를 다른 지표와 조인하는 편이 안전합니다.
  • 문서 흐름(VBFA)을 쓰면 단계가 유연해지지만, 특정 경로만 볼 때는 직접 조인이 분명합니다.

문제

  1. 고객별 2025년 청구 실적과 문서 흐름 건수 (중급)
  2. 자재별 오더·납품·청구 수량 비교 (중급)
  3. 12월 청구 품목의 오더·납품 흐름 추적표 (중급)
  4. 공급업체별 입고 금액과 송장 금액 차이 대사 (고급)
  5. 12월 청구 품목의 오더·납품·청구 문서 흐름 추적 (고급)
  6. 반품 출고가 있었던 구매 품목의 순 입고와 송장 수량 비교 (고급)
  7. 공급업체별 입고에서 송장 전기까지 평균 소요 일수 (고급)