데이터베이스 정규화는 같은 사실을 한 곳에만 적도록 표를 나누는 설계 방법입니다. 회원 도시를 주문마다 적어 두면 이사한 회원의 주소를 여러 줄에서 고쳐야 하는데요, 정규화는 이런 반복을 없애 고칠 곳을 하나로 줄여 줍니다. 저도 SQL 을 처음 배우면서 「한 시트에 다 있으면 편한데 왜 굳이 나눌까?」가 가장 궁금했는데요, 이번 글에서는 한 장짜리 주문 표가 어디서 막히는지 보고, 1·2·3 정규형 순서대로 직접 나눠 보겠습니다. 지난 글: 기본 키와 외래 키: 표와 표는 어떻게 이어질까
SQL 기초 시리즈 3/12 · 전체 커리큘럼
오늘 배울 개념
엑셀로 주문을 관리하다 보면 주문 시트 한 장에 회원 이름, 도시, 상품명, 가격까지 모두 적게 되죠. 보기에는 편하지만 같은 회원이 열 번 주문하면 그 회원의 도시도 열 번 적힙니다. 비유하면 정규화는 서랍 정리와 비슷합니다. 양말·속옷·티셔츠를 한 서랍에 섞어 두면 꺼낼 때마다 뒤적여야 하는 것처럼, 성격이 다른 정보를 한 표에 섞어 두면 고치거나 지울 때마다 다른 정보까지 건드리게 되기 때문입니다.
Oracle 데이터 웨어하우스 설계 문서는 정규화를 「각 사실을 딱 한 곳에만 두어」 중복과 삽입·수정·삭제 이상을 피하려는 설계 과정이라고 설명합니다. MySQL 용어집도 정규화를 데이터를 여러 테이블로 나누고, 반복되는 값을 ID 로 가리키는 한 행으로 모아 같은 값을 여러 번 저장·수정하지 않게 하는 전략으로 설명하죠.
예시로 이해하기: 한 장짜리 주문 표는 어디서 막힐까요?
지난 글의 문구점 주문을 엑셀처럼 한 표에 모두 적어 보겠습니다(가상 데이터). 주문번호와 상품번호를 묶은 것이 기본 키입니다.
order_sheet (한 장짜리 주문 표)
| order_id | product_id | ordered_on | member_id | name | city | product_name | price | quantity |
|---|---|---|---|---|---|---|---|---|
| 101 | 1 | 2026-10-01 | 1 | 김하늘 | 서울 | 볼펜 | 1500 | 2 |
| 101 | 2 | 2026-10-01 | 1 | 김하늘 | 서울 | 노트 | 3000 | 1 |
| 102 | 3 | 2026-10-03 | 2 | 이도윤 | 부산 | 형광펜 | 1200 | 3 |
| 103 | 1 | 2026-10-07 | 1 | 김하늘 | 서울 | 볼펜 | 1500 | 1 |
| 104 | 2 | 2026-10-12 | 3 | 박서연 | 서울 | 노트 | 3000 | 2 |
| 105 | 3 | 2026-10-20 | 1 | 김하늘 | 서울 | 형광펜 | 1200 | 1 |
김하늘 님의 「서울」이 네 번, 상품 가격이 두 번씩 적혀 있는데요, 이 반복 때문에 세 가지 문제가 생깁니다.
- 수정 이상: 김하늘 님이 인천으로 이사해 105번 줄만 고치면, 같은 회원이 서울과 인천 두 곳에 사는 표가 됩니다. Microsoft 정규화 설명 문서도 여러 곳에 있는 데이터는 모든 곳에서 똑같이 고쳐야 한다고 짚습니다.
- 삽입 이상: 아직 아무도 사지 않은 지우개를 등록할 수 없습니다. 주문번호가 기본 키의 일부라 비워 둘 수 없기 때문인데요, 주문이 없는 새 회원도 같은 이유로 못 넣습니다.
- 삭제 이상: 102번 주문을 취소해 그 줄을 지우면 이도윤 님이 부산에 산다는 정보까지 함께 사라집니다. 이도윤 님의 주문이 그 한 줄뿐이었기 때문입니다.
개념 그림: 같은 사실을 여러 줄에 적어 두면 고칠 때, 넣을 때, 지울 때 문제가 생깁니다
로컬 PostgreSQL 에서 이 표를 만들어 보니 지우개 행은 order_id 가 NULL 이라는 오류로 거부됐고, 102번을 지우자 2번 회원의 행이 0개가 됐습니다. 그렇다면 표를 어떤 기준으로 나눠야 이 문제가 없어질까요?
1·2·3 정규형, 어떤 순서로 나눌까요?
정규형(normal form)은 정규화 규칙 하나하나를 부르는 이름입니다. Oracle 문서는 처음 세 단계를 이렇게 설명합니다.
| 단계 | 규칙 | 위 표에서 할 일 |
|---|---|---|
| 1정규형(1NF) | 반복되는 데이터 묶음과 중복 행이 없다 | 한 칸에 「볼펜 2, 노트 1」처럼 여러 값을 적지 않고 상품마다 한 줄로 |
| 2정규형(2NF) | 1정규형이면서, 키가 아닌 열이 기본 키 전체에 종속된다 | 주문번호에만, 상품번호에만 딸린 열을 떼어 냄 |
| 3정규형(3NF) | 키가 아닌 열이 다른 키가 아닌 열에 종속되지 않는다 | 회원번호에 딸린 이름·도시를 떼어 냄 |
여기서 종속은 「A 값이 정해지면 B 값이 하나로 정해진다」는 뜻입니다. PostgreSQL GROUP BY 문서도 상품 ID 가 기본 키라면 상품명과 가격이 상품 ID 에 함수적으로 종속된다고 설명하죠.
1정규형: 위 표는 이미 상품마다 한 줄이라 1정규형입니다. 엑셀에서는 상품1·상품2 열을 옆으로 늘리기 쉬운데요, Microsoft 문서는 공급처 코드 1·2 처럼 비슷한 값을 여러 열에 담지 말라고 하고, PostgreSQL 배열 문서도 배열 안을 뒤져야 한다면 항목마다 한 행인 별도 표를 고려하라고 권합니다.
2정규형: 기본 키가 (order_id, product_id) 인데 ordered_on·member_id·name·city 는 주문번호만 알면 정해지고, product_name·price 는 상품번호만 알면 정해집니다. 키 전체가 있어야 정해지는 열은 quantity 하나뿐이라, 표를 주문(orders)·상품(products)·주문 상품(order_items) 셋으로 나눕니다.
3정규형: 나눈 orders 에 아직 name·city 가 남아 있는데요, 이 둘은 주문번호가 아니라 member_id 에 딸린 값입니다. Microsoft 문서의 예시에서 지도교수 연구실 번호를 교수 표로 옮기는 것처럼, 이름·도시를 회원(members) 표로 옮기고 orders 에는 member_id 만 남깁니다.
개념 그림: 열이 무엇에 딸려 있는지 따라 표를 나누면 지난 글의 네 표가 됩니다
나눈 결과는 지난 글의 네 표와 같은 구조로, members 3행·orders 5행·products 3행·order_items 6행이 됩니다. 이제 김하늘 님의 도시는 members 한 줄만 고치면 되고, 주문 없는 최지우 님과 아직 안 팔린 지우개도 각자 표에 넣을 수 있습니다. 4·5 정규형처럼 더 높은 단계도 있지만, Microsoft 문서는 대부분의 애플리케이션에는 3정규형이면 충분하다고 봅니다.
일부러 합쳐 두는 비정규화는 언제 쓸까요?
정규화가 늘 정답은 아닌데요, MySQL 용어집은 정규화가 항상 권장되지는 않는다고 적고, 주로 조회만 하고 고칠 때는 통째로 지웠다 다시 불러오는 데이터는 중복을 감수하고 적고 큰 표로 두는 경우가 많다고 설명합니다. 이것을 비정규화(denormalization)라고 부릅니다.
| 기준 | 정규화한 표 | 비정규화한 표 |
|---|---|---|
| 잘 맞는 곳 | 주문·예약처럼 자주 넣고 고치는 시스템(OLTP) | 리포트·분석용 데이터 웨어하우스 |
| 장점 | 고칠 곳이 하나라 값이 어긋나지 않음 | 표를 이어 붙이지 않아도 바로 조회 |
| 대가 | 조회할 때 여러 표를 JOIN 해야 함 | 같은 값이 반복되고, 고칠 때 어긋날 위험 |
Oracle 문서는 3정규형 설계가 표가 많아 JOIN 이 많은 복잡한 쿼리가 필요하고 조회가 느려질 수 있다고 짚고, 분석용 스타 스키마는 이해하기 쉽고 조회가 빠르도록 중복을 받아들인다고 설명합니다. 마케터 입장에서 저는 이렇게 나눠 보려고 합니다.
- 원본 데이터가 계속 고쳐지는가? 회원 정보·주문처럼 매일 바뀌는 데이터라면 정규화한 표에 둡니다.
- 조회만 하고 매번 새로 받는가? 매주 내려받는 광고 성과 표처럼 고치지 않고 통째로 교체하는 데이터라면 넓은 표 하나로 두어도 됩니다.
- 매번 같은 JOIN 을 쓰는가? 원본은 나눠 두고, 자주 쓰는 합친 모양은 뷰(view)로 이름을 붙여 둡니다. PostgreSQL 뷰 문서는 뷰를 넉넉히 쓰는 것이 좋은 SQL 데이터베이스 설계의 핵심이라고 말합니다.
연습 문제
1. (쉬움) 위 order_sheet 에서 볼펜 가격이 1,500원에서 1,700원으로 올랐는데 101번 줄만 고쳤습니다. 어떤 이상이고, 정규화한 뒤에는 어디를 고치면 될까요?
정답 보기
수정 이상입니다. 볼펜 가격이 101번 줄(1,700원)과 103번 줄(1,500원)에서 달라집니다. 정규화한 뒤에는 products 표의 볼펜 한 줄만 고치면 됩니다.
2. (보통) 회원 표에 「관심 분야」 열을 두고 문구, 생활, 도서 처럼 쉼표로 이어 적었습니다. 어느 정규형을 어긴 것이고, 어떻게 나누면 될까요?
정답 보기
반복되는 값 묶음을 한 칸에 담아 1정규형을 어긴 것입니다. member_interests(member_id, interest) 같은 표를 따로 만들어 관심 분야마다 한 줄씩 넣고, 두 열을 묶어 기본 키로 삼습니다. 그러면 「문구에 관심 있는 회원」을 쉼표를 뒤지지 않고 찾을 수 있습니다.
3. (보통) 수강 신청 표가 (student_id, course_id, student_name, course_title, instructor, instructor_room) 이고 기본 키는 (student_id, course_id) 입니다. 2정규형과 3정규형을 어기는 열을 각각 찾아보세요.
정답 보기
student_name 은 student_id 에만, course_title·instructor 는 course_id 에만 종속돼 2정규형을 어깁니다. 학생 표와 강의 표로 떼어 내고 수강 신청 표에는 두 번호만 남깁니다. 그다음 강의 표의 instructor_room 은 강의가 아니라 강사에 딸린 값이라 3정규형을 어기므로 강사 표로 옮깁니다.
헷갈리기 쉬운 것
- 정규화는 데이터를 버리는 일이 아닙니다: 나눈 네 표를 JOIN 으로 다시 이으면 처음 order_sheet 와 같은 여섯 줄이 나옵니다. 로컬 PostgreSQL 에서 확인했고, JOIN 문법은 10회에서 다룹니다.
- 반복되는 값이 모두 중복은 아닙니다: order_items 에 주문번호 101이 두 번 나오는 것은 외래 키라서 필요한 반복입니다. 없애야 할 것은 이름·도시처럼 ID 하나로 대신할 수 있는 값을 여러 번 적는 것이죠.
- 바뀌는 값과 그때의 값은 다른 사실일 수 있습니다: 가격을 products 에만 두면 가격을 올린 순간 지난 주문 금액도 바뀌어 보입니다. 주문 당시 금액을 남겨야 한다면 order_items 에 그때 단가를 따로 두는 설계도 생각해 볼 수 있는데요, 이것은 「주문 시점 가격」이라는 다른 사실입니다.
개념 그림: 자주 고치는 원본은 나누고, 조회용 표는 합쳐 둘 수 있습니다
마치며
데이터베이스 정규화는 열이 무엇에 딸려 있는지 보고 같은 사실을 한 곳에만 두도록 표를 나누는 일이고, 1정규형은 반복되는 묶음 없애기, 2정규형은 키 전체에 종속, 3정규형은 키가 아닌 열끼리 종속이 없도록 하는 순서로 진행됩니다. 엑셀 한 시트에 다 적던 습관을 「이 열은 무엇에 딸린 값일까」라는 질문으로 바꿔 보시면 이해가 쉬울 겁니다. 저도 시리즈를 따라가며 공부하고 있으니 전체 커리큘럼을 참고해 주세요. 다음 글에서는 숫자·문자·날짜 같은 데이터 타입과 빈 값인 NULL 을 다룹니다.
자주 묻는 질문
- 정규화는 왜 하나요?
- 같은 사실을 여러 곳에 적어 두면 고칠 때 일부만 바뀌거나, 다른 정보가 없어서 새 데이터를 넣지 못하거나, 한 줄을 지울 때 필요한 정보까지 사라지는 문제가 생깁니다. 정규화는 각 사실을 한 곳에만 두도록 표를 나눠 이런 삽입·수정·삭제 이상을 피하려는 설계 방법입니다.
- 제1정규형 제2정규형 제3정규형은 어떻게 다른가요?
- 1정규형은 반복되는 데이터 묶음과 중복 행 없이 항목마다 한 줄로 적는 단계입니다. 2정규형은 거기에 더해 키가 아닌 열이 기본 키 전체에 종속되도록, 3정규형은 키가 아닌 열이 다른 키가 아닌 열에 종속되지 않도록 표를 나눈 단계입니다.
- 비정규화는 언제 쓰나요?
- 주로 조회만 하고 고칠 때는 통째로 다시 불러오는 리포트·분석용 데이터에서 씁니다. 같은 값이 반복되는 대신 표를 이어 붙이지 않고 바로 조회할 수 있기 때문입니다. 자주 고치는 원본 데이터는 정규화해 두고, 합친 모양이 필요하면 뷰를 만드는 방법도 있습니다.
관련 글 더 보기
- 기본 키와 외래 키: 표와 표는 어떻게 이어질까 - SQL 기초 시리즈 2회, 나눈 표를 잇는 키
- 관계형 데이터베이스 구조: 테이블·행·열·스키마 - SQL 기초 시리즈 1회
- 엑셀 중복 값 제거와 찾기 | 중복 제거·조건부 서식·UNIQUE 함수 - 엑셀 시트에서 반복되는 값을 다루는 방법
