조인(JOIN)은 두 테이블에서 조건이 맞는 행끼리 이어 붙여 한 행으로 만듭니다. 짝이 없는 행을 버리면 내부 조인, 남기면 외부 조인입니다.
SQLD를 먼저 공부했다면 이 단원 끝의 모의 문제부터 풀어 보고, 틀린 문제가 나온 레슨만 골라 봐도 됩니다.
학생 표에는 학과코드만 있고, 학과 이름(학과명)은 학과 표에 있습니다. 학생 이름 옆에 학과명을 붙여 보려면 두 표를 이어야 합니다. 그런데 107번 강도현은 학과코드가 비어 있고(NULL), 경영(BA) 학과에는 학생이 없습니다. 이런 짝 없는 행을 버리느냐 남기느냐에 따라 조인의 종류가 갈립니다. 실기에는 조인 결과를 표로 쓰는 문제가 나오므로, 어느 행이 남고 어느 칸이 NULL이 되는지 예측할 수 있어야 합니다.
1조인 조건과 짝 없는 행
학생과 학과는 학과코드 열을 함께 가지고 있습니다. 이 열의 값이 같은 행끼리 이으면 학생 줄 옆에 학과명을 붙일 수 있습니다.
조인 조건 S.학과코드 = D.학과코드에 따라 짝을 짓습니다. S와 D는 FROM 절에서 학생과 학과에 붙인 별칭(alias)입니다. CS에는 101·102·103이, EE에는 104·105·106이 이어집니다.
짝이 없는 행이 둘 있습니다. 107 강도현은 학과코드가 NULL입니다. NULL은 어떤 값과 견줘도 같다는 판정이 나오지 않아 짝을 찾지 못합니다. 경영(BA) 학과에는 소속 학생이 없습니다.
그림도 짝을 짓고 짝 없는 행을 가려내는 데까지를 먼저 보입니다.
2내부 조인과 외부 조인 셋
그림은 이어서 조인 넷을 차례로 보입니다. 내부 조인(INNER JOIN)은 짝이 있는 6행만 냅니다. 107과 경영은 결과에 없습니다. 왼쪽 외부 조인(LEFT OUTER JOIN)은 JOIN 왼쪽에 쓴 학생 표의 행을 모두 남깁니다. 짝이 없는 107 줄의 학과명은 NULL로 채워져 7행이 됩니다. 오른쪽 외부 조인(RIGHT OUTER JOIN)은 JOIN 오른쪽에 쓴 학과 표의 행을 모두 남깁니다. 짝이 없는 경영 줄의 학번·이름은 NULL로 채워져 7행이 됩니다. 완전 외부 조인(FULL OUTER JOIN)은 양쪽의 짝 없는 행을 모두 남깁니다. 내부 조인 6행에 107 줄과 경영 줄이 더해져 8행입니다.
| 조인 | 남기는 행 | NULL로 채워지는 칸 | 행 수 |
|---|---|---|---|
| INNER JOIN | 짝이 있는 행만 | 없다 | 6 |
| LEFT OUTER JOIN | JOIN 왼쪽 학생 표의 행을 모두 | 107 줄의 학과명 | 7 |
| RIGHT OUTER JOIN | JOIN 오른쪽 학과 표의 행을 모두 | 경영 줄의 학번·이름 | 7 |
| FULL OUTER JOIN | 양쪽의 짝 없는 행까지 모두 | 107 줄의 학과명 · 경영 줄의 학번·이름 | 8 |
3셀프 조인과 교차 조인
그림의 마지막 장면이 셀프 조인입니다. 셀프 조인(SELF JOIN)은 한 테이블을 별칭 둘로 두 번 써서 자기 자신과 잇습니다. 과목 표를 A·B 두 장으로 놓고 A의 선수과목을 B의 과목코드에 이으면, 운영체제 옆에 자료구조가, 네트워크 옆에 운영체제가 붙습니다. 자료구조는 선수과목이 NULL이라 짝이 없습니다.
쉼표 조인에서 WHERE 절의 조인 조건을 빠뜨리거나 조인 조건을 아예 걸지 않으면, 한쪽의 모든 행이 다른 쪽의 모든 행과 하나씩 이어집니다.
이 결과를 카티션 곱(Cartesian product)이라고 합니다.
CROSS JOIN으로 일부러 카티션 곱을 만드는 조인은 교차 조인(CROSS JOIN)이라고 부릅니다.
예제
결과 표는 Oracle에서 돌린 결과입니다.
네 조인은 INNER JOIN 자리의 키워드만 다릅니다.
행 순서를 정하려고 ORDER BY S.학번을 붙였습니다.
학번이 NULL인 경영 줄은 맨 아래에 옵니다. Oracle은 오름차순 정렬에서 NULL을 맨 뒤에 둡니다.
INNER JOIN
SELECT S.학번,
S.이름,
D.학과명
FROM 학생 S
INNER JOIN 학과 D
ON S.학과코드 = D.학과코드
ORDER BY S.학번;
| 학번 | 이름 | 학과명 |
|---|---|---|
| 101 | 김민수 | 컴퓨터 |
| 102 | 이서준 | 컴퓨터 |
| 103 | 김지민 | 컴퓨터 |
| 104 | 박서연 | 전자 |
| 105 | 최민준 | 전자 |
| 106 | 정하윤 | 전자 |
LEFT OUTER JOIN
SELECT S.학번,
S.이름,
D.학과명
FROM 학생 S
LEFT OUTER JOIN 학과 D
ON S.학과코드 = D.학과코드
ORDER BY S.학번;
| 학번 | 이름 | 학과명 |
|---|---|---|
| 101 | 김민수 | 컴퓨터 |
| 102 | 이서준 | 컴퓨터 |
| 103 | 김지민 | 컴퓨터 |
| 104 | 박서연 | 전자 |
| 105 | 최민준 | 전자 |
| 106 | 정하윤 | 전자 |
| 107 | 강도현 | NULL |
RIGHT OUTER JOIN
SELECT S.학번,
S.이름,
D.학과명
FROM 학생 S
RIGHT OUTER JOIN 학과 D
ON S.학과코드 = D.학과코드
ORDER BY S.학번;
| 학번 | 이름 | 학과명 |
|---|---|---|
| 101 | 김민수 | 컴퓨터 |
| 102 | 이서준 | 컴퓨터 |
| 103 | 김지민 | 컴퓨터 |
| 104 | 박서연 | 전자 |
| 105 | 최민준 | 전자 |
| 106 | 정하윤 | 전자 |
| NULL | NULL | 경영 |
FULL OUTER JOIN
SELECT S.학번,
S.이름,
D.학과명
FROM 학생 S
FULL OUTER JOIN 학과 D
ON S.학과코드 = D.학과코드
ORDER BY S.학번;
| 학번 | 이름 | 학과명 |
|---|---|---|
| 101 | 김민수 | 컴퓨터 |
| 102 | 이서준 | 컴퓨터 |
| 103 | 김지민 | 컴퓨터 |
| 104 | 박서연 | 전자 |
| 105 | 최민준 | 전자 |
| 106 | 정하윤 | 전자 |
| 107 | 강도현 | NULL |
| NULL | NULL | 경영 |
INNER와 OUTER는 생략할 수 있습니다.
JOIN은 INNER JOIN과, LEFT JOIN은 LEFT OUTER JOIN과 같습니다.
쉼표로 쓰는 조인
FROM 절에 테이블을 쉼표로 늘어놓고 조인 조건을 WHERE 절에 써도 내부 조인이 됩니다. 결과는 위 INNER JOIN과 같은 6행입니다.
SELECT S.학번,
S.이름,
D.학과명
FROM 학생 S, 학과 D
WHERE S.학과코드 = D.학과코드
ORDER BY S.학번;
셀프 조인
과목 표에 별칭 A와 B를 붙여 두 번 씁니다.
A는 과목 쪽, B는 선수과목 쪽입니다.
결과 열 이름이 겹치지 않게 B의 과목명에는 선수라는 별칭을 붙였습니다.
SELECT A.과목명,
B.과목명 AS 선수
FROM 과목 A
JOIN 과목 B
ON A.선수과목 = B.과목코드
ORDER BY A.과목코드;
| 과목명 | 선수 |
|---|---|
| 네트워크 | 운영체제 |
| 운영체제 | 자료구조 |
자료구조는 선수과목이 NULL이라 내부 조인에서 빠졌습니다. 과목코드 순서로 정렬하면 NW가 OS보다 앞이라 네트워크 줄이 먼저 나옵니다.
카티션 곱
학과와 수강은 함께 가진 열이 없어 이을 조건이 아예 없습니다. 그래도 카티션 곱은 만들어집니다. 아래는 경영(BA) 학과 줄만 골라 본 것입니다. 경영 한 줄이 수강 12행과 모두 이어집니다.
SELECT D.학과명,
E.학번,
E.과목코드
FROM 학과 D
CROSS JOIN 수강 E
WHERE D.학과코드 = 'BA'
ORDER BY E.학번, E.과목코드;
| 학과명 | 학번 | 과목코드 |
|---|---|---|
| 경영 | 101 | DS |
| 경영 | 101 | NW |
| 경영 | 101 | OS |
| 경영 | 102 | DS |
| 경영 | 102 | OS |
| 경영 | 103 | DS |
| 경영 | 103 | NW |
| 경영 | 104 | NW |
| 경영 | 104 | OS |
| 경영 | 105 | DS |
| 경영 | 105 | NW |
| 경영 | 107 | NW |
학과 셋이 모두 이렇게 이어지므로 조건을 걸지 않은 전체 결과는 3 × 12 = 36행입니다.
행 수는 두 테이블 행 수의 곱, 열 수는 두 테이블 열 수의 합입니다. SELECT *로 보면 열은 학과 3개와 수강 3개를 더한 6개입니다.
SELECT COUNT(*) AS 행수
FROM 학과 D
CROSS JOIN 수강 E;
| 행수 |
|---|
| 36 |
자연 조인
자연 조인(NATURAL JOIN)은 두 테이블에서 이름이 같은 열을 찾아 그 값이 같은 행끼리 저절로 잇습니다. 학생과 학과에서 이름이 같은 열은 학과코드 하나라서, 결과는 INNER JOIN과 같은 6행입니다. 잇는 데 쓴 학과코드 열은 결과에 한 번만 나옵니다.
SELECT 학번,
이름,
학과코드,
학과명
FROM 학생
NATURAL JOIN 학과
ORDER BY 학번;
| 학번 | 이름 | 학과코드 | 학과명 |
|---|---|---|---|
| 101 | 김민수 | CS | 컴퓨터 |
| 102 | 이서준 | CS | 컴퓨터 |
| 103 | 김지민 | CS | 컴퓨터 |
| 104 | 박서연 | EE | 전자 |
| 105 | 최민준 | EE | 전자 |
| 106 | 정하윤 | EE | 전자 |
잇는 열에는 테이블 이름이나 별칭을 붙이지 않습니다.
S.학과코드처럼 쓰면 Oracle은 ORA-25155 오류를 냅니다.
시험 포인트
- 필기: 조인 종류는 무엇을 남기는지로 가릅니다. INNER는 짝만, LEFT·RIGHT는 JOIN 키워드 왼쪽·오른쪽 테이블의 짝 없는 행까지, FULL은 양쪽의 짝 없는 행까지 남깁니다. 남은 행의 반대쪽 칸은 NULL입니다. 행 수를 묻는 문제는 내부 조인 행 수에 남긴 짝 없는 행을 더해 셉니다(6 → LEFT 7 · RIGHT 7 · FULL 8). 카티션 곱은 행 수가 두 테이블 행 수의 곱입니다. 자연 조인은 이름이 같은 열로 잇고 그 열을 한 번만 보입니다.
- 실기: 조인 결과를 NULL 칸과 행 순서까지 표로 씁니다. 이름은 내부 조인(INNER JOIN), 외부 조인(LEFT·RIGHT·FULL OUTER JOIN), 셀프 조인(SELF JOIN), 교차 조인(CROSS JOIN)으로 씁니다.
확인 문제
1. 학생 표(7행)와 수강 표(12행)를 학번으로 잇습니다. 수강 기록이 하나도 없는 학생은 106 정하윤 한 명이고, 수강 표의 학번은 모두 학생 표에 있습니다. 완전 외부 조인(FULL OUTER JOIN)의 결과가 내부 조인(INNER JOIN)의 결과와 어떻게 다른지 고르세요.
- ① 12행 · 내부 조인 결과와 같다
- ② 13행 · 106 줄이 하나 더 있고 수강 쪽 칸이 NULL이다
- ③ 14행 · 106 줄과 수강 쪽 짝 없는 줄이 하나씩 더 있다
- ④ 19행 · 학생 7행과 수강 12행을 그대로 이어 붙인다
정답
②입니다. 완전 외부 조인은 양쪽의 짝 없는 행을 모두 남깁니다. 여기서 짝이 없는 행은 학생 쪽 106 하나뿐이라, 내부 조인 12행에 106 줄이 하나 더해져 13행입니다. 그 줄의 과목코드·점수 칸은 NULL로 채워집니다.
- ① 외부 조인도 조인 조건이 맞는 짝만 남긴다고 봤습니다. 짝 없는 행을 남기는 것이 외부 조인과 내부 조인을 가르는 점입니다.
- ③ 양쪽에 짝 없는 행이 다 있다고 봤습니다. 수강 표의 학번은 모두 학생 표에 있어 수강 쪽에는 짝 없는 행이 없습니다.
- ④ 완전 외부 조인을 두 표를 위아래로 이어 붙이는 것으로 봤습니다. 조인은 짝지은 두 행을 옆으로 이어 한 줄로 만듭니다.
2. 다음 (가)~(라)에 맞는 조인의 이름을 한글 이름과 영문으로 함께 쓰세요. 답을 끝까지 쓴 뒤 정답을 펼치세요.
- (가) 수강 표와 과목 표를 과목코드로 이었더니 어느 칸에도 NULL이 없는 12행이 나왔습니다. 짝을 찾지 못한 행은 결과에서 빠집니다.
- (나) 수강 기록이 하나도 없는 학생까지 결과에 남기려고 학생 표를 JOIN 키워드 왼쪽에 두고 이었습니다. 짝 없는 줄의 수강 쪽 칸은 NULL입니다.
- (다) 과목 표 하나를 FROM 절에 두 번 적고 별칭을 달아, 선수과목 칸이 가리키는 과목의 과목명을 같은 줄에 붙였습니다.
- (라) 학생 7명과 수강 기록 12개를 조건 없이 모두 짝지어 84줄을 만들었습니다. 한쪽의 모든 행이 다른 쪽의 모든 행과 하나씩 이어집니다.
정답
받는 답: (가) 내부 조인(INNER JOIN) · (나) 왼쪽 외부 조인(LEFT OUTER JOIN) · (다) 셀프 조인(SELF JOIN) · (라) 교차 조인(CROSS JOIN)
(가)의 실마리는 '짝 없는 행이 결과에서 빠진다', (나)는 '한쪽 표의 행을 모두 남기고 반대쪽 칸을 NULL로 채운다', (다)는 '한 표를 두 번 적어 자기끼리 잇는다', (라)는 '조건 없이 모든 짝을 만든다'입니다.
- 같은 이름의 다른 꼴: (라) 카티션 곱(Cartesian product) — 교차 조인이 만드는 결과의 이름입니다. 문제가 조인의 이름을 물었습니다.
- 틀리는 꼴 오른쪽 외부 조인 — (나)에: JOIN 키워드 오른쪽 표의 행을 모두 남깁니다. (나)에서 모두 남길 학생 표는 왼쪽에 있습니다.
- 틀리는 꼴 자연 조인(NATURAL JOIN) — (다)에: 서로 다른 두 표를 이름이 같은 열로 잇는 조인입니다. (다)는 한 표를 두 번 적었습니다.
- 틀리는 꼴 내부 조인 — (라)에: 조인 조건이 맞는 짝만 남깁니다. (라)에는 조인 조건이 없습니다.
3. 아래 쿼리를 LEFT OUTER JOIN과 GROUP BY까지 처리하면 학생마다 다음 표처럼 묶입니다. (가)·(나)를 채운 뒤, HAVING까지 거친 결과를 표로 쓰세요.
| 이름 | 묶인 줄 수 | 과목코드가 NULL이 아닌 줄 수 |
|---|---|---|
| 강도현 | 1 | (가) |
| 김민수 | 3 | 3 |
| 김지민 | 2 | 2 |
| 박서연 | 2 | 2 |
| 이서준 | 2 | 2 |
| 정하윤 | 1 | (나) |
| 최민준 | 2 | 2 |
SELECT S.이름,
COUNT(E.과목코드) AS 과목수
FROM 학생 S
LEFT OUTER JOIN 수강 E
ON S.학번 = E.학번
GROUP BY S.이름
HAVING COUNT(E.과목코드) <= 1
ORDER BY S.이름;
정답
(가) 1 · (나) 0이고, HAVING이 과목 수가 1 이하인 묶음만 남겨 2행입니다.
| 이름 | 과목수 |
|---|---|
| 강도현 | 1 |
| 정하윤 | 0 |
강도현(107)은 수강 기록이 한 줄 있어 1입니다.
정하윤(106)은 수강 기록이 없지만 왼쪽 외부 조인이 줄 하나를 남기므로 묶음은 생깁니다. 그 줄의 과목코드는 NULL이고 COUNT(열)은 NULL을 세지 않아 0입니다.
COUNT(*)로 세면 정하윤도 1이 되어 두 사람을 가릴 수 없습니다.
더 자세히: SQLD의 조인 · 표준 조인 · 계층형 질의와 셀프 조인