DB SQL (JOIN - 정규화)

sungsimdangmascot·2026년 4월 21일

1. 데이터 정규화 (Data Normalization): 쪼개고 나누기

정규화는 쉽게 말해 '중복을 제거하고, 데이터를 논리적으로 쪼개어 저장하는 과정'이다.

왜 굳이 데이터를 쪼개야 할까? 데이터를 한 곳에 몰아넣으면 관리하기 편할 것 같지만, 실제로는 '이상 현상(Anomaly)'이라는 치명적인 문제가 발생하기 때문이다.

정규화를 하지 않았을 때의 문제점 (비정규화 상태)

게시물을 저장하는 article 테이블 하나에 모든 정보를 다 때려 넣었다고 가정해 보자.

article_idtitlecontentauthor_nameauthor_email
1첫 번째 글내용1김코딩kim@test.com
2두 번째 글내용2김코딩kim@test.com
3세 번째 글내용3박해커park@test.com

이 구조는 다음과 같은 치명적인 문제를 안고 있다.

  1. 수정 이상(Update Anomaly): '김코딩'이 이메일을 kim@new.com으로 변경했다. 우리는 article_id 1번과 2번의 이메일을 모두 찾아서 수정해야 한다. 만약 김코딩이 쓴 글이 10만 개라면? 10만 번의 수정이 필요하고, 실수로 하나라도 누락되면 데이터가 불일치하게 된다.
  2. 삭제 이상(Deletion Anomaly): '박해커'가 쓴 '세 번째 글'을 삭제하려고 한다. 그런데 글을 삭제하는 순간 '박해커'라는 회원의 이메일 정보까지 데이터베이스에서 영영 날아가 버린다.
  3. 삽입 이상(Insertion Anomaly): '이초보'라는 신규 회원이 가입했다. 아직 글을 쓰지 않았는데, 이 테이블에 회원을 등록하려면 article_id, title, content에 억지로 NULL 값이나 더미 데이터를 넣어야만 한다.

정규화의 적용 (관계를 맺어 분리하기)

이러한 문제를 해결하기 위해 성격이 다른 데이터를 분리한다. '게시물' 정보와 '회원(작성자)' 정보를 나누는 것이다. 보통 실무에서는 제1정규형부터 제3정규형(또는 BCNF)까지 주로 고려한다.

member 테이블 (회원 정보)
| member_id (PK) | author_name | author_email |
| :--- | :--- | :--- |
| 100 | 김코딩 | kim@test.com |
| 101 | 박해커 | park@test.com |

article 테이블 (게시물 정보)
| article_id (PK) | title | content | member_id (FK) |
| :--- | :--- | :--- | :--- |
| 1 | 첫 번째 글 | 내용1 | 100 |
| 2 | 두 번째 글 | 내용2 | 100 |
| 3 | 세 번째 글 | 내용3 | 101 |

  • PK (Primary Key, 기본키): 각 행을 고유하게 식별하는 값.
  • FK (Foreign Key, 외래키): 다른 테이블의 PK를 참조하는 값. 여기서 article 테이블의 member_id는 글 작성자가 누구인지 알려주는 연결 고리 역할을 한다.

이렇게 정규화를 거치면 '김코딩'의 이메일이 바뀌어도 member 테이블의 단 한 줄만 수정하면 된다. 데이터의 무결성(정확성과 일관성)이 지켜지는 것이다.


2. 조인 (JOIN): 흩어진 조각 맞추기

[Image of SQL Joins Venn Diagram]

정규화를 통해 데이터를 안전하게 보관할 수 있게 되었지만, 치명적인 단점이 하나 생겼다. 바로 데이터를 조회할 때 불편해졌다는 것이다.

게시물 목록을 보여줄 때 "글 제목, 내용, 작성자 이름"을 함께 보여주어야 하는데, 이제 이 정보들은 두 개의 테이블(article, member)에 쪼개져 있다. 이 쪼개진 테이블들을 다시 하나로 합쳐서 보여주는 마법이 바로 JOIN이다.

조인의 핵심 원리

조인은 두 테이블 간의 연결 고리(주로 PK와 FK)를 기준으로 행을 결합한다.

대표적인 조인의 종류

1) INNER JOIN (교집합)

가장 많이 쓰이는 조인이다. 두 테이블에 모두 데이터가 존재하는, 즉 연결 고리가 딱 맞아떨어지는 행들만 가져온다.

SELECT 
    A.article_id, 
    A.title, 
    M.author_name
FROM article AS A
INNER JOIN member AS M 
    ON A.member_id = M.member_id;

해석: article 테이블(A)과 member 테이블(M)을 합칠 건데, A의 member_id와 M의 member_id가 같은 것들만 묶어서 제목과 작성자 이름을 보여줘.

2) LEFT (OUTER) JOIN (왼쪽 기준 전체)

왼쪽 테이블의 모든 데이터는 무조건 다 가져오고, 오른쪽 테이블에서는 조건에 맞는 데이터만 가져와서 붙인다. 오른쪽 테이블에 맞는 데이터가 없다면 NULL(빈칸)로 채운다.

SELECT 
    M.author_name, 
    A.title
FROM member AS M
LEFT JOIN article AS A 
    ON M.member_id = A.member_id;

해석: member 테이블(M)에 있는 회원은 전부 다 출력해. 그리고 그 회원이 쓴 글이 article 테이블(A)에 있으면 제목을 붙여주고, 글을 한 번도 안 쓴 회원이라면 제목 부분은 NULL로 남겨둬. (가입만 하고 글은 안 쓴 '이초보'를 찾아낼 때 유용하다).

3) RIGHT JOIN / FULL OUTER JOIN

  • RIGHT JOIN: LEFT JOIN의 반대다. 오른쪽 테이블을 기준으로 모두 가져온다. 실무에서는 보통 테이블의 위치를 바꿔서 LEFT JOIN을 쓰는 경우가 더 많다.
  • FULL OUTER JOIN: 합집합이다. 양쪽 테이블의 모든 데이터를 가져오고, 매칭되지 않는 부분은 모두 NULL로 채운다. (MySQL 등 일부 DB에서는 직접 지원하지 않아 LEFT JOIN과 RIGHT JOIN을 UNION으로 합쳐서 사용한다).

요약

  • 정규화: 데이터를 효율적으로 '저장'하기 위해 중복을 없애고 테이블을 논리적으로 분리하는 작업.
  • 조인: 분리되어 저장된 데이터를 사용자가 보기 편하게 다시 하나로 '연결하여 조회'하는 작업.

정규화와 조인은 동전의 양면과 같다. 저장의 효율성을 위해 정규화를 하면 필연적으로 조회할 때 조인을 사용해야 한다.

profile
성심당마스코트

0개의 댓글