조인·서브쿼리 심화 — 안티 조인 · 셀프 조인 · 집합 연산 · 상관 서브쿼리
없는 것을 찾는 안티 조인, 같은 테이블을 두 번 읽는 셀프 조인, 집합 연산, 상관 서브쿼리와 다중 조인을 다룹니다.
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 안에서 두 조건을 모두 이어야 합니다. 한쪽만 이으면 다른 품목의 이력까지 인정됩니다.
- 안티 조인 뒤에는 보통 마스터 조건(국가·삭제 표시)을 함께 겁니다.
문제
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.키로 한 방향만 남겨 중복을 없앱니다. - 원전표와 역분개 전표, 청구와 취소 청구처럼 짝을 찾을 때도 셀프 조인을 씁니다.
- 별칭을 반드시 붙이고, 어느 쪽 컬럼인지 명시하세요.
- 셀프 조인은 행 수가 제곱으로 늘 수 있습니다. 조인 키와 범위 조건을 먼저 좁히세요.
문제
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 는 전체 집합 연산이 끝난 뒤 맨 마지막에 한 번만 씁니다.
문제
- 2025년에 판매 문서가 없는 고객 (초급)
- 정리 대상 거래처 통합 목록 (초급)
- 국가·도시별 거래처 수 (중급)
- 2024·2025년 모두 발주한 공급업체 (중급)
- 표준 오더와 반품이 모두 있는 고객 (중급)
- 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()로 씁니다. 후자가 보통 빠르고 읽기 쉽습니다. - 상관 서브쿼리는 행마다 다시 계산될 수 있어 대량 데이터에서는 윈도우 함수나 조인으로 바꾸는 것이 좋습니다.
문제
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)을 쓰면 단계가 유연해지지만, 특정 경로만 볼 때는 직접 조인이 분명합니다.