여러 테이블(또는 여러 조회 결과)을 하나로 합치는 방법은 크게 두 가지다. 집합 연산자는 결과를 위아래로(세로로) 잇고, 조인은 좌우로(가로로) 잇는다. 이 둘의 방향 차이를 먼저 잡아두니 헷갈리지 않았다.
특히 조인은 관계형 데이터베이스의 진짜 위력이 드러나는 지점이다. 정규화로 잘게 나눠둔 테이블들을 다시 의미 있게 엮어주는 것이 바로 조인이기 때문이다.
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)
조인은 두 개 이상의 테이블을 가로로 연결해 하나의 테이블처럼 조회한다.
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을 쓰는 편이 이식성과 가독성 모두에 유리하다.
'data > oracle' 카테고리의 다른 글
| [데이터베이스실습] 06. 다중행 함수와 그룹화 (3) | 2026.08.06 |
|---|---|
| [데이터베이스실습] 05. 오라클 함수 - 숫자·날짜·변환 (0) | 2026.07.30 |
| [데이터베이스실습] 04. 오라클 함수 - 문자 함수 (0) | 2026.07.23 |
| [데이터베이스실습] 03. SELECT - WHERE 조건 (0) | 2026.07.16 |
| [데이터베이스실습] 02. 테이블 기초와 SELECT (0) | 2026.07.05 |