조인1 - 내부 조인

조인이 필요한 이유

대표님이 "최근 주문 현황을 고객 이름과 상품명을 포함해서 보고서로 만들어줘" 라는 요청이 있다고 가정하자.

우리는 쇼핑몰 운영에 필요한 users, products, orders 테이블을 만들고 데이터를 입력했다. 주문 현황이 필요하기 떄문에, orders 의 정보를 제공하면 될 것 같다.

하지만 일반적인 설계에서 볼 수 있듯이 orders 에는 고객과 상품에 대한 정보가 외래키(정규화)로 연결되어 있기에 직접적인 정보를 확인할 수 없다. 결국 조건을 걸어 직접 하나하나 조회하는 과정이 필요하다.

데이터가 많이 없다면 고객명과 상품명을 각 테이블에서 직접 찾아서 보고서를 작성해도 되지만, 데이터가 수백만건이라면 상당히 큰 어려움이 있을 것이다.

왜, 우리는 이렇게 불편하게 데이터를 여러 테이블에 나누어 저장하는 것일까?

만약 모든 데이터를 하나의 테이블에 저장한다면?

만약 orders 테이블에 users, products 정보를 모두 저장한다면 당장은 편할수도 있다.

하지만 실무에서는 다음과 같은 재앙을 불러온다.

  1. 데이터 중복 - 조회 시 특정 고객이 상품을 100번 주문했다고 가정하면, 고객 정보(이름, 이메일, 주소, 등..) 가 100번이나 불필요하게 반복된다. 이건 매우 큰 저장 공간의 낭비이다.

  2. 갱신 이상 - 수정 시 고객이 주소를 변경했다면 고객과 관련된 100개의 주문 데이터를 찾아서 새로운 주소로 변경해야 한다.. 만약 실수로 단 하나의 주소를 누락한다면, 데이터의 일관성이 깨져버린다.. 어떤 데이터가 진짜 데이터인지 믿을 수 없게 되는 것이다.

  3. 삽입 이상 - 생성 시 우리 쇼핑몰에 아직 아무도 주문하지 않은 새로운 제품을 등록하고 싶지만, 주문이 발생하지 않아 상품을 등록할 수 없는 말도 안되는 상황이 발생한다.

  4. 삭제 이상 - 삭제 시 만약 특정 주문 기록을 삭제해야 한다면 관련 고객 정보까지 날라가 버리는 말도 안되는 상황이 발생한다.

이러한 문제들 때문에 우리는 데이터베이스를 설계할 때 정규화(Normalization) 라는 과정을 거친다. 정규화는 데이터 중복을 최소화하고, 데이터의 일관성을 해치는 '이상 현상' 들을 방지하기 위해 데이터를 논리적인 단위로 분리하는 과정이다.

그래서 조인이 필요하다.

이제 우리는 왜 데이터를 분리해서 저장하는지 이해했다. 데이터 중복을 막고, 일관성을 지키기 위해서이다. 즉, 데이터를 '잘 관리하기 위해서'다.

하지만 잘 관리하기 위해 흩어놓은 데이터에서 의미 있는 정보를 얻으려면, 이 흩어진 조각들을 다시 합쳐야만 한다.

이때 사용하는 기술이 바로 조인(join) 이다.

조인은 데이터 정규화를 통해 얻는 일관성과 효율성의 장점은 그대로 유지하면서, 우리가 원하는 통합된 정보를 얻을 수 있게 해주는, 데이터 분리와 통합을 완성해주는 기술이다.

내부 조인1

우리 쇼핑몰의 대표님이 다음과 같은 요청을 한다. "주문이 완료된(`COMPLETED` ) 모든 주문에 대해, 어떤 고객이 주문했는지 고객 ID, 고객 이름과 주문 날짜를 함께 보고싶네."

이 요구사항을 충족하려면 '고객 이름' 을 담고 있는 users, 테이블과 '주문 날짜' 를 담고 있는 orders 테이블의 정보가 모두 필요하다. 즉, 두 테이블을 연결해야만 한다.

조인은 크게 내부 조인과 외부 조인이 있다.

내부 조인 개념

내부 조인은 두 테이블을 연결할 때, 양쪽 테이블에 모두 공통으로 존재하는 데이터만을 결과로 보여준다. 기준이 되는 컬럼의 값이 서로 일치하는 행들만 짝을 지어주는 것이다.

내부 조인 문법

  • FROM : 기준이 되는 첫 번째 테이블을 지정한다.

  • INNER JOIN : 연결할 두 번째 테이블을 지정한다.

  • ON : 조인에서 가장 중요한 부분이다. 두 테이블을 어떤 조건으로 연결할지 명시하는 연결고리다. ON 절의 조건

    이 참(true)이 되는 행들만 결과에 포함된다.

실습 : 주문별 고객 정보 조회하기

실제 쿼리를 작성해 내부 조인에 대해 알아보자.

1단계 : 두 테이블을 그대로 연결하기

[조인 과정]

  • 데이터베이스는 orders 테이블과 users 테이블을 조회한다.

  • JOIN ON에 의해 ordersuser_idusersuser_id 를 기준으로 조인한다.

  • ordersuser_id 의 값으로 usersuser_id 항목을 찾고 연결한다.

  • user_id:6 인 레오나르도 다빈치의 경우 연결할 대상이 없다. 따라서 내부 조인 대상에 포함되지 않는다.

[실행 결과]

  • 결과에서 볼 수 있듯이, ordersusers 의 모든 컬럼이 옆으로 합쳐진다.

  • user_id 컬럼이 두 번 나타나는 이유는 orders, users 두 테이블 모두에 존재하기 때문이다.

2단계 : 필요한 컬럼만 선택하고, where 로 필터링하기

[실행 결과]

조인 작동 순서

  1. FROM / JOIN : 가장 먼저 FROM 절의 orders 테이블과 INNER JOIN 으로 연결된 users 테이블을 연결하기 위해 ON 절에 명시된 orders.user_id = users.user_id 조건을 만족하는 행들을 결합하여 하나의 큰 가상 테이블을 생성한다.

  2. WHERE : JOIN 을 통해 생성된 가상 테이블에서 WHERE 절의 조건인 orders.status = 'COMPLETED' 를 만족하는 행들만 필터링한다. 즉, COMPLETED 상태의 주문 데이터만 남게 된다.

  3. SELECT : 마지막으로, 필터링된 결과에서 SELECT 절에 명시된 users.user_id, nameorder_date 컬럼을 추출하여 최종 결과를 반환한다.

내부 조인2

조인과 집합

내부 조인을 이해하는 또 다른 방법은 바로 '집합' 의 관점에서 바라보는 것이다.

내부 조인은 두 테이블의 교집합을 찾는 것과 같다.

두 집합에서 공통된 원소만을 결과로 반환한다.

벤 다이어그램으로 이해하기

집합의 관점에서 제외되는 데이터

그렇다면 교집합에 포함되지 않는 데이터는 어떻게 될까?

결론적으로, 내부 조인은 어느 한쪽에만 데이터가 존재하는 경우 결과에 포함시키지 않고, 양쪽 모두에 명확하게 연결고리가 있는 데이터만을 짝지어 보여준다.

내부 조인과 조인 방향

내부 조인은 양방향이다. A 테이블과 B 테이블이 있다고 하면, A -> B 로 조인할 수 있다면 반대로 B -> A 로 조인할 수 있다. 그리고 그 결과는 항상 동일하다.

왜냐면 교집합을 찾는 조인 방식이기 때문이다!

Last updated