1NF

1NF의 조건은 한마디로 정리하면 “릴레이션 이어야 한다”는 것이다.

관계형 모델이야 당연히 릴레이션으로 구성되겠지만 SQL과 같은 우리가 실제로 사용하는 데이터베이스에 적용되는 조건이다.

SQL의 테이블이 1NF이기위한 조건은 다음과 같다.

  1. 행이 위에서 아래로 정렬돼 있지 않다.
  2. 열이 왼쪽에서 오른쪽으로 정렬돼 있지 않다.
  3. 중복하는 행이 존재하지 않는다.
  4. 각 행과 열의 교차점(즉 열의 값)은 도메인(데이터형)에 속하는 요소의 값을 딱 한개만 가진다.
  5. 모든 열의 값은 정의된 것이어야 하고 각 행은 항상 존재한다.

1번, 2번 조건

실제 SQL 사양에서는 테이블 컬럼에 순서가 존재하기에 이를 만족하지 않는다. 하지만 이는 엄밀히 따졌을 때의 얘기이고 실제론 컬럼이나 행의 위치에 위존하는 쿼리를 작성하지 않으면 된다.

데이터베이스 사양에서 순서가 존재하더라도 그 순서에 의존하지 않는 쿼리를 작성하지 않는다면 1NF 1, 2번 조건을 만족할 수 있다는 것이다.

컬럼의 순서에 의존하는 쿼리는 다음과 같은 것들이 있다.

  • SELECT *로 모든 값을 가져온 후 컬럼의 위치에 따라서 데이터에 접근하는 것
  • ORDER BY의 인수로 컬럼에 위치를 지정하는 것

또한 ROWIDObjectID와 같이 행의 순서에 있는 경우에도 이러한 기능을 사용하지 않으면 된다.

3번 조건

3번 조건은 단순하게 중복되는 행이 존재하지 않으면 된다. 기본키나 유니크키와 같은 조건을 붙이면 이런 중복이 사라지게 된다.

하지만 실제로 중요한 것은 실제로 행의 모든 값이 똑같아 중복되는 것이 아니라 의미적으로 중복되지 않는 것이 중요하다. 실제로 의미가 중복되지 않는다면 위와 같은 제약이 없더라도 3번 조건은 성립한다.

이름 학과 등록금
이도환 경영학과 400
노종수 컴퓨터공학과 500
노종수 전자공학과 400
김명환 화학공학과 450

이전 글에서 살펴본 위와 같은 위 테이블에서 만약 실수로 “노종수”의 등록금 값을 하나만 수정했다면 실제 행들이 중복되지는 않지만 의미적으로 값이 중복된다.

릴레이션은 참인 명제의 집합이므로 “노종수는 컴퓨터공학과로 등록금으로 500만원을 낸다”라는 명제와 “노종수는 전자공학과로 등록금으로 400만원을 낸다”이라는 명제가 모두 성립해야 한다. 모순이 생긴다는 것을 확인할 수 있다.

즉, “행이 중복되지 않으면 정규화가 되었다”라고 생각하는 것이 아니라 실제 의미적으로 중복되는 행이 존재하지는 않는지, 발생할 수 있지는 않는지를 확인하는 것이 중요하다

5번 조건

NULL이 포함되어 있으면 관계형 모델이 무너진다. 관계형 모델에서의 NULL은 값을 정할 수 없음이기 때문이다. 따라서 모든 행, 모든 컬럼은 구체적인 값을 가져야만 한다.

단순히 NULL을 없애기위해 NOT NULL의 조건을 건다고 해결되는 문제는 아니다. age 컬럼에 나이가 없음을 나타내기 위해 -1이나 1000과 같은 값을 넣는 것보다는 차라리 NULL을 넣는 것이 명확하고 로직을 간단하게 만들 수 있다.

NULL을 없애는 가장 좋은 방법은 테이블을 나누는 것이다. 새로운 데이터를 삽입할 때 NULL이 들어간다는 것은 아직 그 데이터가 필요없다는 의미로 아직 그 테이블에는 해당 컬럼이 필요없다는 의미이다.

이러한 조건이 지켜질 때 5번 조건이 만족된다.

4번 조건

4번 조건에 대한 논의는 매우 많다. 1NF 조건을 만든 E.F.Codd는 1NF의 요건으로 원자성을 이야기했지만 이후 정의가 모호하다는 비판을 받았다.

값은 원자여야한다. 즉, 값이 그 이상 분해될 수 없다는 정의 자체는 옳지만 해석에 모호성이 존재한다는 것이다.

문자열은 각각의 문자로 분해할 수 있으니 원자성이 존재하지 않는 것이고 문자열은 테이블에 포함되서는 안되는 것일까? 이름의 경우 성과 이름으로 나눌 수 있으니 원자성이 성립하지 않는 것일까? 주소와 같은 경우엔 나라, 시, 구, 빌딩 등등으로 분해할 수 있으니 반드시 모든 내용을 분리해서 저장해야 하는 것일까? 바로 이런 부분에서 모호한 점이 존재한다는 것이다.

관계형 모델에서는 의미가 있는 한 묶음의 데이터를 한 단위로 취급해야 한다. 문자열은 각각의 문자로 분해할 수 있지만 더이상 동일한 의미를 가지지 않을 수도 있다. ‘관계형’이라는 사용자의 닉네임을 ‘관’, ‘계’, ‘형’ 이라는 문자로 나누었을 때 이전과 동일한 의미를 가진다고 할 수 없을 것이다.

따라서 모든 이름을 성과 이름, 모든 주소를 나눠야만 할까? 정답은 도메인에 있다.

만약 우리 시스템이 같은 성을 가진 사람들끼리 처리해야 하는 경우가 있다면 이름에서 SUBSTRING으로 성을 가져와 처리하는 것보다 성과 이름을 나누어 저장하는 것이 좋겠지만 그럴 일이 없다면 성과 이름을 한 문자열로 저장해도 괜찮다는 것이다.

즉, 도메인 설계를 하고 그에 따라 적절히 데이터베이스를 구성한다면 값의 원자성이 지켜질 수 있다.

이전 글에서 다룬 예를 살펴보자

이름 학과 등록금
이도환 경영학과 400
노종수 컴퓨터공학과, 전자공학과 500
김명환 화학공학과 450

위와 같은 설계는 원자성이 지켜지지 않았다. 또한 컬럼 내의 값을 ’,‘와 같은 형식자로 분리해서 처리해야 하므로 관계형 모델을 기반으로 논리를 만들 수도 없다.

이름 학과 1 학과 2 등록금
이도환 경영학과 400
노종수 컴퓨터공학과 전자공학과 500
김명환 화학공학과 450

이를 해결하기 위해 다음과 같은 설계를 했다고 생각해보자. 다전공을 하지 않는 학생의 경우 학과 2에 NULL이 들어가게 되어버리며 학과에 순서가 생겼으므로 1NF의 조건을 어긴다. 이를 해결하는 방법은 앞에서 봤듯이 다음과 같다.

이름 학과 등록금
이도환 경영학과 400
노종수 컴퓨터공학과 500
노종수 전자공학과 500
김명환 화학공학과 450

위 테이블은 1NF의 조건을 만족하며 “노종수”에 대해 다음과 같은 문장도 모두 성립한다.

노종수는 컴퓨터공학과에 속해있으며 등록금으로 500만원을 낸다
노종수는 전자공학과에 속해있으며 등록금으로 500만원을 낸다

1NF의 조건을 만족하지만 이전 글에서 말했듯이 전자공학과의 등록금이 인상되었을 때 처리 등에서 문제가 발생하는 것을 확인할 수 있다.

이는 **함수 종속성(Functional Dependency)**에 대한 문제로 다음 글에서 살펴본다.