[아이티센 부트캠프] 13일차 (MySQL 3)

이언덕·2026년 4월 1일

아이티센 부트캠프

목록 보기
34/115
post-thumbnail

1. 결과 집합 결합과 테이블 연결 (SET Operator & JOIN)

개념

앞에서는 한 테이블 안에서 데이터를 조회하고, 조건을 걸고, 정렬하고, 집계하는 흐름을 봤다.
이제는 한 단계 더 나아가서 여러 조회 결과를 합치거나, 여러 테이블을 연결해서 함께 보는 방법을 알아야 한다.

이 파트에서 먼저 구분해야 할 것은 두 가지다.

  • UNION / UNION ALL
    → 두 쿼리 결과를 세로로 합치는 방식

  • JOIN
    → 두 개 이상의 테이블을 가로로 연결하는 방식

UNION / UNION ALL은 두 쿼리 결과를 행으로 합치는 것이고, JOIN은 두 개 이상의 테이블을 가로로 묶어서 하나의 결과 집합으로 만드는 것이다.

즉, 이 파트의 핵심은
결과를 합치는 것과, 테이블을 연결하는 것을 구분해서 이해하는 것이다.



1. UNION과 UNION ALL

개념

UNION과 UNION ALL은 두 개의 SELECT 결과를 하나로 합치는 구문이다.
이때 중요한 점은 결과를 옆으로 붙이는 것이 아니라, 아래로 이어 붙인다는 것이다.

즉,

  • 첫 번째 SELECT 결과
  • 두 번째 SELECT 결과

를 하나의 결과 집합으로 세로 방향으로 합친다.
UNION / UNION ALL은 두 쿼리 결과를 행으로 합치는 것, 즉 세로로 묶는 방식이다.

UNION / UNION ALL로 결과를 합치려면, 두 SELECT 결과의 컬럼 개수와 순서는 같아야 하고, 각 위치의 데이터도 서로 비교 가능한 형식이어야 한다.

처음에는 같은 컬럼 하나를 조회하는 예제로 보면
UNION의 핵심인 결과를 세로로 합친다는 점이 가장 잘 보인다.

UNION과 UNION ALL의 차이

둘의 차이는 중복 처리 방식이다.

  • UNION
    → 양쪽 쿼리 결과를 합치되, 중복 결과는 한 번만 남긴다

  • UNION ALL
    → 양쪽 쿼리 결과를 합칠 때, 중복 결과도 그대로 모두 보여준다

즉,
UNION은 중복을 정리해서 보여주는 방식이고,
UNION ALL은 결과를 있는 그대로 전부 이어 붙이는 방식이다.

세로로 합친다는 뜻 이해하기

UNION / UNION ALL은 테이블끼리 관계를 따라 연결하는 문장이 아니다.
이미 만들어진 두 SELECT 결과를 하나의 결과표로 이어 붙이는 문장이다.

예를 들어 첫 번째 SELECT가 4행, 두 번째 SELECT가 4행을 반환하면,
결과는 그 8행이 아래로 이어진 하나의 표가 된다.

즉, UNION은
결과와 결과를 합치는 방식이라고 이해하면 된다.

예제: 부서가 다른 직원 이름을 하나의 결과로 합치기

select ename
from emp
where deptno = 10

union

select ename
from emp
where deptno = 20;

이 쿼리는 부서 10 직원 이름 결과와
부서 20 직원 이름 결과를 하나의 결과로 합쳐서 보여준다.

여기서 UNION을 쓰면
두 결과에 같은 값이 있더라도 한 번만 남는다.

예제: 중복까지 그대로 보고 싶을 때

select ename
from emp
where deptno = 10

union all

select ename
from emp
where deptno = 20;

이번에는 UNION ALL을 썼기 때문에
중복이 있어도 제거하지 않고 그대로 보여준다.

즉,

  • 결과를 정리해서 보고 싶으면 UNION
  • 결과를 있는 그대로 다 보고 싶으면 UNION ALL

이렇게 구분하면 된다.

헷갈리기 쉬운 부분

UNION은 JOIN과 다르다.

  • UNION
    → 결과를 세로로 합친다

  • JOIN
    → 테이블을 가로로 연결한다

또 UNION은 중복을 제거하고,
UNION ALL은 중복도 그대로 포함한다.

즉, UNION / UNION ALL은
결과 집합을 합치는 문장이라고 이해하면 된다.



2. JOIN이 왜 필요한가

개념

관계형 데이터베이스는 보통 모든 정보를 한 테이블에 몰아넣지 않는다.
왜냐하면 그렇게 하면 중복이 많아지고, 빈칸이 늘어나고, 수정도 불편해지기 때문이다.

데이터베이스의 테이블은 중복과 공간 낭비를 줄이고 데이터 무결성을 지키기 위해 여러 개로 나누어 저장되며, 이렇게 분리된 테이블은 서로 관계를 가진다. 1:N 관계가 가장 보편적이다.

즉, 먼저 테이블을 나누고,
필요할 때 다시 연결해서 함께 보는 방식이 관계형 데이터베이스의 기본 흐름이다.

하나의 표에 다 넣으면 왜 불편한가

처음에는 고객 정보와 구매 정보를 한 표에 다 넣는 것이 편해 보일 수 있다.
하지만 이렇게 되면 고객 정보와 구매 정보가 한 줄에 같이 섞이게 된다.

고객 이름, 주소, 연락처 같은 정보는 고객이 바뀌지 않는 이상 그대로인데,
구매 내역이 추가될 때마다 이 정보가 계속 반복될 수 있다.

공간 낭비가 왜 생기는가

이렇게 한 표에 다 넣으면
구매 내역이 없는 고객은 오른쪽 구매 관련 칸이 비게 된다.

즉, 데이터가 채워진 부분과 비어 있는 부분이 섞이면서
불필요한 빈칸이 많아지고, 표 구조도 비효율적이 된다.

그래서 테이블을 나눈다

그래서 관계형 데이터베이스에서는
고객 정보와 구매 정보를 역할에 따라 테이블을 나눈다.

예를 들면

  • 고객 정보는 고객 테이블
  • 구매 정보는 구매 테이블

이렇게 분리한다.

이렇게 하면

  • 고객 정보 중복이 줄고
  • 공간 낭비가 줄고
  • 수정도 쉬워진다

즉, 테이블을 나누는 이유는
중복 제거와 구조 정리에 있다.

나눈 뒤에는 다시 연결해야 한다

테이블을 나누면 정보가 정리되는 대신,
필요할 때는 다시 연결해서 봐야 한다.

이때 연결 기준이 되는 것이 PK와 FK다.

  • PK
    → 각 행을 구분하는 기준값

  • FK
    → 다른 테이블의 기준값을 참조하는 연결값

즉, 고객 테이블과 구매 테이블을 나눈 뒤에는
PK와 FK를 통해 “누가 무엇을 샀는지”를 다시 연결해서 볼 수 있다.

이때 JOIN이 필요해진다.


3. JOIN이란 무엇인가

개념

JOIN은 두 개 이상의 테이블을 가로로 연결해서 하나의 결과로 보는 구문이다.
예를 들어 직원 정보는 emp 테이블에 있고, 부서 정보는 dept 테이블에 있을 때,
직원 이름과 부서명을 함께 보려면 두 테이블을 연결해야 한다.

JOIN은 서로 관련 있는 컬럼을 기준으로
두 테이블의 정보를 한 줄에서 함께 보게 만드는 방식이라고 이해하면 된다.

가로로 연결한다는 뜻

이 그림에서 봐야 할 포인트는 단순하다.

  • UNION
    → 아래로 붙음

  • JOIN
    → 옆으로 붙음

즉, JOIN은
서로 관련 있는 컬럼을 기준으로
왼쪽 테이블과 오른쪽 테이블의 정보를 한 줄에 같이 보여주는 방식이다.

실제로 어떤 테이블들이 연결되는가

JOIN을 이해할 때는
실제로 어떤 테이블이 어떤 키로 연결되는지 보는 것도 중요하다.

예를 들어

  • emp
    → 직원 정보

  • dept
    → 부서 정보

  • locations
    → 지역 정보

이렇게 나뉘어 있다면,
직원이 어느 부서에 속해 있는지,
그 부서가 어느 지역에 있는지까지
테이블을 따라가며 연결해서 볼 수 있다.

즉, ER 다이어그램은
나중에 어떤 컬럼으로 JOIN해야 하는지 알려 주는 구조도라고 보면 된다.


4. INNER JOIN

개념

INNER JOIN은 두 테이블에서 조인 조건에 맞는 행만 남기는 조인이다.

이때 조인 조건은 보통 ON이나 USING()으로 작성한다.

  • ON은 어떤 컬럼끼리 연결할지 조건을 직접 적는 방식이다.

    • 예를 들면 emp.deptno = dept.deptno처럼 쓰면서, 두 테이블의 어떤 값을 기준으로 연결할지 분명하게 보여준다.

  • USING()은 양쪽 테이블에 같은 이름의 공통 컬럼이 있을 때 더 짧게 쓰는 방식이다.

    • 예를 들어 두 테이블에 모두 deptno가 있다면 USING(deptno)처럼 작성할 수 있다.

또 INNER JOIN은 INNER를 생략할 수 있다.
그래서 INNER JOIN이라고 써도 되고, 그냥 JOIN이라고만 써도 같은 의미다.

예를 들어 직원 테이블의 deptno와 부서 테이블의 deptno를 연결하면,
양쪽 테이블에 같은 deptno가 있는 경우만 결과에 포함된다.


반대로 한쪽에는 값이 있는데 다른 쪽에는 대응되는 값이 없으면 결과에서 빠진다.
조인 기준으로 사용하는 컬럼 값이 서로 다르거나 NULL이어도 연결되지 않는다.


즉, INNER JOIN은
두 테이블에서 공통으로 연결되는 데이터만 보는 방식이라고 이해하면 된다.

예제: 직원과 부서를 연결해서 보기

select *
from emp inner join dept
on emp.deptno = dept.deptno;

이 쿼리는 직원 정보와 부서 정보를 deptno로 연결해서 함께 보여준다.
즉, 직원이 어느 부서에 속해 있는지 한 줄에서 같이 확인할 수 있다.

연습 단계에서는 전체 구조를 보기 위해 select *를 써도 되지만,
실제로는 필요한 컬럼만 골라 쓰는 쪽이 더 읽기 쉽다.


예제: 같은 이름 컬럼이면 USING()도 가능하다

select *
from emp join dept
using(deptno);

이 코드는 양쪽 테이블에 있는 deptno를 공통 기준으로 연결한 것이다.
같은 이름의 컬럼이 있을 때는 ON 대신 USING()으로도 표현할 수 있다.


여기서 봐야 할 포인트

INNER JOIN은 연결에 성공한 행만 남긴다.


그래서

  • 양쪽 테이블에 공통 값이 없거나
  • 조인 기준 컬럼 값이 다르거나
  • 조인 기준 컬럼이 NULL이면

결과에서 제외될 수 있다.


또 문법도 함께 기억하면 좋다.

  • JOIN만 써도 기본은 INNER JOIN
  • ON은 조인 조건을 직접 쓰는 방식
  • USING()은 같은 이름의 공통 컬럼이 있을 때 쓰는 방식

즉, INNER JOIN은
두 테이블에서 실제로 연결되는 데이터만 조회하는 가장 기본적인 조인 방식이다.


5. OUTER JOIN

개념

OUTER JOIN은 조건이 맞지 않는 행도 포함해서 보는 조인이다.
INNER JOIN은 연결되는 행만 남기기 때문에 조건이 맞지 않으면 결과에서 아예 사라진다. 하지만 실제로는 연결되지 않은 데이터도 함께 확인해야 할 때가 있다.

예를 들어 부서가 없는 직원도 같이 보고 싶을 수 있고, 반대로 소속 직원이 없는 부서도 함께 보고 싶을 수 있다.
이처럼 한쪽 테이블을 기준으로 조건이 맞지 않는 행도 결과에 남기고 싶을 때 OUTER JOIN을 사용한다.

즉, OUTER JOIN은 조건이 맞지 않아도 기준에 따라 데이터를 남겨서 보는 방식이라고 이해하면 된다.


OUTER JOIN의 종류

OUTER JOIN은 어느 쪽을 기준으로 남길지에 따라 나뉜다.

  • LEFT OUTER JOIN
    → 왼쪽 테이블의 행은 모두 남긴다

  • RIGHT OUTER JOIN
    → 오른쪽 테이블의 행은 모두 남긴다

  • FULL OUTER JOIN
    → 양쪽 테이블의 행을 모두 남긴다

즉, 왼쪽을 남길지, 오른쪽을 남길지, 양쪽 모두를 남길지에 따라 종류가 달라진다.


LEFT OUTER JOIN

처음에는 가장 자주 보는 LEFT OUTER JOIN부터 이해하면 쉽다.
LEFT OUTER JOIN은 왼쪽 테이블의 행은 모두 남기고, 오른쪽 테이블에서 조건이 맞는 데이터만 붙이는 방식이다.

예를 들어 emp를 왼쪽에 두고 dept를 오른쪽에 두면, 직원 정보는 모두 남고 부서 정보는 연결되는 경우만 오른쪽에 붙는다.

예제: 왼쪽 테이블 기준으로 모두 보기

select *
from emp
left outer join dept
on emp.deptno = dept.deptno;

이 쿼리는 emp를 기준으로 보기 때문에 직원 정보는 모두 남고, 부서 정보는 연결되는 경우만 오른쪽에 붙는다.
즉, LEFT OUTER JOIN은 왼쪽 테이블 기준으로 결과를 유지하는 방식이다.


RIGHT OUTER JOIN

RIGHT OUTER JOIN은 LEFT OUTER JOIN과 반대다.
즉, 오른쪽 테이블의 행은 모두 남기고, 왼쪽 테이블에서 조건이 맞는 데이터만 붙이는 방식이다.

예를 들어 emp를 왼쪽에 두고 dept를 오른쪽에 두면, 부서 정보는 모두 남고 직원 정보는 연결되는 경우만 왼쪽에 붙는다.

예제: 오른쪽 테이블 기준으로 모두 보기

select *
from emp
right outer join dept
on emp.deptno = dept.deptno;

이 쿼리는 dept를 기준으로 보기 때문에 부서 정보는 모두 남고, 직원 정보는 연결되는 경우만 왼쪽에 붙는다.
즉, RIGHT OUTER JOIN은 오른쪽 테이블 기준으로 결과를 유지하는 방식이다.


FULL OUTER JOIN

FULL OUTER JOIN은 왼쪽과 오른쪽 양쪽 테이블의 행을 모두 남기는 방식이다.
즉, 한쪽에만 있는 데이터도 빠지지 않고 결과에 포함한다.

다만, MySQL은 FULL OUTER JOIN을 지원하지 않는다.


헷갈리기 쉬운 부분

INNER JOIN과 OUTER JOIN의 차이는 조건이 맞지 않는 행을 결과에 남기느냐, 제외하느냐다.

  • INNER JOIN
    → 양쪽에 모두 연결되는 데이터만 남김

  • OUTER JOIN
    → 기준에 따라 연결되지 않는 데이터도 남길 수 있음

그리고 OUTER JOIN 안에서도 기준이 다시 나뉜다.

  • LEFT OUTER JOIN
    → 왼쪽 기준

  • RIGHT OUTER JOIN
    → 오른쪽 기준

  • FULL OUTER JOIN
    → 양쪽 기준

즉, OUTER JOIN은 하나의 단일 방식이 아니라 어느 쪽 데이터를 남길지에 따라 갈라지는 조인 묶음이라고 이해하면 된다.


6. CROSS JOIN

개념

CROSS JOIN은 두 테이블의 가능한 조합을 모두 만드는 조인이다.
앞에서 본 INNER JOIN이나 LEFT OUTER JOIN은 보통 어떤 컬럼끼리 연결할지 기준이 있었다.
하지만 CROSS JOIN은 그런 연결 조건 없이, 왼쪽 테이블의 각 행을 오른쪽 테이블의 모든 행과 하나씩 전부 짝지어 본다.


즉, 이 조인은
“서로 맞는 값만 연결한다”가 아니라
“가능한 경우를 전부 만들어 본다”에 가깝다.

예를 들어 왼쪽 테이블에 행이 3개, 오른쪽 테이블에 행이 4개 있으면
결과는 3 × 4 = 12개가 된다.
그래서 CROSS JOIN은 결과 행 수가 빠르게 많아질 수 있다는 점도 같이 기억해야 한다.



핵심 특징

CROSS JOIN은 ON절로 연결 조건을 주지 않는다.
조건으로 행을 걸러서 연결하는 조인이 아니라, 두 테이블의 행을 전부 조합해서 결과를 만들기 때문이다.

그래서 이 조인을 볼 때는 항상
왼쪽 행 수 × 오른쪽 행 수
이 흐름을 먼저 떠올리면 된다.

이 조인은 보통 테이블 관계를 따라 연결하는 용도보다는
가능한 조합을 만들어야 할 때 사용한다.
반대로, 관계형 조인처럼 생각하고 쓰면 필요 이상으로 많은 행이 나와서 결과가 이상해질 수 있다.


즉, CROSS JOIN은 관계를 따라 데이터를 연결하는 조인이 아니라,
두 테이블의 행을 조건 없이 전부 조합하는 조인이라고 이해하면 된다.

예제: 직원과 부서를 가능한 조합으로 모두 만들기

select ename, dname
from emp
cross join dept;

이 코드는 emp의 각 직원을 dept의 모든 부서와 하나씩 짝지어 결과를 만든다.
여기서는 emp.deptno = dept.deptno 같은 연결 조건이 없기 때문에,
실제 소속 부서와 맞는 경우만 남기는 것이 아니라 가능한 조합 전체가 나온다.

예를 들어 emp가 14행이고 dept가 4행이면
결과는 14 × 4 = 56행이 된다.
이처럼 CROSS JOIN은 행 수가 곱으로 늘어난다는 점이 핵심이다.



헷갈리기 쉬운 부분

CROSS JOIN은 INNER JOIN과 완전히 다르다.

INNER JOIN은
조건이 맞는 행만 연결한다.


반면 CROSS JOIN은
조건 없이 전부 조합한다.


그래서 아래 두 쿼리는 겉보기는 비슷해도 의미가 완전히 다르다.

select ename, dname
from emp
join dept
on emp.deptno = dept.deptno;

이 쿼리는 실제로 연결되는 직원과 부서만 출력한다.


select ename, dname
from emp
cross join dept;

이 쿼리는 가능한 조합을 전부 출력한다.

즉, 하나는 관계대로 연결하는 것이고
다른 하나는 전체 조합을 만드는 것이다.

CROSS JOIN은
연결 조건이 없는 조인,
가능한 조합을 모두 만드는 조인이라고 이해하면 된다.


7. SELF JOIN

개념

SELF JOIN은 하나의 테이블을 자기 자신과 다시 연결해서 보는 조인이다.
말이 조금 낯설지만, 실제로는 한 테이블 안에 들어 있는 두 역할을 서로 연결하는 것이라고 생각하면 쉽다.


예를 들어 emp 테이블에는 직원 정보가 들어 있다.
그런데 이 테이블 안에는 직원 자신의 번호인 empno도 있고,
그 직원의 매니저 번호인 mgr도 함께 들어 있다.

즉, 한 테이블 안에 이런 식의 관계가 들어 있는 것이다.

  • empno : 자기 자신의 사번
  • mgr : 나를 관리하는 매니저의 사번

여기서 “직원의 매니저 이름”을 알고 싶다면,
직원 한 명의 mgr 값을 가지고
다시 emp 테이블의 empno를 찾아야 한다.


결국 직원도 emp 테이블에 있고, 매니저도 emp 테이블에 있기 때문에
같은 테이블을 두 번 보는 방식이 필요하다.
이럴 때 사용하는 것이 SELF JOIN이다.


왜 별칭이 꼭 필요한가

SELF JOIN에서는 같은 테이블을 두 번 사용하므로
각각의 역할을 구분하기 위해 별칭(alias)을 꼭 붙여야 한다.

예를 들어 아래처럼 보면 된다.

  • e : 직원 역할
  • m : 매니저 역할

실제 테이블은 둘 다 emp이지만,
쿼리 안에서는 역할이 다르기 때문에 이름을 따로 붙여 구분하는 것이다.

별칭이 없으면 ename, empno 같은 열이
어느 쪽 emp를 가리키는지 알 수 없어서 헷갈리게 된다.
그래서 SELF JOIN에서는 별칭이 거의 필수다.


예제: 직원의 매니저 이름 찾기

select e.ename as 직원이름, m.ename as 매니저이름
from emp e
join emp m
on e.mgr = m.empno;

이 코드는 emp 테이블을 두 번 사용한다.

  • emp e : 직원 정보를 보는 쪽
  • emp m : 매니저 정보를 보는 쪽

그리고 on e.mgr = m.empno는
직원의 매니저 번호(mgr)와 매니저의 사번(empno)를 연결하라는 뜻이다.


즉, 흐름은 이렇게 이해하면 된다.

1. 먼저 직원 한 명을 본다.
2. 그 직원의 mgr 값을 확인한다.
3. 그 번호와 같은 empno를 가진 사람을 같은 emp 테이블에서 다시 찾는다.
4. 그 사람이 바로 매니저다.

그래서 e.ename은 직원 이름이 되고,
m.ename은 그 직원의 매니저 이름이 된다.


핵심 특징

SELF JOIN은 다른 테이블과 연결하는 조인이 아니라
같은 테이블 안의 서로 다른 행끼리 연결하는 조인이다.

주로 한 테이블 안에 다음과 같은 구조가 있을 때 사용한다.

  • 직원 ↔ 매니저
  • 부모 글 ↔ 답글
  • 상위 부서 ↔ 하위 부서

즉, 한 테이블 안에 참조 관계나 계층 구조가 들어 있을 때 SELF JOIN이 필요하다.


헷갈리기 쉬운 부분

SELF JOIN은 테이블이 두 개 있는 것이 아니다.
테이블은 하나인데, 그 하나를 두 역할로 나눠서 보는 것이다.

또한 INNER JOIN으로 작성하면
mgr 값이 없는 직원은 결과에서 빠질 수 있다.
예를 들어 최고 관리자처럼 매니저가 없는 직원은 연결할 대상이 없기 때문이다.


여기서 봐야 할 포인트

SELF JOIN의 핵심은
같은 테이블을 두 번 쓰는 것 자체가 아니라, 같은 테이블 안에 있는 두 역할을 연결해서 보는 것이다.

그래서 SELF JOIN을 볼 때는
“왜 같은 테이블을 두 번 썼지?”보다
“이 테이블 안에서 어떤 역할과 어떤 역할을 연결하려는 거지?”를 먼저 보면 이해가 쉬워진다.


8. JOIN 종류 한눈에 보기

이 그림은 지금까지 본 내용을 한 번에 다시 정리할 때 보는 참고 이미지다.
앞에서는 조인을 하나씩 따로 봤다면, 여기서는 각 조인이 무엇을 남기고 어떻게 연결하는지만 다시 정리해서 보면 된다.


지금 단계에서는 아래 흐름으로 다시 보면 된다.

  • INNER JOIN
    → 양쪽 테이블에 모두 연결되는 데이터만 남긴다

  • OUTER JOIN
    → 조건이 맞지 않는 데이터도 기준에 따라 함께 남길 수 있다

  • LEFT OUTER JOIN
    → 왼쪽 테이블 기준으로 남긴다

  • RIGHT OUTER JOIN
    → 오른쪽 테이블 기준으로 남긴다

  • FULL OUTER JOIN
    → 양쪽 테이블의 데이터를 모두 남기는 방식이다
    다만, MySQL은 FULL OUTER JOIN을 지원하지 않는다

  • CROSS JOIN
    → 가능한 조합을 모두 만든다

  • SELF JOIN
    → 같은 테이블을 자기 자신과 다시 연결한다

즉, JOIN은 전부 같은 방식이 아니라
무엇을 기준으로 남길지,
조건이 맞지 않는 데이터를 포함할지,
아예 가능한 조합을 전부 만들지에 따라 종류가 나뉜다고 이해하면 된다.


헷갈리기 쉬운 부분

첫째, UNION과 JOIN은 완전히 다르다.

  • UNION
    → 결과를 세로로 합친다

  • JOIN
    → 테이블을 가로로 연결한다




    둘째, UNION과 UNION ALL의 차이는 중복 처리다.
  • UNION
    → 중복 제거

  • UNION ALL
    → 중복도 그대로 포함




    셋째, INNER JOIN은 연결되는 행만 남긴다.
    조건이 맞지 않거나 조인 컬럼 값이 없으면 결과에서 빠질 수 있다.


    넷째, OUTER JOIN은 조건이 맞지 않는 행도 기준에 따라 남길 수 있다.
    그리고 그 안에서 다시 기준이 나뉜다.
  • LEFT OUTER JOIN
    → 왼쪽 기준

  • RIGHT OUTER JOIN
    → 오른쪽 기준

  • FULL OUTER JOIN
    → 양쪽 기준
    다만, MySQL은 FULL OUTER JOIN을 지원하지 않는다




    다섯째, CROSS JOIN은 관계를 따라 연결하는 조인이 아니다.
    조건 없이 가능한 조합을 전부 만드는 조인이다.


    여섯째, SELF JOIN은 같은 테이블을 두 번 쓰는 것이지, 테이블이 두 개 있는 것이 아니다.
    같은 테이블 안에 들어 있는 관계를 역할로 나눠서 연결하는 방식이다.


짧게 흐름으로 다시 보기

UNION / UNION ALL
→ 두 SELECT 결과를 세로로 합치기


JOIN
→ 두 개 이상의 테이블을 가로로 연결하기


INNER JOIN
→ 연결되는 데이터만 보기


OUTER JOIN
→ 조건이 맞지 않는 데이터도 기준에 따라 함께 보기


LEFT OUTER JOIN
→ 왼쪽 테이블 기준으로 모두 보기


RIGHT OUTER JOIN
→ 오른쪽 테이블 기준으로 모두 보기


FULL OUTER JOIN
→ 양쪽 데이터를 모두 보는 방식이지만, MySQL은 지원하지 않음


CROSS JOIN
→ 가능한 조합을 모두 만들기


SELF JOIN
→ 같은 테이블을 자기 자신과 다시 연결하기

즉, 이 파트의 핵심은
결과를 합치는 방식과, 테이블을 연결하는 방식을 구분해서 이해하는 것,
그리고 조인 종류마다 무엇을 남기고 무엇을 제외하는지가 다르다는 점이다.




JOIN 실습문제

문제 해석은 이렇게 한다

UNION, UNION ALL, JOIN, OUTER JOIN, ROLLUP 문제는 문장을 한 번에 보면 헷갈리기 쉽다.
그래서 바로 SQL을 쓰지 말고, 먼저 문제 문장을 그대로 잘라서 읽어야 한다.

순서는 보통 이렇다.

1. 결과를 세로로 합치는 문제인지
2. 테이블을 가로로 연결하는 문제인지
3. 어느 테이블을 기준으로 남겨야 하는지
4. 어떤 컬럼으로 연결해야 하는지
5. 원본 행을 먼저 거를 조건인지
6. 출력 결과를 가공해야 하는지
7. 정렬까지 필요한지

즉, 이런 문제는 보통
세로 결합인지 가로 연결인지 → 어떤 테이블을 써야 하는지 → 무엇으로 연결해야 하는지 → 먼저 거를 조건인지 → 출력 모양을 어떻게 만들지 → 어떻게 정렬할지

이 순서로 읽으면 된다. 마지막 실습 파일도 1~2번은 course1, course2, 3번은 집계, 4번 이후는 emp, dept, locations, salgrade를 활용하는 구조다.



1. course1 을 수강하는 학생들과 course2 를 수강하는 학생들의 이름, 나이를 출력하는데 나이가 적은 순으로 출력하시오. 단, 두 코스를 모두 수강하는 학생들의 정보는 한 번만 출력한다.

course1 을 수강하는 학생들과 course2 를 수강하는 학생들의
→ course1 결과와 course2 결과를 함께 봐야 한다는 뜻이다.


이름, 나이를 출력하는데
→ 출력해야 하는 컬럼은 name, age다.
즉, 양쪽 SELECT의 컬럼 개수와 순서를 같게 맞춰야 한다.


나이가 적은 순으로 출력하시오
→ 마지막에 age 기준 오름차순 정렬이 필요하다.


단, 두 코스를 모두 수강하는 학생들의 정보는 한 번만 출력한다
→ 양쪽 결과에 같은 학생 정보가 있더라도 한 번만 남겨야 한다는 뜻이다.
즉, 여기서는 UNION을 떠올리면 된다.


두 결과를 세로로 합치되 중복은 제거하는 문제다. 마지막 실습 파일의 예시 결과도 고길동, 듀크, 둘리, 또치, 마이콜, 도우너, 유니코 순으로 한 번씩만 나온다.

정답 코드

select name, age
from course1

union

select name, age
from course2
order by age;





2. course1 을 수강하는 학생들과 course2 를 수강하는 학생들의 이름, 전화 번호 그리고 나이를 출력하는데 나이가 많은 순으로 출력하시오. 단, 두 코스를 모두 수강하는 학생들의 정보를 중복해서 출력한다.

course1 을 수강하는 학생들과 course2 를 수강하는 학생들의
→ course1 결과와 course2 결과를 함께 봐야 한다는 뜻이다.


이름, 전화 번호 그리고 나이를 출력하는데
→ 출력해야 하는 컬럼은 name, phone, age다.
즉, 양쪽 SELECT의 컬럼 개수와 순서를 같게 맞춰야 한다.


나이가 많은 순으로 출력하시오
→ 마지막에 age 기준 내림차순 정렬이 필요하다.


단, 두 코스를 모두 수강하는 학생들의 정보를 중복해서 출력한다
→ 이번에는 중복을 제거하면 안 된다는 뜻이다.
즉, 여기서는 UNION ALL을 떠올리면 된다.


두 결과를 세로로 합치되 중복도 그대로 남기는 문제다. 마지막 실습 파일 예시도 또치, 둘리처럼 두 코스에 모두 있는 학생 정보가 중복되어 출력된다.

정답 코드

select name, phone, age
from course1

union all

select name, phone, age
from course2
order by age desc;





3. 직무별 그리고 입사년도별 직원들 수를 출력하는데 직무별 직원수(소합계)와 전체 직원수(전체 합계)도 함께 출력한다.

직무별 그리고 입사년도별
→ job과 year(hiredate)를 기준으로 묶어야 한다는 뜻이다.


직원들 수를 출력하는데
→ 각 그룹마다 count(*)를 세면 된다.


직무별 직원수(소합계)
→ 직무별로 한 번 더 묶인 소합계 행도 같이 나와야 한다는 뜻이다.


전체 직원수(전체 합계)도 함께 출력한다
→ 마지막에 전체 합계 행까지 같이 나와야 한다는 뜻이다.


이 문제는 단순 GROUP BY가 아니라
상세 그룹 + 소합계 + 전체 합계를 같이 구하는 ROLLUP 문제다.
마지막 실습 파일 안에는 여기 SQL이 group by job;로 들어가 있지만, 문제 예시처럼 직무별 소합계와 전체 합계를 함께 내려면 with rollup이 필요하다.

정답 코드

select job as 직무,
       year(hiredate) as 입사년도,
       count(*) as 직원수
from emp
group by job, year(hiredate) with rollup;





4. RESEARCH 부서에서 근무하는 직원의 이름, 직무, 부서이름을 출력하시오.

RESEARCH 부서에서 근무하는
→ 부서이름이 RESEARCH인 직원만 골라야 한다는 뜻이다.


직원의 이름, 직무
→ emp 테이블에 있다.


부서이름을 출력하시오
→ dept 테이블에 있다.
즉, emp와 dept를 조인해야 한다.

정답 코드

select ename as 이름,
       job as 직무,
       dname as 부서이름
from emp
join dept
on emp.deptno = dept.deptno
where dname = 'RESEARCH';





5. 이름에 'A'가 들어가는 직원들의 이름과 부서이름을 출력하시오.

이름에 'A'가 들어가는 직원들의
→ 직원 이름에 A가 포함된 행만 찾아야 한다는 뜻이다.
즉, ename like '%A%' 조건을 떠올리면 된다.


이름
→ emp 테이블 정보다.


부서이름을 출력하시오
→ dept 테이블 정보다.
즉, emp와 dept를 조인해야 한다.

정답 코드

select ename as 이름,
       dname as 부서이름
from emp
join dept
on emp.deptno = dept.deptno
where ename like '%A%';





6. 직원이름과 그 직원이 속한 부서의 부서명, 그리고 급여를 출력하는데 급여가 3000이상인 직원을 출력하시오.

직원이름
→ emp 테이블에서 가져와야 한다.


그 직원이 속한 부서의 부서명
→ dept 테이블에서 가져와야 한다.
즉, emp와 dept를 연결해야 한다.


그리고 급여를 출력하는데
→ 급여 sal도 같이 출력해야 한다.


급여가 3000이상인 직원을 출력하시오
→ 집계 결과 조건이 아니라 원본 행 조건이다.
즉, where sal >= 3000이다. 실습 예시도 SCOTT, KING, FORD 세 명만 나오고 급여는 3,000원, 5,000원, 3,000원 형태다.

정답 코드

select ename as 직원이름,
       dname as 부서명,
       concat(format(sal, 0), '원') as 급여
from emp
join dept
on emp.deptno = dept.deptno
where sal >= 3000;





7. 커미션이 책정된 직원들의 직원번호, 이름, 연봉, 연봉커미션, 급여등급을 출력하되, 각각의 컬럼명을 '직원번호', '직원이름', '연봉','실급여', '급여등급'으로 하여 출력하시오. 또한 실급여가 적은 순으로 출력하시오.

커미션이 책정된 직원들의
→ comm이 NULL이 아닌 직원만 보라는 뜻이다.
즉, where comm is not null 조건이 필요하다.


직원번호, 이름
→ emp 테이블에서 가져온다.


연봉
→ sal * 12로 계산하면 된다.


연봉커미션
→ 문제 문장 표현은 조금 어색하지만, 출력 예시를 보면 실제로는 연봉 + 커미션 값, 즉 실급여를 의미한다.
예를 들어 WARD는 연봉 15000, 실급여 15200, KING은 연봉 60000, 실급여 63500으로 제시되어 있다.


급여등급
→ salgrade 테이블을 조인해서 구해야 한다.
즉, sal between losal and hisal 조건으로 연결해야 한다.


또한 실급여가 적은 순으로 출력하시오
→ 마지막에 계산식 기준 오름차순 정렬이 필요하다.

정답 코드

select empno as 직원번호,
       ename as 직원이름,
       sal * 12 as 연봉,
       sal * 12 + ifnull(comm, 0) as 실급여,
       grade as 급여등급
from emp
join salgrade
on sal between losal and hisal
where comm is not null
order by sal * 12 + ifnull(comm, 0);





8. 부서번호가 10번인 직원들의 부서번호, 부서이름, 직원이름, 급여, 급여등급을 출력하시오.

부서번호가 10번인 직원들의
→ 먼저 deptno = 10인 직원만 남기라는 뜻이다.


부서번호, 부서이름
→ 부서번호는 emp에도 있지만, 부서이름은 dept에 있다.
즉, emp와 dept를 연결해야 한다.


직원이름, 급여
→ emp에서 가져오면 된다.


급여등급을 출력하시오
→ salgrade 테이블을 한 번 더 조인해야 한다.

정답 코드

select emp.deptno as 부서번호,
       dname as 부서이름,
       ename as 직원이름,
       sal as 급여,
       grade as 급여등급
from emp
join dept
on emp.deptno = dept.deptno
join salgrade
on sal between losal and hisal
where emp.deptno = 10;





9. 직무가 'SALESMAN'인 직원들의 직무와 그 직원이름, 그리고 그 직원이 속한 부서 이름을 출력하시오.

직무가 'SALESMAN'인 직원들의
→ 원본 행 조건이므로 where job = 'SALESMAN'이다.


직무와 그 직원이름
→ emp 테이블 정보다.


그 직원이 속한 부서 이름
→ dept 테이블 정보다.

즉, emp와 dept를 조인해야 한다.

정답 코드

select job as 직무,
       ename as 직원이름,
       dname as 부서이름
from emp
join dept
using(deptno)
where job = 'SALESMAN';





10. 부서번호가 10번, 20번인 직원들의 부서번호, 부서이름, 직원이름, 급여, 급여등급을 출력하시오. 그리고 그 출력된 결과물을 부서번호가 낮은 순으로, 급여가 많은 순으로 정렬하시오.

부서번호가 10번, 20번인 직원들의
→ deptno in (10, 20) 조건이 필요하다는 뜻이다.


부서번호, 부서이름, 직원이름, 급여
→ emp와 dept를 조인해야 한다는 뜻이다.


급여등급을 출력하시오
→ salgrade 테이블도 함께 조인해야 한다.


부서번호가 낮은 순으로
→ 먼저 deptno asc 정렬이 필요하다.


급여가 많은 순으로 정렬하시오
→ 같은 부서 안에서는 sal desc 정렬이 필요하다. 실습 파일도 10번 부서가 먼저 나오고, 그 안에서 KING, CLARK, MILLER 순으로 정렬되어 있다.

정답 코드

select emp.deptno as 부서번호,
       dname as 부서이름,
       ename as 직원이름,
       sal as 급여,
       grade as 급여등급
from emp
join dept
using(deptno)
join salgrade
on sal between losal and hisal
where emp.deptno in (10, 20)
order by emp.deptno, sal desc;





11. 사원들의 이름, 부서번호, 부서이름을 출력하시오. 단, 직원이 없는 부서도 출력하며 이경우 이름을 '없음'이라고 출력한다. 부서번호별로 정렬한다.

사원들의 이름, 부서번호, 부서이름을 출력하시오
→ 기본적으로 직원과 부서를 함께 봐야 하므로 emp와 dept를 연결해야 한다.


단, 직원이 없는 부서도 출력하며
→ 이번에는 부서 쪽 데이터가 반드시 남아야 한다는 뜻이다.
즉, dept를 기준으로 결과를 유지해야 한다.


이경우 이름을 '없음'이라고 출력한다
→ 연결되는 직원이 없어서 ename이 NULL이면 ifnull(ename, '없음')으로 바꿔야 한다.


부서번호별로 정렬한다
→ dept.deptno 기준 정렬이 필요하다. 실습 예시도 40 OPERATIONS, 50 INSA가 직원 없이 없음으로 출력된다.


부서 기준 OUTER JOIN 문제다. dept를 왼쪽에 두고 left outer join으로 쓰면, 초보자가 “부서를 남기는 조인”이라는 흐름을 더 바로 읽기 쉽다. 기존 JOIN 정리도 LEFT OUTER JOIN은 왼쪽 테이블 기준 유지라고 설명하고 있다.

정답 코드

select ifnull(ename, '없음') as 이름,
       dept.deptno as 부서번호,
       dname as 부서이름
from dept
left outer join emp
using(deptno)
order by dept.deptno;





12. 직원들의 이름, 부서번호, 부서이름을 출력하시오. 단, 아직 부서 배치를 못받은 직원도 출력하며 이경우 부서번호와 부서명은 null 로 출력한다. 또한 직원들의 이름순으로 정렬한다.

직원들의 이름, 부서번호, 부서이름을 출력하시오
→ 직원과 부서를 함께 봐야 하므로 emp와 dept를 연결해야 한다.


단, 아직 부서 배치를 못받은 직원도 출력하며
→ 이번에는 직원 쪽 데이터가 반드시 남아야 한다는 뜻이다.
즉, emp를 기준으로 결과를 유지해야 한다.


이경우 부서번호와 부서명은 null 로 출력한다
→ 연결되는 부서가 없으면 그대로 NULL로 두면 된다.


또한 직원들의 이름순으로 정렬한다
→ ename 기준 정렬이 필요하다. 실습 예시에서도 ADAMS가 NULL, NULL로 포함되어 있다.


직원 기준 OUTER JOIN 문제다.

정답 코드

select ename as 이름,
       dept.deptno as 부서번호,
       dname as 부서이름
from emp
left outer join dept
on emp.deptno = dept.deptno
order by ename;






13번부터는 dept, locations, salgrade까지 같이 보는 문제다.
여기서는 dept 테이블의 loc_code와 locations 테이블의 loc_code를 연결해서
부서가 어느 도시에 속하는지 함께 확인하면 된다.

13. 커미션이 정해진 모든 직원의 이름, 커미션, 부서이름, 도시명을 출력하시오.

커미션이 정해진 모든 직원의
→ comm이 NULL이 아닌 직원만 보라는 뜻이다.
출력 예시를 보면 0도 포함되므로, 0은 제외하면 안 되고 NULL만 제외해야 한다.


이름, 커미션
→ emp 테이블 정보다.


부서이름
→ dept 테이블 정보다.


도시명을 출력하시오
→ locations 테이블 정보다.
즉, emp → dept → locations 순서로 연결해야 한다. 실습 예시도 KING-SEOUL, JONES-DALLAS, ALLEN/WARD/MARTIN/TURNER-CHICAGO 형태다.

정답 코드

select ename as 직원명,
       comm as 커미션,
       dname as 부서명,
       city as 도시명
from emp
join dept
using(deptno)
join locations
using(loc_code)
where comm is not null;





14. DALLAS에서 근무하는 사원의 이름, 급여, 등급을 출력하시오.

DALLAS에서 근무하는 사원의
→ 근무 도시가 DALLAS인 사원만 골라야 한다는 뜻이다.
즉, locations 테이블까지 연결해야 한다.


이름, 급여
→ emp 테이블 정보다.


등급을 출력하시오
→ 급여 등급은 salgrade 테이블 정보다.
즉, sal between losal and hisal 조건으로 연결해야 한다. 실습 예시도 SMITH 800 1, JONES 2975 4, SCOTT 3000 4, FORD 3000 4로 제시되어 있다.

정답 코드

select ename as 이름,
       sal as 급여,
       grade as 등급
from emp
join dept
using(deptno)
join locations
using(loc_code)
join salgrade
on sal between losal and hisal
where city = 'DALLAS'
order by sal;





15. 사원들의 이름, 부서번호, 부서명을 출력하시오. 단, 직원이 없는 부서도 출력하며 이경우 직원 이름을 '누구?'라고 출력한다. 아직 부서 배치를 못받은 직원도 출력하며 부서 번호와 부서 이름을 '어디?' 이라고 출력한다. (16행) 부서명을 기준으로 정렬한다.

사원들의 이름, 부서번호, 부서명을 출력하시오
→ 기본적으로 직원과 부서를 함께 봐야 한다는 뜻이다.


단, 직원이 없는 부서도 출력하며
→ 부서 쪽에만 있는 데이터도 남겨야 한다는 뜻이다.


아직 부서 배치를 못받은 직원도 출력하며
→ 직원 쪽에만 있는 데이터도 남겨야 한다는 뜻이다.


즉, 이 문제는
양쪽에서 연결되지 않는 데이터까지 모두 남겨야 하는 문제다.

개념으로 보면 FULL OUTER JOIN이 가장 잘 맞는다.
하지만 기존 정리에서 본 것처럼 MySQL은 FULL OUTER JOIN을 지원하지 않는다. 그래서 이런 문제는 보통 LEFT OUTER JOIN 결과와 RIGHT OUTER JOIN 결과를 UNION으로 합쳐서 해결한다.


이경우 직원 이름을 '누구?'라고 출력한다
→ 직원 이름이 NULL이면 누구?로 바꿔야 한다.


부서 번호와 부서 이름을 '어디?' 이라고 출력한다
→ 부서가 연결되지 않으면 부서번호와 부서명을 어디?로 바꿔야 한다.


부서명을 기준으로 정렬한다
→ 마지막에 부서명 기준 정렬이 필요하다.


여기서 UNION ALL이 아니라 UNION을 쓰는 이유는,
양쪽 조인 결과에서 겹치는 행은 한 번만 남겨야 하기 때문이다. 기존 정리에서도 UNION은 중복 제거, UNION ALL은 중복 유지로 설명하고 있다.

정답 코드

select ifnull(ename, '누구?') as 직원명,
       ifnull(cast(dept.deptno as char), '어디?') as 부서번호,
       ifnull(dname, '어디?') as 부서명
from emp
left outer join dept
on emp.deptno = dept.deptno

union

select ifnull(ename, '누구?') as 직원명,
       ifnull(cast(dept.deptno as char), '어디?') as 부서번호,
       ifnull(dname, '어디?') as 부서명
from emp
right outer join dept
on emp.deptno = dept.deptno
order by 부서명;





16. 'CHICAGO' 에서 근무하는 직원들의 이름, 입사일, 급여를 출력한다. (서브쿼리로 해결한다.)

'CHICAGO' 에서 근무하는 직원들의
→ CHICAGO라는 도시 정보에서 시작해서, 그 도시에 있는 부서를 찾고, 그 부서에 속한 직원을 찾아야 한다는 뜻이다.


이름, 입사일, 급여를 출력한다
→ 이 값들은 최종적으로 emp 테이블에서 가져와야 한다.


(서브쿼리로 해결한다.)
→ 이번에는 조인으로 직접 연결하지 말고,
지역 → 부서 → 직원 흐름을 서브쿼리로 풀라는 뜻이다. 실습 파일도 16번은 서브쿼리, 17번은 조인으로 나눠 제시하고 있다.

정답 코드

select ename,
       hiredate,
       sal
from emp
where deptno in (
    select deptno
    from dept
    where loc_code in (
        select loc_code
        from locations
        where city = 'CHICAGO'
    )
);





17. 'CHICAGO' 에서 근무하는 직원들의 이름, 입사일, 부서명을 출력한다. (조인로 해결한다.)

'CHICAGO' 에서 근무하는 직원들의
→ 역시 도시 조건은 locations에서 확인해야 한다는 뜻이다.


이름, 입사일
→ emp 테이블 정보다.


부서명을 출력한다
→ dept 테이블 정보다.


(조인로 해결한다.)
→ 16번처럼 서브쿼리로 풀지 말고,
이번에는 emp, dept, locations를 직접 연결해서 풀라는 뜻이다. 실습 파일 예시도 결과는 SALES 부서 직원 6명이다.

정답 코드

select ename,
       hiredate,
       dname
from emp
join dept
using(deptno)
join locations
using(loc_code)
where city = 'CHICAGO';





18. 'DALLAS' 에서 근무하는 직원들의 이름, 연봉, 부서명을 연봉이 큰 순으로 출력하는데 연봉의 계산은 (급여+커미션)*12 을 적용하는데 커미션이 정해지지않은 직원은 0으로 계산한다.

'DALLAS' 에서 근무하는 직원들의
→ 도시가 DALLAS인 직원만 보라는 뜻이다.
즉, locations까지 연결해야 한다.


이름, 연봉, 부서명을
→ 이름은 emp, 부서명은 dept에서 가져온다.
연봉은 계산해서 만들어야 한다.


연봉의 계산은 (급여+커미션)*12 을 적용하는데
→ 여기서는 sal * 12 + comm이 아니라
반드시 (sal + ifnull(comm, 0)) * 12 흐름으로 읽어야 한다는 뜻이다.
실습 예시에서 JONES의 연봉이 36060으로 제시된 것도 (2975 + 30) * 12로 계산해야 맞는다.


커미션이 정해지지않은 직원은 0으로 계산한다
→ ifnull(comm, 0)이 필요하다.


연봉이 큰 순으로 출력
→ 마지막에 계산된 연봉 기준 내림차순 정렬이 필요하다.

정답 코드

select ename as 이름,
       (sal + ifnull(comm, 0)) * 12 as 연봉,
       dname as 부서명
from emp
join dept
using(deptno)
join locations
using(loc_code)
where city = 'DALLAS'
order by (sal + ifnull(comm, 0)) * 12 desc;





19 도시명 'SEOUL' 에서 근무중인 직원들의 인원을 출력하시오.

도시명 'SEOUL' 에서 근무중인 직원들의
→ 근무 도시가 SEOUL인 직원만 골라야 한다는 뜻이다.
즉, locations까지 연결해야 한다.


인원을 출력하시오
→ 직원 수를 세야 하므로 count(*)를 떠올리면 된다.
실습 예시도 결과를 3명으로 제시하고 있다.

정답 코드

select concat(count(*), '명') as 인원수
from emp
join dept
using(deptno)
join locations
using(loc_code)
where city = 'SEOUL';

0개의 댓글