
두 개 이상의 테이블을 연결해 데이터를 출력하는 것을 조인이라고 합니다. 조인은 관계형 데이터베이스의 핵심기능입니다.
일반적으로, 레코드는 PK 나 FK 값의 연관에 의해 조인이 가능합니다. 예를 들어, 선수 테이블과 팀 테이블이 있을 때 선수 테이블을 기준으로 필요한 데이터를 검색하고 선수 레코드와 연관된 팀 테이블의 특정 레코드를 찾아 연결하는 것이 조인입니다. 하지만 어떤 경우는 논리적인 값들의 연관만으로 조인이 성립될 수 있습니다.
조인 시 주의할 점은 FROM 절에 여러 테이블이 나열되더라도 SQL에서 데이터를 처리할 때는 단 두 개의 집합 간에만 조인이 일어난다는 것입니다. FROM 절에 A, B, C 테이블이 나열되면 특정 2개의 테이블을 먼저 조인하고 조인의 결과와 남은 한 개의 테이블을 다음 차례로 조인합니다. 예를 들어 A, B, C, D 4개의 테이블을 조인할 경우 옵티마이져는 (((A JOIN D) JOIN C) JOIN B)와 같이 순차적으로 조인합니다. 조인의 순서는 사용자 옵티마이저 힌트가 없으면 옵티마이저에 의해 결정됩니다.
'=' 연산자를 활용한 EQUI JOIN
EQUI 조인은 동치 연산자를 활용해 조인하는 방식입니다. 일반적인 형태는 아래와 같습니다.
SELECT TABLE1.COLUMN, TABLE2.CALUMN,...
FROM TABLE1, TABLE2
WHERE TABLE2.COLUMN = TABLE1.COLUMN;
= 연산자를 활용한 EQUE JOIN을 통해 선수 테이블과 팀 테이블에서 선수 이름과 소속된 팀의 이름을 출력합니다.
SELECT PLAYER.PRAYER_NAME, TEAM.TEAM_NAME
FROM PLAYER, TEAM
WHERE TEAM.TEAM_ID = PLAYER.TEAM_ID;
ANSI/ISO SQL 표준 방식의 INNER JOIN 문법을 활용한 EQUI 조인은 아래와 같습니다.
SELECT PLAYER.PRAYER_NAME, TEAM.TEAM_NAME
FROM PLAYER INNER JOIN TEAM
ON TEAM.TEAM_ID = PLAYER.TEAM_ID;
위 SQL을 보면 SELECT 구문에 단순히 컬럼명이 오지 않고 TABLE.COLUMN 형태로 함께 기술합니다. 이렇게 특정 칼럼에 접근하기 위해 그 칼럼이 어느 테이블에 존재하는 칼럼인지 명시하는 것은 두 가지 이유가 있습니다.
첫번째로 조인에 사용되는 여러 개의 테이블에 같은 칼럼명이 존재하는 경우, DBMS는 어떤 칼럼을 사용해야 할지 모르기 때문에 파싱 단계에서 오류가 발생합니다. 두 번째로 개발자나 사용자가 조회할 데이터가 어느 테이블에 있는 칼럼인지 쉽게 파악하도록 하여 가독성과 유지보수성을 높입니다. 이런 이유로 여러 테이블의 조인을 포함한 SQL 문장에서는 칼럼명 앞에 테이블 명을 사용하는 것이 권장됩니다.
조인 조건에 맞는 데이터만 출력하는 INNER JOIN 에 참여하는 대상 테이블이 N개 라고 했을 때, 필요한 조인 조건의 수는 대상 테이블의 수에서 하나를 뺀 N-1개 이상의 조건입니다. (예외적으로 옵티마이저가 일부 조인 조건을 실행계획 수립 단계에서 추가할 수도 있습니다.)
조인 조건은 WHERE 절에 기술합니다.
선수-팀 EQUI-JOIN 사례를 통해 알아보는 조인
선수 테이블과 팀 테이블에서 선수명과 백넘버 그리고 그 선수가 소속돼 있는 팀명 및 연고지를 조회합니다. 테이블 간의 관계는 아래와 같습니다.
- 선수 테이블에 있는 소속팀 ID 칼럼이 팀 테이블의 팀 ID 칼럼과 FK 관계에 있다.
SELECT PLAYER.PLAYER_NAME, PLAYER.BACK_NO, PLAYER.TEAM_ID, TEAM.TEAM_NAME, TEAM.REGISON_NAME
FROM PLAYER, TEAM
WHERE TEAM.TEAM_ID = PLAYER.TEAM_ID;
위 예제를 확인하면 테이블명이 컬럼 앞에 반복해 위치합니다. 긴 테이블명을 계속 되풀이해 입력하면 입력을 실수할 가능성이 높아집니다. 그래서 SELECT 절에서 칼럼에 대한 ALIAS를 사용하는 것처럼, FROM 절의 테이블에 대해 ALIAS를 사용할 수 있습니다.
-- 칼럼과 테이블에 ALIAS를 적용해 위 SQL을 수정한다.
SELECT A.PLAYER_NAME, A.BACK_NO, A.TEAM_ID, B.TEAM_NAME, B.REGION_NAME
FROM PLAYER A, TEAM B
WHERE B.TEAM_ID = A.TEAM_ID;
선수-팀 WHERE 절 검색 조건 사례
WHERE 절에 조인 조건 이외의 검색 조건을 덧붙여 사용할 수 있습니다. EQUI JOIN의 최소 연관 관계를 위해 '테이블 개수 - 1' 개의 조인 조건을 WHERE 절에 명시하고, 논리 연산자와 함께 추가적인 제한 조건을 입력할 수 있습니다.
SELECT A.PLAYER_NAME, B.BACK_NO
B.REGION_NAME, B.TEAM_NAME
FROM PLAYER A, TEAM B
WHERE A.POSITION = 'GK'
AND B.TEAM_ID = A.TEAM_ID
ORDER BY A.BACK_NO
만약 테이블에 대한 ALIAS를 적용한 경우, WHERE 절과 SELECT 절에는 테이블명이 아닌 테이블에 대한 ALIAS를 사용해야 합니다.
팀-경기장 EQUI 조인 사례
팀 테이블과 경기장 테이블의 관계를 이용해 소속팀이 갖고있는 전용구장 정보를 팀 정보와 함께 출력합니다.
SELECT A.REGION_NAME, A.TEAM_NAME, A.STADIUM_ID, B.STADIUM_NAME, B.SEAT_COUNT
FROM TEAM A, STADIUA B
WHERE B.STADIUM_ID = A.STADIUM_ID;
조인 하려는 테이블의 중복되지 않는 칼럼의 경우 ALIAS를 사용하지 않아도 오류가 발생하지 않는다. 하지만 가독성 및 일관성 측면에서 일괄적으로 ALIAS를 사용하는 것이 바람직합니다.
Non EQUI JOIN
NON EQUI JOIN은 두 개의 테이블 간에 논리적인 연관 관계를 갖고 있으나, 칼럼 값들이 서로 일치하지 않는 경우 사용합니다. NON EQUI JOIN의 경우에는 "=" 연산자가 아닌 다른 연산자를 사용해 JOIN을 수행합니다. 두 테이블 간에 칼럼 값들이 서로 정확하게 일치하지 않는 경우에는 EQUI JOIN을 사용할 수 없습니다. 이런 경우 NON EQUI JOIN을 시도할 수 있으나 데이터 모델에 따라 불가능한 경우도 있습니다. NON EQUI JOIN의 대략적인 형태는 다음과 같습니다. 조인조건으로 BETWEEN 연산자를 사용합니다.
-- SALGRADE 테이블에는 LOSAL / HISAL 을 기준으로 5개의 급여등급이 존재한다.
SELECT A.ENAME, A.JOB, A.SAL, B.GRADE
FROM EMP.A, SALGRADE B
WHERE A.SAL BETWEEN B.LOSAL AND B.HISAL;
3개 이상 TABLE JOIN
선수, 팀, 경기장 3개의 테이블을 조인하려고 합니다. 선수 테이블과 운동장 테이블이 서로 관계가 없으므로 중간에 팀 테이블이라는 서로 연관관계가 있는 테이블에 추가해 세 개의 테이블을 조인해야만 원하는 데이터를 얻을 수 있습니다. 선수 테이블의 소속팀 id가 팀 테이블의 팀 id와 PK-FK 관계가 있음을 알 수 있습니다. 세 개의 테이블에 대한 조인이므로 where 절에 2개 이상의 조인 조건이 필요합니다.
SELECT A.PLAYER_NAME, A.POSITION
,B.REGION_NAME, B.TEAM_NAME
,C.STADIUM_NAME
FROM PLAYER A, TEAM B, STADIUM C
WHERE B.TEAM_ID = A.TEAM_ID
AND C.STADIUM_ID = B.STADIUM_ID
ORDER BY 선수명;
OUTER JOIN
EQUI JOIN, NON EQUI JOIN 은 모두 조인 조건의 결과가 참인 행들만 반환하는 INNER 조인입니다. OUTER JOIN은 조인 조건을 만족하지 않는 행들도 함께 반환합니다.
TABLE1과 TABLE2를 OUTER JOIN 조인하면 TABLE1을 기준으로 TABLE2에 JOIN 할 데이터가 있으면 TABLE2의 데이터를 함께 출력하고, TABLE2에 조인 조건에 해당하는 데이터가 없어도 TABLE1의 모든 데이터를 표시합니다.
TABLE1의 모든 레코드에 대해 TABLE2의 데이터가 반드시 존재한다는 보장이 없지만 TABLE1의 레코드는 모두 결과집합에 포함되야한다면 OUTER JOIN을 사용해 해결할 수 있습니다. ORACLE은 다음과 같이 조인조건의 조인대상 칼럼 뒤에 (+) 기호를 표시해 OUTER JOIN 합니다.
SELECT TABLE1.COLUMN, TABLE2.COLUMN...
FROM TABLE1, TABLE2
WHERE TABLE2.COLUMN(+) = TABLE1.COLUMN;
주의해야할 점은 (+)의 기호 위치입니다. 위의 예시에서 OUTER JOIN의 기준이 되는 테이블(조인할 데이터가 없는 경우에도 모든 데이터를 표시하는 테이블)은 TABLE1이다. 즉, (+) 표시의 반대편에 있는 테이블이 OUTER JOIN의 기준 테이블이 됩니다. OUTER JOIN 의 결과에서 조인에 성공한 행들은 INNER JOIN과 동일하게 조인에 참여한 각 테이블의 칼럼들이 표시됩니다. 조인에 실패한 행들의 경우, 기준 테이블의 값은 표시되고 대상 테이블의 칼럼은 NULL로 표시됩니다.
STADIUM에 등록된 경기장 중에는 홈팀이 없는 경기장도 있습니다. STADIUM과 TEAM을 조인하되 홈팀이 없는 경기장의 정보도 같이 출력하도록 합니다. 이때 OUTER JOIN을 활용합니다.
-- INNER JOIN이라면 홈팀이 배정된 15개의 경기장만 출력됐겠지만, OUTER JOIN 을 사용했기 때문에 홈팀이 없는 경기장의 정보까지 추가로 출력됐다.
SELECT A.STADIUM_NAME, A.STADIUM_ID, A.SETA_COUNT, A.HOMETEAM_ID, B.TEAM_NAME
FROM STADIUM A, TEAM B
WHERE A.TEAM_ID = B.TEAM_ID(+)
ORDER BY A.HOMETEAM_ID;
마찬가지로 DEPT에 등록된 부서 중에는 사원이 없는 부서도 있습니다. DEPT와 EMP를 조인하되 사원이 없는 부서 정보도 같이 출력합니다.
-- 사원이 배정되지 않은 부서의 정보까지 출력됐다.
SELECT A.EMPLOYEE_ID , B.DEPARTMENT_NAME
FROM HR.DEPARTMENTS A, HR.EMPLOYEES B
WHERE A.DEPARTMENT_ID; = B.DEPARTMENT_ID(+)
조인이 필요한 기본적인 이유는 정규화를 위해서입니다. 정규화란 불필요한 데이터의 정합성을 확보하고 이상현상을 피하기위해 테이블을 분할해 생성하는 작업을 말합니다. 하나의 테이블에 모든 데이터를 집중시켜 놓고 그 테이블로부터 필요한 데이터를 조회하면 데이터 정합성에 더 큰 비용을 지불해야 하고, 데이터를 추가, 삭제, 수정하는 작업에 상당한 노력이 필요합니다. 성능 측면에서도 간단한 데이터를 조회하기 위해 규모가 큰 테이블에서 필요한 데이터를 찾아야 하므로 오히려 검색 속도가 떨어집니다.
테이블을 정규화하면 위와 같은 문제는 자연스럽게 해결된다. 그렇지만 특정 요구 사항을 만족하는 데이터들을 분할된 테이블로 부터 조회하기 위해서는 테이블 간 논리적인 연관관계가 필요합니다. 그런 관계성을 통해 다양한 데이터를 출력할 수 있습니다. 이런 논리적인 관계를 구체적으로 표현하는 것이 조인입니다.