SQL 첫걸음 32강. 테이블 결합

DongHo Im·2022년 1월 17일
0

SQL첫걸음

목록 보기
33/37

테이블 결합

  • 보통 데이터베이스는 하나의 테이블에 많은 데이터를 저장하지 않고 몇 개의 테이블로 나누어 저장합니다.
  • 이처럼 여러 개로 나뉜 데이터를 하나로 묶어 결과를 내는 방법이 바로 테이블 결합이며 이 개념이 집합론에서는 곱집합입니다.

곱집합과 교차결합

  • 곱집합은 두 개의 잡합을 곱하는 연산 방법으로 적집합 또는 카티전곱(Cartesian Product)라고도 불립니다.

교차결합(Cross Join)

  • 데이터베이스 테이블은 집합의 한 종류이기 때문에 SELECT 명령에서 FROM 구에 두 개의 집합, 다시 말해 두 개의 테이블을 지정하면 이들은 곱집합으로 계산이 됩니다.

만약 아래와 같은 테이블 sample72_x와 sample72_y가 존재한다고 가정해봅시다.

--sample72_x
+------+
| x    |
+------+
| A    |
| B    |
| C    |
+------+

--sample72_y
+------+
| y    |
+------+
|    1 |
|    2 |
|    3 |
+------+

이를 곱집합하는 방법은 아래와 같습니다.

mysql > SELECT * FROM sample72_x, sample72_y;

+------+------+
| x    | y    |
+------+------+
| A    |    1 |
| B    |    1 |
| C    |    1 |
| A    |    2 |
| B    |    2 |
| C    |    2 |
| A    |    3 |
| B    |    3 |
| C    |    3 |
+------+------+
9 rows in set (0.00 sec)
  • 이처럼 SELECT 명령에서 FROM 구에 복수의 테이블을 지정하면 교차결합을 하여 두 개의 테이블을 곱집합으로 계산합니다.

UNION 연결과 결합 연결의 차이

  • UNION 키워드와 FROM 구에서의 복수 테이블 지정은 동일한 결괏값을 반환할 것처럼 보이지만 둘은 확대 방향이 다릅니다.

  • UNION 키워드의 경우 합잡합이기 때문에 세로 방향으로 더해지고 반대로 FROM 구에 복수 테이블을 지정하는 것은 곱집합이기 때문에 가로 방향으로 더해집니다.

  • 조금 더 직관적으로 이를 비교하기 위해 앞서 UNION 키워드를 통해 합집합을 계산했던 테이블 sample71_a와 sample71_b를 통해 확인해보겠습니다.

  • 아래와 같이 FROM 구에 두 테이블을 지정한 것과 UNION 키워드를 사용한 결과가 다른 것을 확인할 수 있습니다.

  • FROM 구에 복수의 테이블을 지정한 결과는 가로 방향으로 확장이 되었고 UNION 키워드를 사용한 결과는 세로 방향으로 확장이 되었습니다.

$ mysql > SELECT * FROM sample71_a, sample71_b;

+------+------+
| a    | b    |
+------+------+
|    1 |    2 |
|    2 |    2 |
|    3 |    2 |
|    1 |   10 |
|    2 |   10 |
|    3 |   10 |
|    1 |   11 |
|    2 |   11 |
|    3 |   11 |
+------+------+
9 rows in set (0.00 sec)

$ mysql > SELECT * FROM sample71_A 
           UNION 
          SELECT * FROM sample71_b;

+------+
| a    |
+------+
|    1 |
|    2 |
|    3 |
|   10 |
|   11 |
+------+
5 rows in set (0.00 sec)

내부결합

  • 교차결합을 사용할 경우 결합해야 하는 테이블의 수가 늘어날 수록 집합 자체도 무척 거대해집니다. 그래서 보통 내부결합을 사용합니다.

  • 수학에서의 집합은 유일한 요소로 구성됩니다. 이와 마찬가지로 데이터베이스 또한 중복된 값이 없게, 테이블의 데이터가 유일한 값을 가지도록 권장합니다.

    이런 유일성과 연관된 기본키(Primary Key) 개념을 접했습니다. 이때 기본키는 하나의 데이터행을 대표하게 됩니다.

  • 중복된 값을 지양하는 이유는 하나의 데이터에 변경이 발생할 때 다른 데이터도 모두 변경해줘야 하고 이 과정에서 오류가 발생할 수 있기 때문입니다.

  • 내부결합에 관해 살펴보기 이전 아래와 같이 product_no를 기본키로 가지는 테이블 products를 우선 생성해줍니다.

mysql > CREATE TABLE products (
    -> product_no CHAR(4) NOT NULL,
    -> product_name VARCHAR(32) NOT NULL,
    -> price DECIMAL(6, 2) NOT NULL,
    -> CONSTRAINT pkey_products PRIMARY KEY (product_no)
    -> );

Query OK, 0 rows affected (0.00 sec)

mysql > DESC products;

+--------------+--------------+------+-----+---------+-------+
| Field        | Type         | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+-------+
| product_no   | char(4)      | NO   | PRI | NULL    |       |
| product_name | varchar(32)  | NO   |     | NULL    |       |
| price        | decimal(6,2) | NO   |     | NULL    |       |
+--------------+--------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

그리고 아래와 같이 상품의 재고를 관리할 수 있는 테이블 product_stocks를 생성합니다.

mysql > CREATE TABLE product_stocks (
    -> product_no CHAR(4) NOT NULL,
    -> received_date DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
    -> stock INTEGER NOT NULL
    -> );

Query OK, 0 rows affected (0.01 sec)

mysql > DESC product_stocks;

+---------------+----------+------+-----+-------------------+-------------------+
| Field         | Type     | Null | Key | Default           | Extra             |
+---------------+----------+------+-----+-------------------+-------------------+
| product_no    | char(4)  | NO   |     | NULL              |                   |
| received_date | datetime | NO   |     | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
| stock         | int      | NO   |     | NULL              |                   |
+---------------+----------+------+-----+-------------------+-------------------+
3 rows in set (0.00 sec)
  • 테이블 products와 product_stocks는 동일하게 product_no열을 통해 연결될 수 있습니다. - 이처럼 다른 테이블의 데이터를 참조해야 할 때 참조할 테이블의 기본키와 동일한 이름과 자료형으로 열을 만들어서 행을 연결할 수 있습니다.

두 테이블을 단순히 FROM 구의 복수 테이블로 지정하여 교차결합하면 그 결과는 아래와 같습니다.

mysql > SELECT * FROM products, product_stocks;

+------------+--------------+--------+------------+---------------------+-------+
| product_no | product_name | price  | product_no | received_date       | stock |
+------------+--------------+--------+------------+---------------------+-------+
| 0001       | 신발         |  24.00 | 0001       | 2021-12-09 18:06:12 |    20 |
| 0002       | 코트         | 100.50 | 0001       | 2021-12-09 18:06:12 |    20 |
| 0003       | 스웨터       |  49.50 | 0001       | 2021-12-09 18:06:12 |    20 |
| 0001       | 신발         |  24.00 | 0002       | 2021-12-09 18:06:12 |    30 |
| 0002       | 코트         | 100.50 | 0002       | 2021-12-09 18:06:12 |    30 |
| 0003       | 스웨터       |  49.50 | 0002       | 2021-12-09 18:06:12 |    30 |
| 0001       | 신발         |  24.00 | 0001       | 2021-10-09 08:06:12 |    50 |
| 0002       | 코트         | 100.50 | 0001       | 2021-10-09 08:06:12 |    50 |
| 0003       | 스웨터       |  49.50 | 0001       | 2021-10-09 08:06:12 |    50 |
| 0001       | 신발         |  24.00 | 0003       | 2021-10-09 08:06:12 |    70 |
| 0002       | 코트         | 100.50 | 0003       | 2021-10-09 08:06:12 |    70 |
| 0003       | 스웨터       |  49.50 | 0003       | 2021-10-09 08:06:12 |    70 |
+------------+--------------+--------+------------+---------------------+-------+
12 rows in set (0.00 sec)

product_no에 따라서 묶이지 않고 단순한 곱집합의 결과를 반환하여 상품 별 재고를 한 눈에 볼 수 없습니다. 이럴 때 아래와 같이 WHERE 구의 조건을 활용하여 상품 코드가 동일한 것만 묶어서 반환할 수 있습니다.

mysql > SELECT * 
          FROM products, product_stocks 
         WHERE products.product_no = product_stocks.product_no;

+------------+--------------+--------+------------+---------------------+-------+
| product_no | product_name | price  | product_no | received_date       | stock |
+------------+--------------+--------+------------+---------------------+-------+
| 0001       | 신발         |  24.00 | 0001       | 2021-12-09 18:06:12 |    20 |
| 0002       | 코트         | 100.50 | 0002       | 2021-12-09 18:06:12 |    30 |
| 0001       | 신발         |  24.00 | 0001       | 2021-10-09 08:06:12 |    50 |
| 0003       | 스웨터       |  49.50 | 0003       | 2021-10-09 08:06:12 |    70 |
+------------+--------------+--------+------------+---------------------+-------+
4 rows in set (0.00 sec)

이처럼 교차결합으로 계산된 곱집합에서 원하는 조합을 검색하는 것을 내부결합(Inner Join)이라하며 이때 사용된 조건을 결합조건이라 합니다.


INNER JOIN으로 내부결합하기

위의 내부결합은 INNER JOIN 명령을 사용하여 더 간단하게 구할 수 있습니다. 그 방법은 아래와 같습니다.

mysql > SELECT * 
          FROM products 
         INNER JOIN product_stocks 
            ON products.product_no = product_stocks.product_no;

+------------+--------------+--------+------------+---------------------+-------+
| product_no | product_name | price  | product_no | received_date       | stock |
+------------+--------------+--------+------------+---------------------+-------+
| 0001       | 신발         |  24.00 | 0001       | 2021-12-09 18:06:12 |    20 |
| 0002       | 코트         | 100.50 | 0002       | 2021-12-09 18:06:12 |    30 |
| 0001       | 신발         |  24.00 | 0001       | 2021-10-09 08:06:12 |    50 |
| 0003       | 스웨터       |  49.50 | 0003       | 2021-10-09 08:06:12 |    70 |
+------------+--------------+--------+------------+---------------------+-------+
4 rows in set (0.00 sec)
  • 앞서 WHERE 구를 통해 지정했던 결합조건을 INNER JOIN 명령의 ON 구에 사용하면 됩니다.
  • 물론 INNER JOIN을 사용해도 아래와 같이 여전히 WHERE, ORDER BY 구를 사용할 수 있습니다.
// 제품테이블에서 재고테이블을 내부결합하고 제품테이블에서 
//가격이 50보다 작거나 같은 리스트를 불러오며 정렬은 재고의 입고일자를 내림차순하여 보여준다.
$ mysql > SELECT * 
            FROM products 
           INNER JOIN product_stocks 
              ON products.product_no = product_stocks.product_no 
           WHERE products.price <= 50 
           ORDER BY product_stocks.received_date DESC;

+------------+--------------+-------+------------+---------------------+-------+
| product_no | product_name | price | product_no | received_date       | stock |
+------------+--------------+-------+------------+---------------------+-------+
| 0001       | 신발         | 24.00 | 0001       | 2021-12-09 18:06:12 |    20 |
| 0001       | 신발         | 24.00 | 0001       | 2021-10-09 08:06:12 |    50 |
| 0003       | 스웨터       | 49.50 | 0003       | 2021-10-09 08:06:12 |    70 |
+------------+--------------+-------+------------+---------------------+-------+
3 rows in set (0.01 sec)

내부결합을 활용한 데이터 관리

  • 위 예시에서는 product_no가 기본키가 되어 하나의 상품이 독립적인 제품이 되는 예시였습니다.
  • 그러나 아래와 같이 각 상품 별로 메이커가 존재하는 경우 중복되는 메이커코드가 존재할 수 있습니다.
--상품 테이블
+--------------+-----------+-----------------+--------+--------------+
| 상품코드     | 상품명    | 메이커코드      | 가격   | 상품분류     |
+--------------+-----------+-----------------+--------+--------------+
| 0001         | 상품1     | M001            |    100 | 식료품       |
| 0002         | 상품2     | M001            |    200 | 식료품       |
| 0003         | 상품3     | M002            |   1980 | 생활용품     |
+--------------+-----------+-----------------+--------+--------------+

이때 메이커코드를 관리하는 테이블 메이커를 아래와 같이 독립적으로 생성하여 관리할 수 있습니다.

--메이커 테이블
+-----------------+--------------+
| 메이커코드      | 메이커명     |
+-----------------+--------------+
| M001            | 메이커1      |
| M002            | 메이커2      |
+-----------------+--------------+
  • 이때 하나의 메이커코드에는 여러 상품이 존재할 수 있지만 반대로 하나의 상품은 하나의 메이커코드에 종속됩니다.

    이러한 관계를 상품을 기준으로 봤을 때 일대다(1:N) 관계라 합니다.

  • 이처럼 데이터베이스에서는 관계가 무척 중요합니다.

    • 일대일(1:1)
    • 일대다(1:N)
    • 다대다(N:M)

외부키

위 예시에서 메이커코드는 테이블 메이커에서 기본키입니다.

  • 테이블 상품에서 외부의 기본키인 메이커코드를 참조하였습니다. 이처럼 외부의 기본키를 참조하는 걸 외부키(Foreign Key)라 합니다.

자기결합(Self Join)

  • 자기결합(Self Join)은 테이블에 별명을 붙일 수 있는 기능을 이용해 같은 테이블끼리 결합하는 것을 의미합니다.
  • 이때 유의할 점은 동일한 테이블을 참조하기 때문에 INNER JOIN 구에서 참조하는 테이블에 별명(AS)을 통해 테이블명을 다르게 해야 한다는 것입니다.
mysql> SELECT products.product_name AS first_name, 
              products.product_name AS second_name 
         FROM products 
        INNER JOIN products AS prod 
           ON prod.product_no = products.product_no;

+------------+-------------+
| first_name | second_name |
+------------+-------------+
| 신발       | 신발        |
| 코트       | 코트        |
| 스웨터     | 스웨터      |
+------------+-------------+
3 rows in set (0.01 sec)

자기결합은 자기 자신의 기본키를 참조하는 열을 자기 자신이 가지는 데이터 구조로 되어 있을 경우에 사용됩니다.


외부결합

  • 앞선 결합 방법은 모두 내부결합이었습니다.

    결합의 종류에는 외부결합도 있습니다.

  • 외부결합은 교차결합으로 결합 조건을 지정하여 검색한다는 부분이 동일하지만 어느 한 쪽에만 존재하는 데이터행을 어떻게 다룰지를 변경할 수 있는 결합 방법입니다.

  • 테이블 products에는 product_no가 0004인 행이 존재하는데 테이블 product_stocks에는 존재하지 않는다고 가정해봅시다.

  • INNER JOIN 명령을 사용하여 내부결합을 하면 아래와 같이 product_no의 값이 0004인 행은 검색되지 않습니다.

mysql > SELECT * 
          FROM products 
          JOIN product_stocks 
            ON products.product_no = product_stocks.product_no;

+------------+--------------+--------+------------+---------------------+-------+
| product_no | product_name | price  | product_no | received_date       | stock |
+------------+--------------+--------+------------+---------------------+-------+
| 0001       | 신발         |  24.00 | 0001       | 2021-12-09 18:06:12 |    20 |
| 0002       | 코트         | 100.50 | 0002       | 2021-12-09 18:06:12 |    30 |
| 0001       | 신발         |  24.00 | 0001       | 2021-10-09 08:06:12 |    50 |
| 0003       | 스웨터       |  49.50 | 0003       | 2021-10-09 08:06:12 |    70 |
+------------+--------------+--------+------------+---------------------+-------+
4 rows in set (0.00 sec)

MySQL에서 단순히 JOIN만 사용할 경우 내부적으로 내부결합인 INNER JOIN 명령으로 인식합니다.

그러나 아래와 같이 LEFT JOIN 명령을 사용하여 이를 외부결합할 경우 product_no의 값이 0004인 행도 정상적으로 출력되는 것을 확인할 수 있습니다.

mysql > SELECT * 
          FROM products 
          LEFT JOIN product_stocks 
            ON products.product_no = product_stocks.product_no;

+------------+--------------+--------+------------+---------------------+-------+
| product_no | product_name | price  | product_no | received_date       | stock |
+------------+--------------+--------+------------+---------------------+-------+
| 0001       | 신발         |  24.00 | 0001       | 2021-12-09 18:06:12 |    20 |
| 0001       | 신발         |  24.00 | 0001       | 2021-10-09 08:06:12 |    50 |
| 0002       | 코트         | 100.50 | 0002       | 2021-12-09 18:06:12 |    30 |
| 0003       | 스웨터       |  49.50 | 0003       | 2021-10-09 08:06:12 |    70 |
| 0004       | 니트         |  24.50 | NULL       | NULL                |  NULL |
+------------+--------------+--------+------------+---------------------+-------+
5 rows in set (0.00 sec)

이때 우측 테이블인 product_stocks를 기준으로 하고 싶으면 LEFT JOIN 명령 대신 RIGHT JOIN을 사용하면 됩니다.

그러면 아래와 같이 내부결합을 한 것과 동일한 결괏값을 반환합니다.

mysql > SELECT * 
          FROM products 
         RIGHT JOIN product_stocks 
            ON products.product_no = product_stocks.product_no;

+------------+--------------+--------+------------+---------------------+-------+
| product_no | product_name | price  | product_no | received_date       | stock |
+------------+--------------+--------+------------+---------------------+-------+
| 0001       | 신발         |  24.00 | 0001       | 2021-12-09 18:06:12 |    20 |
| 0002       | 코트         | 100.50 | 0002       | 2021-12-09 18:06:12 |    30 |
| 0001       | 신발         |  24.00 | 0001       | 2021-10-09 08:06:12 |    50 |
| 0003       | 스웨터       |  49.50 | 0003       | 2021-10-09 08:06:12 |    70 |
+------------+--------------+--------+------------+---------------------+-------+
4 rows in set (0.00 sec)

구식방법에서의 외부결합과 표준 SQL

  • 구식 결합방법에서는 FROM 구에 결합 조건을 기술하지 않고 WHERE 구로 결합 조건을 지정합니다.

  • 그리고 이때 특수한 연산자를 붙이지 않으면 내부결합으로 인지하고 특수기호를 붙여서 조건식을 지정하면 외부결합으로 인지합니다.

  • 아래는 Oracle에서의 외부결합 예시입니다.

    Oracle의 경우 외부결합을 위해 특수 기호 (+)를 붙이고 SQL Server의 경우 *= 또는 =*를 붙입니다.

SELECT * FROM products
  FROM products, product_stocks
 WHERE products.no = product_stocks.product_no (+)
  • 현재는 표준화로 인해 내부결합은 INNER JOIN 명령을, 외부결합은 LEFT JOIN 또는 RIGHT JOIN 명령을 사용하도록 권장합니다.
profile
[DATABASE] 비전공자 출신의 개발 도전!

0개의 댓글

관련 채용 정보