data/oracle

[데이터베이스실습] 07. 집합 연산자와 조인

렁치 2026. 8. 15. 18:36

여러 테이블(또는 여러 조회 결과)을 하나로 합치는 방법은 크게 두 가지다. 집합 연산자는 결과를 위아래로(세로로) 잇고, 조인은 좌우로(가로로) 잇는다. 이 둘의 방향 차이를 먼저 잡아두니 헷갈리지 않았다.

특히 조인은 관계형 데이터베이스의 진짜 위력이 드러나는 지점이다. 정규화로 잘게 나눠둔 테이블들을 다시 의미 있게 엮어주는 것이 바로 조인이기 때문이다.


1. 집합 연산자

두 SELECT 결과를 세로로 잇는다. 두 SELECT의 열 개수와 자료형이 같아야 한다.

연산자 의미
UNION 합집합, 중복 제거
UNION ALL 합집합, 중복도 모두 출력
MINUS 차집합 (첫 결과에서 두 번째 결과를 뺌)
INTERSECT 교집합 (양쪽에 다 있는 행만)
SELECT empno, ename, deptno FROM EMP WHERE deptno = 10
UNION
SELECT empno, ename, deptno FROM EMP WHERE deptno = 20;

UNION은 중복 제거를 위해 내부적으로 정렬을 하므로, 중복이 없다고 확신하거나 성능이 중요하면 UNION ALL이 더 빠르다.


2. 조인 (Join)

조인은 두 개 이상의 테이블을 가로로 연결해 하나의 테이블처럼 조회한다.

조인 조건이 없으면 카티전 곱(Cartesian Product)
FROM 절에 여러 테이블만 나열하고 연결 조건을 안 주면, 모든 행의 조합이 만들어진다. 결과 행 수가 각 테이블 행 수의 단순 곱(EMP 14행 × DEPT 4행 = 56행)이 되는 것이다. 의미 있는 결과를 얻으려면 연결 조건이 반드시 필요하고, N개 테이블을 이으려면 최소 N−1개의 조인 조건이 든다.
SELECT * FROM EMP, DEPT;                                -- 카티전 곱 (잘못된 사용)
SELECT * FROM EMP, DEPT WHERE EMP.DEPTNO = DEPT.DEPTNO; -- 올바른 조인

-- 테이블 별칭은 AS 없이 붙인다
SELECT * FROM EMP E, DEPT D WHERE E.DEPTNO = D.DEPTNO;

조인 조건을 깜빡하고 56행이 쏟아진 적이 있었는데, 그 덕에 조인이 "카티전 곱에서 조건에 맞는 행만 거른 것"이라는 정의가 몸으로 이해됐다.


3. 조인의 종류

등가 조인 (Equi Join)

가장 흔한 조인. 두 테이블에서 특정 열의 값이 일치(=)하는 행을 연결한다. 두 테이블에 같은 이름의 열이 있으면 반드시 테이블을 명시해야 한다.

SELECT E.EMPNO, E.ENAME, E.DEPTNO, D.DNAME, D.LOC
FROM EMP E, DEPT D
WHERE E.DEPTNO = D.DEPTNO AND E.SAL >= 3000;

비등가 조인 (Non-Equi Join)

=가 아니라 범위로 연결한다. 급여 등급표(SALGRADE)처럼 "구간에 해당하는지"로 잇는 경우다.

SELECT E.ENAME, E.SAL, S.GRADE
FROM EMP E, SALGRADE S
WHERE E.SAL BETWEEN S.LOSAL AND S.HISAL;

자체 조인 (Self Join)

하나의 테이블을 별칭으로 두 번 참조해 연결한다. "사원과 그 사원의 직속 상관"처럼 같은 테이블 안에서 행끼리 엮을 때 쓴다.

SELECT E1.ENAME AS 사원, E2.ENAME AS 상관
FROM EMP E1, EMP E2
WHERE E1.MGR = E2.EMPNO;

외부 조인 (Outer Join)

기본 조인은 조건이 맞는 행만 남긴다. 그러면 위 자체 조인에서 상관이 없는 KING(MGR이 NULL)은 결과에서 빠진다. 짝이 없는 행도 살리고 싶을 때 외부 조인을 쓴다.

-- 오라클 전통 문법: 부족한(채워줘야 할) 쪽에 (+)를 붙인다
WHERE E1.MGR = E2.EMPNO(+);   -- 왼쪽(E1) 모든 행 보존
WHERE E1.MGR(+) = E2.EMPNO;   -- 오른쪽(E2) 모든 행 보존

(+) 기호의 위치가 헷갈리기 쉬운데, "행이 부족한 반대쪽"에 붙인다고 기억하면 된다. 살리고 싶은 테이블의 반대편에 (+)를 다는 것이다.


4. SQL-99 표준 조인 문법

오라클 전통 문법(,(+))은 다른 DBMS와 호환되지 않는다. 그래서 여러 DBMS에서 통하는 SQL-99 표준 문법을 함께 익혀두는 게 좋다.

종류 문법 특징
NATURAL JOIN FROM A NATURAL JOIN B 같은 이름 열로 자동 등가조인
JOIN ~ USING FROM A JOIN B USING (열) 기준 열을 명시
JOIN ~ ON FROM A JOIN B ON 조건 조인 조건을 직접 명시
OUTER JOIN LEFT/RIGHT/FULL OUTER JOIN ... ON ... 외부 조인
-- 아래 셋은 모두 같은 결과
SELECT * FROM EMP E NATURAL JOIN DEPT D;
SELECT * FROM EMP E JOIN DEPT D USING (DEPTNO);
SELECT * FROM EMP E JOIN DEPT D ON E.DEPTNO = D.DEPTNO;

-- 외부 조인 (왼쪽/오른쪽/양쪽)
SELECT * FROM EMP E1 LEFT  OUTER JOIN EMP E2 ON E1.MGR = E2.EMPNO;
SELECT * FROM EMP E1 RIGHT OUTER JOIN EMP E2 ON E1.MGR = E2.EMPNO;

ON이 가장 명시적이고 유연해 실무에서 가장 많이 쓰인다. NATURAL JOIN은 편하지만 "같은 이름 열"이 의도치 않게 더 있으면 결과가 틀어질 수 있어 주의가 필요하다.


실습 쿼리로 익히기

-- 부하직원이 없는 사원 찾기 (외부 조인 + NULL 활용)
-- E1.MGR(+)=E2.EMPNO 로 모든 상관을 살린 뒤, 매칭된 부하(E1)가 없는 행만
SELECT e2.ENAME AS "부하 없는 사원"
FROM EMP e1, EMP e2
WHERE e1.MGR(+) = e2.EMPNO AND e1.EMPNO IS NULL;

-- 사원이 없는 부서(40번)까지 보이게 (오른쪽 외부 조인)
SELECT D.DEPTNO, D.DNAME, E.ENAME
FROM EMP E, DEPT D
WHERE E.DEPTNO(+) = D.DEPTNO
ORDER BY D.DEPTNO;
-- SQL-99: FROM EMP E RIGHT OUTER JOIN DEPT D ON E.DEPTNO = D.DEPTNO

-- 4개 테이블 다중 조인: 사원 + 부서 + 급여등급 + 상관
SELECT D.DNAME, E1.ENAME, S.GRADE, E2.ENAME AS 상관
FROM EMP E1 RIGHT OUTER JOIN DEPT D     ON E1.DEPTNO = D.DEPTNO
            LEFT  OUTER JOIN SALGRADE S ON E1.SAL BETWEEN S.LOSAL AND S.HISAL
            LEFT  OUTER JOIN EMP E2     ON E1.MGR = E2.EMPNO
ORDER BY D.DEPTNO;

마지막 예처럼 조인은 세 개, 네 개로 얼마든지 이어붙일 수 있다. 각 조인이 "어느 테이블을 어떤 조건으로 붙이는지"만 또박또박 따라가니, 복잡해 보이는 다중 조인도 결국 2-테이블 조인의 반복일 뿐이었다.


수업에서 직접 친 쿼리

먼저 조인 조건 없이 FROM EMP, DEPT를 던져 56행이 나오는 걸 직접 확인한 뒤, 조건을 붙여 정상 조인으로 좁혀갔다.

SELECT COUNT(*) FROM EMP, DEPT;                       -- 14 × 4 = 56
SELECT * FROM EMP e, DEPT d WHERE e.DEPTNO = d.DEPTNO; -- 올바른 조인

구체적인 질문으로 조인을 익혔다. "SMITH의 근무지", "DALLAS 근무 사원", 비등가 조인으로 "JONES의 급여 등급"을 구했다.

-- SMITH가 어디서 근무하는가
SELECT ENAME, LOC AS "근무지"
FROM EMP, DEPT
WHERE (EMP.DEPTNO = DEPT.DEPTNO) AND UPPER(EMP.ENAME) = 'SMITH';

-- 비등가 조인: JONES의 급여 등급
SELECT e.ENAME, s.GRADE
FROM EMP e, SALGRADE s
WHERE (e.SAL BETWEEN s.LOSAL AND s.HISAL) AND UPPER(e.ENAME) = 'JONES';

자체 조인으로 사원-상관을 엮고, 외부 조인 (+)으로 "부하직원이 없는 사원"을 뽑는 게 까다로웠다.

-- 부하직원이 없는 사원 (외부 조인 + NULL 활용)
SELECT e2.ENAME AS "상관 이름"
FROM EMP e1, EMP e2
WHERE (e1.MGR(+) = e2.EMPNO) AND e1.EMPNO IS NULL;

연습문제 Q4가 압권이었다. 사원·부서·급여등급·상관 4개 테이블을 외부 조인으로 엮는 문제를, (+) 전통 문법과 SQL-99 문법 두 가지로 다 작성해봤다.

-- SQL-99: RIGHT/LEFT OUTER JOIN 체이닝
SELECT D.DEPTNO, D.DNAME, E1.ENAME, S.GRADE, E2.ENAME AS MGR_ENAME
FROM EMP E1 RIGHT OUTER JOIN DEPT D     ON (E1.DEPTNO = D.DEPTNO)
            LEFT  OUTER JOIN SALGRADE S ON (E1.SAL BETWEEN S.LOSAL AND S.HISAL)
            LEFT  OUTER JOIN EMP E2     ON (E1.MGR = E2.EMPNO)
ORDER BY D.DEPTNO, E1.EMPNO;

같은 문제를 NATURAL JOIN, USING, ON 세 가지 SQL-99 문법으로 모두 풀어보며 차이를 비교한 것도 도움이 됐다.


오늘 느낀 점

  • 집합 연산자는 세로, 조인은 가로라는 방향 구분 하나로 둘이 명확히 갈렸다. 합치는 방향을 먼저 잡으니 언제 무엇을 쓸지가 분명해졌다.
  • 조인 조건을 빠뜨려 56행이 나온 실수가 오히려 도움이 됐다. "조인 = 카티전 곱 + 조건"이라는 정의가 그 한 번으로 박혔다.
  • 다중 조인이 결국 2-테이블 조인의 반복이라는 걸 4-테이블 예제로 직접 확인하니, 복잡한 쿼리에 대한 부담이 줄었다.

한 걸음 더

  • 조인은 본질적으로 "카티전 곱을 만든 뒤 조건에 맞는 행만 거른 것"이다. 그래서 조인 조건을 빠뜨리면 56행, 더 많은 테이블에선 수만 행이 폭발적으로 생긴다. 실무에서 쿼리가 갑자기 어마어마하게 느려지거나 결과가 이상하게 많아졌다면, 조인 조건을 빠뜨린 카티전 곱을 가장 먼저 의심한다.
  • 같은 조인이라도 DBMS는 내부적으로 여러 방식(중첩 루프, 해시 조인, 정렬 병합 조인)으로 실행한다. 데이터 크기와 인덱스 상황에 따라 최적화기가 알아서 고르는데, 큰 테이블 조인이 느릴 때 실행 계획(EXPLAIN PLAN)을 보면 어떤 방식이 쓰였는지 확인할 수 있다.
  • 외부 조인의 (+)는 오라클 고유 문법이라 다른 DBMS에선 동작하지 않고, FULL OUTER JOIN은 아예 표현할 수도 없다. 그래서 새로 작성하는 쿼리는 가급적 SQL-99의 LEFT/RIGHT/FULL OUTER JOIN을 쓰는 편이 이식성과 가독성 모두에 유리하다.