[ORACLE] 여러 테이블 조회를 위한 테이블 조인

[ORACLE] 여러 테이블 조회를 위한 테이블 조인

여러 테이블 조회를 위한 테이블 조인


TABLE JOIN


개념


  • 조인을 사용하여 둘 이상의 테이블에서 데이터를 한 번에 쿼리할 수 있다.
  • N개의 테이블 JOIN 시, 반드시 N-1 개의 조인 조건이 요구된다.
  • 다음은 표준 SQL:1999에서 제공하는 조인 기법이다.
SELECT  table1.column, table2.column
  FROM  table1
[NATURAL JOIN table2] |
[JOIN table2 USING (column_name)] |
[JOIN table2 ON (table1.column_name = table2.column_name)] |
[LEFT|RIGHT|FULL OUTER JOIN table2 ON (table1.column_name = table2.column_name)] |
[CROSS JOIN table2];

아래 예시를 보자.

SELECT  e.employee_id, e.last_name, e.department_id,
        d.department_id, d.location_id
  FROM  employees e JOIN departments d -- 'e'와 'd'는 ALIAS 이다.
    ON  (e.department_id = d.department_id); -- ON 은 조인조건 절이다.

-- e 테이블의 department_id 와 d 테이블의 department_id 가 같은 값만 JOIN 한다.
 EMPLOYEE_IDLAST_NAMEDEPARTMENT_IDDEPARTMENT_ID_1LOCATION_ID
1200Whalen10101700
2201Hartstein20201800
3202Fay20201800
4124Mourgos50501500
5141Rajs50501500
     
19206Gietz1101101700

`employees’ 테이블에는 20명의 사원이 있고, ‘departments’ 테이블에는 8개의 부서가 존재한다.

위 쿼리에서 부서 번호가 같은 값만 요청했기 때문에, 그에 해당하는 값만 출력된다.

위 쿼리 출력 결과 19개의 데이터 행이 출력되었는데, 한 명의 사원이 쿼리 결과에서 제외된 것을 알 수 있다.

해당 사원은 ‘대기발령자’로, 부서 번호가 NULL 이기 때문에 조인 조건에서 걸러져 제외되었다.

만약에 해당 부서가 어떤 부서인지 알려면 어떻게 해야 할까?

위 쿼리문의 SELECT 절에서 ‘d.department_name’ 을 추가하면 될 것이다.

원래 각 부서에 대한 자세한 정보는 ‘employees’ 테이블에서는 조회가 불가능하다.

하지만 JOIN 을 통해 ‘departments’ 테이블을 연결해서 출력하여 마치 하나의 테이블에서 조회한 것 처럼 할 수 있다.

ON 을 쓰지 않을 경우, 모든 ‘e’테이블의 행이 모든 ‘d’테이블의 행과 연결된다.

이 때 그냥 ON 절을 생략하면 오류가 발생한다.

표준 SQL에서는 JOIN 을 사용할 때 ON 이 필수적으로 있어야 하기 때문인데, JOIN, 로 바꾸면 된다.

아니면 CROSS JOIN 이라는 ORACLE 전용 SQL로 바꾸어도 된다.

결과로는 20명의 사원 X 8개의 부서 = 총 160건의 행이 출력된다.

다시 위 표준 SQL JOIN 기법을 보자.

NATURAL JOIN 시 ‘table1’과 ‘table2’에 동일한 이름의 칼럼이 존재한다면, 이 칼럼들로 “동등 조인”이라는 것을 수행한다.

이는 조인 조건을 필요로 하지 않는다.

설령 데이터가 같더라도, 칼럼명이 다르면 안된다.

OUTER JOIN 은 동등 조인에서 누락되는 데이터, 즉 왼쪽에는 있는데 오른쪽에는 없거나, 오른쪽에는 있는데 왼쪽에는 없는 데이터를 포함시킨다. 왼쪽, 오른쪽, 모두는 옵션으로 지정할 수 있다.

기본적으로 NATURAL JOIN 과 마찬가지로 동등 조인을 수행한다.

Oracle 전용 SQL


SELECT  e.employee_id, e.last_name, e.department_id,
        d.department_id, d.location_id
  FROM  employees e , departments d
 WHERE  e.department_id = d.department_id;

JOIN 대신에 , 가, 조인 조건으로 ON 대신에 WHERE 가 쓰였다.

Join 처리 시 Alias 사용


  • 테이블 접두어를 사용하여 여러 테이블에 있는 열 이름을 한정
  • 테이블 접두어를 사용하여 성능 향상
  • 테이블 alias로 테이블에 짧은 이름을 지정
    • SQL 코드 크기를 줄여 메모리를 적게 사용
  • 유의사항
    • 테이블 alias는 30자까지 사용할 수 있으면 길이는 짧을수록 좋다
    • FROM 절의 특정 테이블 이름에 대해 테이블 alias가 사용될 경우 SELECT 문 전체에서 테이블 이름 대신 해당 테이블 alias를 사용해야 한다
    • 테이블 alias는 의미 있는 단어 선택
    • 테이블 alias는 현재 SELECT 문에 대해서만 유효

Natural Join


  • NATURAL JOIN 절은 이름이 같은 두 테이블의 모든 행을 기반으로 한다
  • 이 절은 두 테이블에서 대응되는 모든 열의 값이 동일한 행을 선택
  • 동일한 이름을 가진 열이 서로 다른 데이터 유형을 가지면 오류 발생
SELECT  department_id, department_name, location_id, city
  FROM  departments NATURAL JOIN locations;
 DEPARTMENT_IDDEPARTMENT_NAMELOCATION_IDCITY
160IT1400Southlake
250Shipping1500South San Francisco
310Administration1700Seatle
490Executive1700Seatle

NATURAL JOIN 은 ‘departments’ 테이블과 locations 테이블에서 공통 칼럼인 ‘location_id’를 찾아서 이를 기준으로 동등 조인을 해준다.

사전에 테이블의 관계와 구조를 알고 있다면, NATURAL JOIN 을 통해 쉽게 조인을 할 수 있다.

Using절을 이용한 Join


  • 여러 열이 이름은 같지만 데이터 유형은 다를 경우 USING 절을 사용하여 Equijoin에 대한 열을 지정할 수 있다.
  • USING 절을 사용하면 두 개 이상의 열이 일치하는 경우 하나의 열만 일치하도록 할 수 있다.
SELECT  employee_id, last_name,
        location_id, department_id
  FROM  employees JOIN departments
 USING  (department_id);
 EMPLOYEE_IDLAST_NAMELOCATION_IDDEPARTMENT_ID
1200Whalen170010
2201Hartstein180020
3202Fay180020
4144Vargas150050
5143Matos150050

USING 절에는 두 테이블의 공통 칼럼을 넣어준다.

이 때 주의할 점이 있다.

다음과 같이 USING 절에 사용되는 열은 alias 지정 시 오류가 발생한다.

USING (e.department_id)

두 테이블에 걸쳐 공통된 칼럼이기 때문에, 특정 alias 를 사용하면 에러가 난다고 이해하면 되겠다.

Natural VS Using


  • NATURAL JOIN은 공통된 칼럼이 있으면 자연스럽게 동등 조인을 해주는 것이고, USING을 사용한 JOIN은 두 개의 테이블에서 공통된 칼럼을 직접 명시해주는 것이다.
  • 만약에 두 테이블에 이름이 같은 칼럼이 두 개 이상 있을 경우, NATURAL JOIN에서는 성립하지 않는다. 이 때는 USING을 사용하여 JOIN을 해야 한다.
    • 이 경우 USING 에 공통된 칼럼들을 다 명시하는 방법도 있지만 특정 칼럼만을 명시할 수도 있을 것이다.

On 절을 이용한 Join


  • ON 절을 사용하여 임의 조건을 지정하거나 조인할 열을 지정
  • ON 절을 사용하여 조인 조건을 지정. 이렇게 하면 WHERE 절의 검색 또는 필터 조건과 별도로 조인 조건을 지정할 수 있다.
SELECT  e.employee_id, e.last_name, e.department_id,
        d.department_id, d.location_id
  FROM  employees e JOIN departments d 
    ON  (e.department_id = d.department_id);

ON 절을 이용한 JOIN 은 동등 조인의 표준이라고 할 수 있다.

추가로 조건을 넣고 싶다면, AND 를 사용하거나 WHERE 을 넣어도 상관없다.

On 절로 3-way Join 생성


SELECT  employee_id, city, department_name
  FROM  employees e
  JOIN  departments d
    ON  d.department_id = e.department_id
  JOIN  locations l
    ON  d.location_id = l.location_id;
 EMPLOYEE_IDCITYDEPARTMENT_NAME
1100SeatleExecutive
2101SeatleExecutive
3102SeatleExecutive
4103SouthlakeIT
5104SouthlakeIT

3개의 테이블을 조인하기 때문에, 2개의 조인 조건 요구된다.

Oracle 전용 SQL


SELECT  employee_id, city, department_name
  FROM  employees e, departments d, locations l
 WHERE  e.department_id = d.department_id;
   AND  d.location_id = l.location_id

Oracle 전용 SQL에서는 3개의 테이블을 모두 FROM 절에 나열한다.

그리고 조인 조건은 WHERE 절에 기술한다.

Using 을 사용한 SQL


SELECT  e.employee_id, l.city, d.department_name
  FROM  employees e
  JOIN  departments d
 USING  (department_id)
  JOIN  locations l
 USING  (location_id)

Self Join


  • EMPLOYEES (WORKER)

    EMPLOYEE_IDLAST_NAMEMANAGER_ID
    200Whalen101
    201Hartstein100
    205Fay201
    206Higgins105
  • EMPLOYEES (MANAGER)

    EMPLOYEE_IDLAST_NAME
    200Whalen
    201Hartstein
    202Fay
    205Higgins

WORKER 테이블의 MANAGER_ID는 MANAGER 테이블의 EMPLOYEE_ID와 같다.

즉 EMPLOYEES 테이블은 자기 자신을 참조하는 테이블이다.

SELECT  worker.last_name emp, manager.last_name mgr
  FROM  employees worker JOIN employees manager
    ON  (worker.manager_id = manager.employee_id);
 EMPMGR
1HunoldDe Haan
2FayHartstein
3GietzHiggins
4LorentzHunold
5ErnstHunold

이를 재귀, 순환 모델을 가진다고 한다.

이는 다음과 같이 활용할 수 있다.

-- 누가 누구에게 보고하는지 출력
SELECT  e.last_name || ' report to ' || m.last_name
  FROM  employees e JOIN employees m
    ON  (e.manager_id = m.employee_id);
-- 자신의 상사보다 먼저 입사한 사원을 출력
SELECT  w.last_name, w.hire_date, m.last_name, m.hire_date
  FROM  employees w, employees m
 WHERE  w. manager_id = m.employee_id
   AND  w.hire_date < m.hire_date;

비 동등 조인


동등(=)이 아닌, 비 동등에서의 조인이다.

  • EMPLOYEES

     LAST_NAMESALARY
    1Whalen4400
    2Hartstein13000
    3Fay6000
    4Higgins12000
    5Gietz8300
  • JOB_GRADES

     GRADE_LEVELLOWEST_SALHIGHEST_SAL
    1A10002999
    2B30005999
    3C60009999
    4D1000014999
    5E1500024999
    6F2500040000

‘JOB_GRADES’ 테이블은 각 ‘GRADE_LEVEL’에 대해 ‘LOWEST_SAL’ 및 ‘HIGHEST_SAL’ 값의 범위를 정의한다.

따라서 ‘GRADE_LEVEL’ 열을 사용하여 각 사원에 등급을 지정할 수 있다.

이 때 ‘EMPLOYEES’ 테이블의 ‘SALARY’ 칼럼과 ‘JOB_GRADES’ 테이블의 ‘LOWEST_SAL’, ‘HIGHEST_SAL’ 칼럼이 비 동등 조인된다.

SELECT  e.last_name, e.salary, j.grade_level
  FROM  employees e JOIN job_grades j
    ON  e.salary BETWEEN j.lowest_sal AND j.highest_sal;
 LAST_NAMESALARYGRADE_LEVEL
1Vargas2500A
2Matos2600A
3Davies3100B
4Rajs3500B
5Lorentz4200B

Outer Join으로 직접 일치하지 않는 행 반환


  • DEPARTMENTS

     DEPARTMENT_NAMEDEPARTMENT_ID
    1Administration10
    2Marketing20
    3Shipping50
    4IT60
      
    8Contracting190

    부서 190에는 사원이 없다.

  • Equijoin with EMPLOYEES

     DEPARTMENT_IDLAST_NAME
    110Whalen
    220Hartstein
    320Fay
    4110Higgins
      
    1980Taylor

    “Grant” 사원에게는 부서 ID가 할당되지 않았다.

즉 동등 조인에서 ‘DEPARTMENTS’ 테이블의 “Contracting”과, ‘EMPLOYEES’ 테이블의 “Grant”가 누락되었다.

그 누락된 데이터를 포함시키는 것이 OUTER JOIN 이다.

Left Outer Join


SELECT  e.last_name, e.department_id, d.department_name
  FROM  employees e LEFT OUTER JOIN departments d
    ON  (e.department_id = d.department_id);
 LAST_NAMEDEPARTMENT_IDDEPARTMENT_NAME
1Whalen10Administration
2Fay20Marketing
3Hartstein20Marketing
4Vargas50Shipping
   
20Grant(null)(null)

LEFT OUTER JOIN 의 경우 왼쪽에 있는 테이블에는 존재하고, 오른쪽에 있는 테이블에는 존재하지 않는 데이터를 포함한다.

즉, 20번 행에서 ‘employees’ 테이블에 존재하는 “Grant”가 포함된다.

Oracle 전용 SQL


SELECT  e.last_name, e.department_id, d.department_name
  FROM  employees e, departments d
 WHERE  e.department_id = d.department_id(+);

Oracle 전용 SQL에서는 반대 방향에 (+) 기호를 넣는다.

Right Outer Join


SELECT  e.last_name, e.department_id, d.department_name
  FROM  employees e RIGHT OUTER JOIN departments d
    ON  (e.department_id = d.department_id);
 LAST_NAMEDEPARTMENT_IDDEPARTMENT_NAME
1Whalen10Administration
2Fay20Marketing
3Hartstein20Marketing
4Vargas50Shipping
   
20(null)190Contracting

RIGHT OUTER JOIN 의 경우 오른쪽에 있는 테이블에는 존재하고, 왼쪽에 있는 테이블에는 존재하지 않는 데이터를 포함한다.

즉, 20번 행에서 ‘departments’ 테이블에 존재하는 “Contracting”이 포함된다.

Oracle 전용 SQL


SELECT  e.last_name, e.department_id, d.department_name
  FROM  employees e, departments d
 WHERE  e.department_id(+) = d.department_id;

마찬가지로 반대 방향에 (+) 기호를 넣는다.

Full Outer Join


SELECT  e.last_name, e.department_id, d.department_name
  FROM  employees e FULL OUTER JOIN departments d
    ON  (e.department_id = d.department_id);
 LAST_NAMEDEPARTMENT_IDDEPARTMENT_NAME
1Whalen10Administration
2Fay20Marketing
3Hartstein20Marketing
4Vargas50Shipping
   
20Grant(null)(null)
21(null)190Contracting

FULL OUTER JOIN 은 양쪽에 매칭되지 않는(Both Not Match) 데이터를 포함한다.

따라서 21번 행까지 출력된다.

Oracle 전용 SQL


위와 마찬가지로 생각했을때 양쪽에 (+) 기호를 넣으면 될 것 같지만, 이는 불가능하다.

Oracle에서는 애시당초 FULL OUTER JOIN 이 없었기 때문에, 다음과 같이 표현해야한다.

SELECT  e.last_name, e.department_id, d.department_name
  FROM  employees e, departments d
 WHERE  e.department_id(+) = d.department_id

union -- 중복된 값을 배제한 합집합

SELECT  e.last_name, e.department_id, d.department_name
  FROM  employees e, departments d
 WHERE  e.department_id = d.department_id(+);

Cartesian Product = Cross Join


CROSS JOIN 을 사용하여 카티션 프로덕트를 생성할 수 있다.
Oracle Syntax, ANSI 모두 카티션 프로덕트 생성이 가능하다.

  • 다음과 같은 경우 Cartesian Product 생성
    • 조인 조건이 생략된 경우
    • 조인 조건이 잘못된 경우
    • 첫 번째 테이블의 모든 행이 두 번째 테이블의 모든 행에 조인되는 경우
  • Cartesian Product가 생성되지 않게 하려면 반드시 유효한 조인 조건을 포함해야 한다.

© 2022. All rights reserved. 신동민의 블로그