T-SQL에서 관계형 나누기 연산과 빈 집합 처리

T-SQL에는 전용 나누기 연산자가 존재하지 않지만 관계형 나누기(Relational Division)를 NOT EXISTS 구문으로 구현할 수 있다. 일반적인 나누기 결과는 피제수 관계에서 가져온 컬럼 하나와 제수 관계에서 가져온 컬럼들로 구성된다. 예를 들어 Customers, Employees, Orders 세 테이블이 있고, 모든 미국 직원과 최소 한 번 이상 거래한 고객을 찾는다고 하자.

SELECT custid 
FROM Sales.Customers AS C
WHERE NOT EXISTS
    (SELECT * FROM HR.Employees AS E
     WHERE country = 'USA'
       AND NOT EXISTS
           (SELECT * FROM Sales.Orders AS O
            WHERE O.custid = C.custid
              AND O.empid = E.empid));

이 쿼리는 23명의 고객을 반환하며, 각 고객이 미국 직원 모두와 거래 기록을 하나 이상 가지고 있음을 뜻한다.

그렇다면 직원의 국가 조건을 'Israel'로 바꿔 보자.

SELECT custid 
FROM Sales.Customers AS C
WHERE NOT EXISTS
    (SELECT * FROM HR.Employees AS E
     WHERE country = 'Israel'
       AND NOT EXISTS
           (SELECT * FROM Sales.Orders AS O
            WHERE O.custid = C.custid
              AND O.empid = E.empid));

이번 결과는 91행으로, 전체 고객 수와 동일하다. 그러나 실제로 이스라엘 국적의 직원은 존재하지 않기 때문에 이 결과는 올바르지 않다. 제수 집합이 공집합일 때 모든 피제수 행이 조건을 만족하는 공진(empty truth) 현상이 발생한 것이다. 관계형 나누기에서 "0으로 나누면 결과가 무한대"라는 표현이 바로 이 상황을 비유한다.

이 문제를 막으려면 제수 집합이 비어 있지 않음을 명시적으로 확인해야 한다. 아래와 같이 EXISTS를 덧붙여 실제로 이스라엘 직원이 존재할 때만 나누기를 수행하도록 한다.

SELECT custid 
FROM Sales.Customers AS C
WHERE NOT EXISTS
    (SELECT * FROM HR.Employees AS E
     WHERE country = 'Israel'
       AND NOT EXISTS
           (SELECT * FROM Sales.Orders AS O
            WHERE O.custid = C.custid
              AND O.empid = E.empid))
  AND EXISTS (SELECT * FROM HR.Employees AS E WHERE country = 'Israel');

이제 빈 집합을 안전하게 처리하므로 0건이 출력된다.

동일한 요구 사항은 피제수-제수-중개 관계 대신 그룹화와 HAVING을 활용한 카운팅 방식으로도 해결할 수 있다. 다음은 미국 직원 집합에 적용한 예다.

SELECT custid
FROM Sales.Orders
WHERE empid IN
    (SELECT empid FROM HR.Employees WHERE country = N'USA')
GROUP BY custid
HAVING COUNT(DISTINCT empid) = (SELECT COUNT(*) FROM HR.Employees WHERE country = N'USA');

이 쿼리 역시 모든 미국 직원과 거래한 고객 23명을 정확히 찾아낸다. 그룹별 고유 직원 수가 전체 직원 수와 일치하는 행만 남기는 논리이므로, 제수 집합이 비어 있을 때는 서브쿼리로 얻은 건수가 0이 되고 자동으로 공진 오류를 회피할 수 있다.

태그: T-SQL 관계형 나누기 NOT EXISTS GROUP BY HAVING

9월 26일 11:37에 게시됨