프로그래밍 일기 — 데이터베이스 — Part 2

배우는 자(Learner Of Life)
12 min readMay 8, 2024

데이터베이스의 정규화(Normalization)

데이터도 깔끔해야 보기 좋고, 쓰기 좋다(1).

지난 글에서 데이터베이스의 여러 SQL 구분에 대해 배웠다. 다시 한번 리뷰하자면, DDL은 데이터베이스의 구조를 정의하는 부분이고, DQL은 데이터베이스내 스키마(Schema)간의 관계를 정의하고, DML은 데이터를 본격적으로 다루는 부분이며 거의 대부분의 유저가 사용하는 부분이다. DCL은 데이터베이스에 대한 접근을 제어하는 명령어들이고, TCL은 데이터베이스의 안정성을 보장하기 위해 여러 쿼리를 한번에 수행하는 Transaction을 제어하는 부분으로써, 하나의 쿼리라도 실패할 경우 Transaction을 Rollback시켜 데이터베이스의 신뢰성을 높여주는 부분이다.

데이터베이스에 대한 학습은 여기에서 끝나지 않는다. 데이터베이스를 다루는데 있어 정규화(Normalization)또한 이해해야하는 중요한 개념 중 하나다. 과연 이 정규화는 도대체 어떤 의미이며, 데이터베이스를 다루는데 있어 어떠한 이점을 가져다줄까?

데이터베이스의 정규화(Normalization)

정규화란 데이터베이스에 데이터를 정리하는 과정을 말한다. 예를 들어 데이터의 중복성(Redunancy)과 일관성없는 의존성(Inconsistent Dependency)를 제거하는 방식으로 테이블을 생성하거나, 데이터를 보호하고 데이터베이스를 더욱 유연하게 할 수 있는 특정 규칙에 기반해 테이블간의 관계를 설정하는 작업들이 이에 포함된다.

중복되는 데이터는 메모리 상에서 쓸떼없는 공간을 차지할 뿐만아니라, 유지보수측면에서도 문제를 발생시킬 수 있다. 만약 하나 이상의 테이블에 존재하는 데이터가 수정이 필요할 경우, 모든 테이블에서 해당 데이터가 같은 방식으로 변경되어야한다. 예를 들어 Customers라는 테이블에서 특정 고객의 주소 정보가 변경되어야한다면, 해당 데이터가 데이터베이스내 다른 테이블에 존재하지 않아야 변경이 용이하다.

일관성없는 의존성(Inconsistent Dependency)

그렇다면 “일관성없는 의존성(Inconsistent Dependency)”이란 무엇일까? 예를 들어 고객의 이름, 주소 등 신상정보를 담은 Customers라는 테이블이 있다면, 이전에 언급한 특정 고객의 주소 정보를 이 테이블에서 확인하는 것은 일리가 있다. 그러나, 해당 고객을 담당하는 내부 담당 직원의 연봉 정보를 보고 싶다면, 아마도 Customers 테이블에서 확인하는 것은 논리적이지 못할 것이다. 이 상황에서 직원의 연봉 정보는 고객이 아닌 직원의 정보와 관계가있다. 즉, 직원 연봉의 정보는 고객이아닌 직원의 정보에 의존한다. 따라서 이 정보는 Customers라는 테이블이 아니라 Employees라는 테이블을 만들어 입력하는 것이 논리적일 것이다. 일관성없는 의존성은 데이터를 찾는 경로가 부재하거나 손상된 경우를 전제하기에 데이터를 접근하기 어렵게 만든다.

데이터베이스 정규화의 규칙

데이터베이스 정규화에는 몇 가지 규칙이 존재한다. 각 규칙은 “정규화 형태(Normal Form)”으로 불린다. 만약 첫 번째 규칙이 발견된다면, 데이터베이스는 “첫 번째 정규화 형태"를 갖는다고 말할 수 있다. 만약 첫 세 가지 규칙이 발견된다면, 데이터베이스는 “세 번째 정규화 형태"를 갖는다고 말할 수 있다. 세 번째 형태를 넘어서는 것도 있으나, 대부분의 어플리케이션에서는 최대 세 번째 정규화 형태까지를 만족하는 편이다.

아래에 소개할 정규화 규칙들은 데이터베이스의 신뢰성을 높여주는데 도움을 주지만, 다른 모든 규칙이나 표준들이 그렇듯 현실 세계에서 모든 상황에 완벽히 적용되기는 힘들 수 있다. 또한 일반적으로 정규화는 추가적인 테이블의 생성을 요구하기에, 일부 사용자들은 이를 번거롭다고 여기기도한다. 그러나 현실의 거의 대부분의 데이터베이스는 세 번째 정규화 형태까지 만족시켜 데이터베이스의 신뢰성을 보증하기에, 그렇지 않은 경우에 대해서는 추후 발생할 수 있는 데이터 중복이나 일관성없는 의존성과 같은 문제들을 해결할 수 있는 방법을 가능한한 찾아 적용하는 것이 권장된다.

세 가지 단계의 정규화 형태를 적용하기에 앞서, 아래 비정규화된 상태의 테이블을 하나 가정한다.

비정규화된 상태의 Students 테이블(2)

첫 번째 정규화 형태(First Normal Form)

첫 번째 정규화 형태는 아래 작업을 수행함으로써 만족시킬 수 있다.

  • 각 테이블에서 반복되는 그룹을 제거한다.
  • 다른 테이블에 존재하는 관계(혹은 의존성이)가 있는 데이터를 별도의 테이블을 만들어 분리한다.
  • 기본키(Primary Key)를 통해 서로 다른 테이블에 관계(혹은 의존성이)가 있는 데이터를 구분한다.

이때 주의할 점은, 하나의 테이블에서 비슷한 데이터를 저장하기 위해 여러 필드를 만들지 않는 것이다. 예를 들어, 두 가지 소스에서 나올 수 있는 인벤토리 아이템의 정보를 찾고자 할 경우, 인벤토리 데이터가 Vendor Code 1과 Vendor Code 2에 대한 필드를 포함시킬 수 있을 것이다. 이 상태에서 세 번째 필드를 추가하면 어떻게 될까? 여기서 Vendor Code 3를 추가하는 것은, 프로그램 및 데이터베이스내 테이블의 수정을 요구하기에 Vendor Code의 정보가 증가할 때마다 유동적으로 대응하는 것을 번거롭게 만들 수 있다.

이때는 모든 Vendor Code 정보를 Vendors라는 테이블을 만들어 이 곳에 저장하고, 각 Vendor Code의 데이터를 갖는 본래 테이블의 인벤토리 정보와 연결하는 것이 좋다. 인벤토리의 기본키로 Vendors 테이블에 연결하거나 Vendors 테이블의 키를 통해 인벤토리 정보와 연결시키는 방식을 활용하는 것이 더 효과적이다.

위 Students 테이블에 대해 첫 번째 정규화 형태를 적용해보자. 아래와 같이 중복된 그룹이 없는 테이블이 만들어져야한다. 테이블은 오직 2개의 차원만 가져야한다. 즉, 한 학생이 여러 수업(Class)를 가질 수 있지만, 이들을 개별적인 필드로 만들기보다는, Class라는 하나의 컬럼으로 통합하는 것이 더 효과적이다. 일반적인 Excel 시트는 3차원까지 구성될때도 있지만, 테이블은 그렇지 않다. 하나의 데이터가 여러 다른 데이터와 연관이 되어 있는 상태를 One-to-Many 관계라고하는데, 이러한 One-to-Many 관계가 같은 테이블에 구현되어 있는 것은 좋지 않다. 이를 해결하기위해, 아래 처럼 Class라는 별도의 칼럼을 가진 테이블을 새로 만들어 첫 번째 정규화 형태를 적용해 줄 수 있다.

첫 번째 정규화 형태를 적용시킨 Students 테이블(2).

두 번째 정규화 형태(Second Normal Form)

두 번째 정규화 형태는 아래 작업들을 통해 구현할 수 있다.

  • 여러 데이터의 값에 적용되는 별도의 테이블을 생성한다.
  • 각 테이블을 외래키(Foreign Key)를 통해 연관짓는다.

데이터는 테이블의 기본키(Primary Key)혹은 필요에따라서 혼합키(Compound Key)이외에 그 어떤 다른 것에 의존해서는 안된다. 예를 들어, 회계 시스템에서 고객의 주소 정보가 존재한다고 가정하자. 주소는 Customers 테이블이 필요로하는 정보이지만, 동시에 Orders, Shipping 및 Invoices 등 여러 다른 데이터 테이블에서도 필요로 할 수 있다. 그렇다면 고객의 주소 정보를 각각의 테이블에 별도로 입력하는 것보다는, Customers 테이블에만 저장하거나 Addresses라는 별도의 테이블에 만들어 한 곳에서 관리하는 것이 더 효율적이다.

예를 들어 이전에 첫 번째 정규화 형태를 취한 테이블을 두 번째 정규화 형태를 만족하도록 수정해보자. 위 처럼 각 Student 마다 여러 Class 데이터를 가질 수 있지만, Class 컬럼이 Student 칼럼에 기능적으로 의존하지 않는다. 즉, 아직 각 Student 칼럼의 데이터와 Class 컬럼의 데이터가 서로 기본키로 연관되어 있지는 않은 상태인 것이다. 따라서 아래와 같이 Students라는 테이블과 Classes라는 테이블을 분리하여, Student 칼럼을 기본키로 삼아 두 테이블을 연결할 수 있다.

Class 칼럼을 갖지 않는 Students 테이블로써 Student 칼럼이 기본키가된다(2).
Student 칼럼을 외래키로 삼아 각각에 대한 Class 값을 정리한 Classes 테이블(2)

세 번째 정규화(Third Normal Form)

마지막 세 번째 정규화는 아래 작업을 통해 구현할 수 있다.

  • 키에 의존하지 않는 필드를 제거하기

테이블내에서 데이터의 값이 데이터의 키에 포함되지 않으면, 분리시키는 것이 좋다. 일반적으로 여러 필드의 데이터를 테이블내 한 개 이상의 데이터에 적용한다면, 다른 테이블로 분리하는 것이 좋다. 예를 들어 Recruitment라는, 대졸 신입 직원 채용에 관련된 데이터를 포함하는 테이블에서, 지원자의 대학 이름과 주소 정보가 있다고 가정하자. 이때, 단체 메일을 보내기위해 국내에 존재하는 모든 대학들의 이름을 총 망라한 테이블이 필요하다. 만약 대학 정보가 Candidates 테이블에 저장되어있다고한다면, 현재 지원자들이 다니지 않는 대학들에 대한 정보가 없다는 문제가 있을 수 있다. 이때에는 Universities라는 테이블을 별도로 만들어서 University를 구별하는 Code Key를 기본키로 삼아 Candidates 테이블의 대학 이름 정보와 연결할 수 있다.

예를 들어, 마지막 세 번째 정규화 형태를 위 두 번째 정규화를 만족시킨 테이블들에 적용한다면 아래와 같다. 즉 Students 테이블의 Advisor 속성을 Adv-Room 속성과 분리시키는 것이다. Student 칼럼과 Class 칼럼을 기본키를 통해 관계지어 별도의 테이블들로 분리시킨 것처럼, 이 두 가지 속성 역시 하나는 Students 테이블에 남기고, 다른 속성은 Faculty라는 새로운 테이블을 만들어 분리시킨 것이다. 즉, 최대한 서로 관계 지어질 수 있는 남은 모든 데이터 필드를 별도의 테이블로 분리시켜 기본키로 관계짓는 것이다.

Student와 Advisor 칼럼 만을 남긴 Students 테이블(2)
기존 Students 테이블에서 Adv Room 정보를 분리해 Room 속성으로 재정의한 Faculty 테이블(2)

단, 이렇게 세 번째 정규화 형태를 적용하는 것이 모든 상태에 적용가능하지 않을 수 있다. 예를 들어, 고객 정보를 담은 Customers 테이블이 있고, 해당 테이블내 존재하는 모든 필드간 의존성을 제거하고 싶다고 가정하자. 그렇다면 세 번째 정규화 형태를 적용할 경우, 고객 하나마다 주소 정보, 번지수 정보, 담당 영업 직원 정보, 고객의 등급 정보 등등, 많은 고객의 개별적인 정보를 다른 테이블로 전부 분리해야할 것이다. 이론적으로 이러한 정규화를 추진하는 것이 데이터베이스의 신뢰성을 높여준다고 할지라도, 지나치게 많은 테이블의 수는 오히려 성능을 저해하고 메모리를 쓸떼없이 많이 차지하는 비효율적인 선택이 될 수도 있다.

따라서, 세 번째 정규 형태를 적용할 경우, “자주 변경되는 데이터"에만 적용하는 것이 더 효율적인 선택일 수 있다. 만약 일부 필드가 여전히 서로 한 테이블 내에서 의존성을 가지고 있다면, 특정 데이터 필드의 값이 변경될 경우, 사용자에게 모든 관련된 필드값을 검증하도록 하는 방식으로 어플리케이션을 설계하는 것이 좋다.

그 이외의 정규화 형태

BCNF(Boyce — Codd Normal Form)과 같은 네 번째 정규화 형태와 다섯 번째 정규화 형태(Fifth Normal Form) 역시 존재하지만, 앞서 언급한 것처럼 실제 어플리케이션에서는 거의 활용되지 않는 편이다. 물론 이러한 수준까지 데이터베이스를 정규화시킨다면 조금 더 이상적인 데이터베이스 설계에 가까워질 수 있지만, 들인 노력대비 얻는 기능적 이득이 세 번째 정규화까지 만족시키는 것에 비해서는 크지 않은 편이다.

  • 혼합키(Compound Key)(3): 다른 말로 Composite Key라고도 불리며, 두 개 이상의 속성을 가지고, 이 들이 테이블내 Tuple을 유니크하게 구별할 수 있게 해준다. 키의 각 속성이 자체로 키가 되어, 각 속성이 외래키로 여겨질 수 있다. 예를 들어, 아래와 같은 테이블 A에서, 하나의 속성만으로 개별적인 데이터를 구분하는게 불가능한 경우가 있다. 즉, 하나의 속성만으로 기본키를 만들 수 없는 경우가 있다. 이럴 때는 아래와 같이 Cust_Id와 Prod_code 두 개의 필드를 혼합해 홉합키를 만들어 구분할 수 있다.
하나의 키 만으로 개별적인 데이터 구분이 불가할 경우 두 개 이상을 혼합하여 혼합키를 생성한다(3).

참조:

(1) https://pixabay.com/photos/campus-normal-university-820878/

(2) https://learn.microsoft.com/en-us/office/troubleshoot/access/database-normalization-description

(3) https://www.javatpoint.com/composite-key-in-dbms

--

--

배우는 자(Learner Of Life)

배움은 죽을 때까지 끝이 없다. 어쩌면 그게 우리가 살아있다는 증거일지도 모른다. 배움을 멈추는 순간, 혹은 배움의 기회가 더 이상 존재하지 않는 순간, 우리의 삶은 어쩌면 거기서 끝나는 것은 아닐까? 나는 배운다 그러므로 나는 존재한다. 배울 수 있음에, 그래서 살아 있음에 감사한다.