1. 참조 무결성

 

1) 참조 관계

 

👩🏻‍🚀  제 1 정규화에서 예로 들었던 직원과 프로젝트 테이블을 생성하고 관계를 맺는다

       ➡️ 두 개의 테이블을 만들되 프로젝트에 비용 필드를 추가

 

 

📌 먼저 부모 테이블에 해당하는 직원 테이블을 생성하고 샘플 데이터를 입력

 

CREATE TABLE `tEmployee`
(
`name` CHAR(10) PRIMARY KEY, // 직원의 이름을 기본키로 지정
`salary` INT NOT NULL, // 월급
`addr` VARCHAR(30) NOT NULL // 주소
);
INSERT INTO `tEmployee` VALUES ('김상형', 650, '이천시');
INSERT INTO `tEmployee` VALUES ('문종민', 480, '대구시');
INSERT INTO `tEmployee` VALUES ('권성직', 625, '안동시');
 

 

📌 다음은 직원 테이블과 관계를 맺을 프로젝트 테이블을 생성

 

CREATE TABLE `tProject`
(
`projectID` INT PRIMARY KEY,  --기본키인 projectID는 자동 증가열로 정의하는 것이 간편
`employee` CHAR(10) NOT NULL, --담당자
`project` VARCHAR(30) NOT NULL, --프로젝트이름 
`cost` INT --비용
);
INSERT INTO `tProject` VALUES (1, '김상형', '홍콩 수출건', 800);
INSERT INTO `tProject` VALUES (2, '김상형', 'TV 광고건', 3400);
INSERT INTO `tProject` VALUES (3, '김상형', '매출분석건', 200);
INSERT INTO `tProject` VALUES (4, '문종민', '경영 혁신안 작성', 120);
INSERT INTO `tProject` VALUES (5, '문종민', '대리점 계획', 85);
INSERT INTO `tProject` VALUES (6, '권성직', '노조 협상건', 24);
 

 

 

👾  employee 외래키는 직원 테이블의 name키를 가리키며 이 관계를 통해 프로젝트의 담당자가 누구인지 알 수 있음

    💡 두 테이블은 담당한다는 관계 = (참조)를 맺음

 

 

* 참고 *

  -  테이블 이름은 표현하는 엔티티로 붙이는 것이 좋음

     ➡️ 도시의 정보를 다루니 tCity로 이름을 붙이고 직원 목록은 tStaff으로 붙임 프로젝트명은 tProject가 어울림.

  -  레코드 여러 개를 저장한다고 해서 굳이 복수형으로 작성할 필요는 없음

     ➡️ tCities, tStaffs, tProjects로 해도 문제는 없지만 테이블에는 어차피 여러 개의 레코드가 들어가니 복수형일 필요 없음

 

 

 

2) 외래키 제약

 

👩🏻‍💻 직원과 프로젝트의 샘플 데이터는 규칙에 맞게 입력하여 참조 관계가 정확

 

 

📌 새로 레코드를 삽입할 때는 아직 문제가 있음

 

INSERT INTO `tProject` VALUES (7, '홍길동', '원자재 매입', 900);
 

  👾 새로운 프로젝트의 담당자로 지정한 '홍길동'은 직원 테이블에 존재하지 않음

  👾 외래키가 존재하지 않는 잘못된 키를 가리키고 있지만 이상 없이 잘 삽입됨

 

 

📌 다음 명령도 문제가 있음

 

DELETE FROM `tEmployee` WHERE name = '김상형';
 

  👾 이 명령은 '김상형' 직원을 삭제

        ➡️ 이 직원이 맡고 있는 모든 프로젝트는 담당자를 잃어버림.

  👾 외래키가 가리키는 대상이 사라져 참조 무결성이 깨져 버림

  👾 관계를 기반으로 동작하는 RDB의 참조 무결성이 깨지면 이후의 동작을 보증할 수 없음

 

 

👩🏻‍💻 DB 엔진은 시키는대로 할 뿐 실행을 거부할 이유 x  ➡️  이런 이유로 외래키(FK) 제약이 필요

👩🏻‍💻 외래키 제약은 어떤 필드가 외래키인지, 어떤 테이블의 무슨 키를 참조하는지 지정

👩🏻‍💻 외래키 제약이 있으면 참조 무결성이 깨지는 명령의 실행을 거부

 

 

📌 직원 테이블은 원상 복구해 두고 tProject 테이블은 다시 정의

 

CREATE TABLE `tProject` (
`projectID` INT PRIMARY KEY,
`employee` CHAR(10) NOT NULL,
`project` VARCHAR(30) NOT NULL,
`cost` INT,
 CONSTRAINT FK_emp FOREIGN KEY(`employee`) REFERENCES `tEmployee`(name`)
 // 마지막 필드 끝에 콤마를 찍고 제일 아래에 외래키 제약을 추가
);
 

 

  👾  제약의 이름과 종류를 지정하고 REFERENCES 키워드 다음에 테이블(필드) 형식으로 참조하는 테이블과 키를 지정

  👾  오라클과 SQL Server는 외래키 필드 선언문에 컬럼 제약으로 지정할 수도 있음

 

`employee` ...CHAR(10) NOT NULL REFERENCES
`tEmployee`(name),
 

  👾 외래키 제약은 employee 필드가 tEmployee 테이블의 name 필드와 연결되었음을 선언

        ➡️ 이후 DBMS는 이 제약을 위반하는 모든 명령의 실행을 거부

 

INSERT INTO `tProject` VALUES (7, '홍길동', '원자재 매입', 900);
// ORA-02291: 무결성 제약조건(SYSTEM.FK_EMP)이 위배되었습니다- 부모 키가 없습니다
 
DELETE FROM `tEmployee` WHERE `name` = '김상형';
// ORA-02292: 무결성 제약조건(SYSTEM.FK_EMP)이 위배되었습니다- 자식 레코드가 발견되었습니다
 

  💡 둘 다 에러 메시지를 출력하고 실행을 거부

         ➡️ 원하는대로 하려면 다음 두 명령을 순서대로 실행

 

INSERT INTO `tEmployee` VALUES ('홍길동', 330, '장성');
INSERT INTO `tProject` VALUES (7, '홍길동', '원자재 매입', 900);
 

  💡 직원을 먼저 등록해야 이 직원에게 프로젝트를 맡길 수 있음

        ➡️ 마찬가지로 직원을 삭제하려면 다음 두 명령을 순서대로 실행

DELETE FROM `tProject` WHERE `employee` = '김상형';
DELETE FROM `tEmployee` WHERE `name` = '김상형';
 

  💡 직원이 맡고 있는 프로제트를 모두 제거하거나 아니면 다른 직원에게 넘겨야 이 직원을 컷할 수 있음

 

 

 

  📌  FK 제약이 설정되어 있는 상태에서 다음 쿼리문도 에러 처리

DROP TABLE `tEmployee`;
 

  👾 직원 테이블을 삭제하면 프로젝트 테이블 전체가 무효해짐

         ➡️ 정직원 테이블을 삭제하려면 프로젝트 테이블부터 비워야 함

 

 

👩🏻‍💻 DBMS가 외래키 제약에 대한 규칙을 정확하게 알고 있으므로 무결성을 해치는 모든 명령을 원천적으로 차단

👩🏻‍💻 외래키 제약은 참조 무결성을 지키는 강력한 수단

 

 

 

3) 연계 참조 무결성 제약

 

 

👩🏻‍🚀 외래키 제약관련 작업을 자동화하여 한 번에 처리하는 연계 참조 무결성 제약

👩🏻‍🚀 관련키의 삭제나 수정을 무조건 금지하는 것이 아니라 추가 동작까지 자동으로 처리하여 무결성을 유지하는 기능

👩🏻‍🚀 부모 테이블을 변경하면 자식 테이블까지 알아서 수정

 

  💡 이 기능을 사용하려면 외래키 제약 뒤에 다음 선언을 추가

ON DELETE { NO ACTION | CASCADE | SET NULL | SET DEFAULT }
ON UPDATE { NO ACTION | CASCADE | SET NULL | SET DEFAULT }
 

👾 ON DELETE는 참조되는 키가 삭제될 때의 동작을 정의하며 ON UPDATE는 참조되는 키가 변경될 때의 동작을 정의

 

  📌  각각에 대해 4개의 옵션을 지정할 수 있음

      * NO ACTION은 아무것도 하지 않고 실패하도록 내버려 두는 것이며 이 옵션이 디폴트

      * CASCADE는 참조되는 키와 연결되어 있는 외래키를 자동으로 삭제하거나 변경

      * SET NULL이나 SET DEFAULT외래키를 NULL이나 기본값으로 변경하는데 외래키가 NULL 허용이거나 기본값이 지정되어 있어야 함

 

 

  📌 tProject 테이블을 삭제한 후 다음 명령으로 다시 생성

 

CREATE TABLE `tProject` (
`projectID` INT PRIMARY KEY,
`employee` CHAR(10) NOT NULL,
`project` VARCHAR(30) NOT NULL,
`cost` INT,

CONSTRAINT FK_emp FOREIGN KEY(`employee`) REFERENCES `tEmployee`
ON DELETE CASCADE
);
 

 

👾 ON DELETE에 대해 CASCADE 옵션을 지정하여 필요한 추가 처리를 자동으로 하도록 함

👾 이 상태에서 다음 명령으로 직원을 삭제

 

DELETE FROM `tEmployee` WHERE `name` = '김상형';
 

  💡 외래키 제약만 걸려 있다면 이 명령은 당연히 에러지만 CASCADE 옵션이 지정되어 있어 에러 없이 실행 가능

 

 

  📌 실행하기 전 후 차이

 

 
 

 

 

👾 직원 뿐만 아니라 tProject에 이 직원이 담당한 프로젝트도 같이 삭제

👾 연계 참조란 이런 식으로 하나를 삭제하면 연결된 다른 테이블을 자동으로 수정하는 기능

 

  💡 만약 연계에 의해 삭제되는 레코드에 또 다른 연계 참조 무결성 제약이 걸려 있다면 연쇄적으로 수정

 

 

 

 

👾 ON UPDATE 선언을 하면 참조키 수정시 관련 외래키를 같이 수정하며 동작 방식은 ON DELETE와 유사

👾 SQL Server와 MariaDB는 이 기능을 지원하지만 오라클은 ON DELETE까지만 지원하며 똑같은 기능을 구현하려면 트리거를 사용해야 함

 

 

  📌  tProject 테이블을 삭제한 후 다음 명령으로 다시 생성

  📌  ON DELETE와 ON UPDATE에 대해 모두 CASCADE 동작을 지정

 

CREATE TABLE `tProject`(
`projectID` INT PRIMARY KEY,
`employee` CHAR(10) NOT NULL,
`project` VARCHAR(30) NOT NULL,
`cost` INT,
CONSTRAINT FK_emp FOREIGN KEY(`employee`) REFERENCES `tEmployee`
ON DELETE CASCADE ON UPDATE CASCADE
);
 

👾  직원을 삭제하면 연관 정보도 같이 삭제

 

 

  📌  이번에는 직원이 이름을 변경한 상황을 가정하여 UPDATE 명령으로 참조키를 수정

 `tEmployee` SET `name` = '문사장' WHERE `name` = '문종민';
 

👾 이름이 바뀌는 것은 원래의 이름이 없어지는 것과 같아 프로젝트의 담당자가 무효해짐

👾 외래키 제약만 걸려 있다면 담당자의 이름만 바꾸는 것은 불가능

👾 연계 참조 무결성 제약이 걸려 있으면 참조되는 키를 수정할 수 있음

      ➡️ 직원 이름을 수정하면 tProject의 외래키도 같이 변경

      ➡️ 담당자의 이름과 프로젝트의 담당자가 동시에 바뀌므로 무결성은 여전히 유지

 

 

💡 자동으로 처리한다는 면에서 편리하지만 이 기능은 함부로 사용 x, 실무에서도 잘 사용하지 않음

 

 

 

 

 

[ 내용 참조 : IT 학원 강의 ]

 

 


 

 

1.  정규화

 

🌙  정규화는 테이블을 재구성하여 논리적 구조를 개선하는 절차

🌙  불필요하게 중복되는 데이터를 제거하여 저장 공간을 절약하고 전체적인 성능을 향상시킴

🌙  실무에서는 주로 1~ 3 정규화까지 사용

 


 

1) 제 1 정규화

 

👾 제 1 정규화는 필드의 데이터를 원자화(Atomicity)

👾 필드에는 단일값을 저장하고 필드끼리는 독립적이어야 함

 

  📌  다음 테이블은 직원의 신상과 맡은 프로젝트를 저장

이름
월급
주소
프로젝트
김상형
650
이천시
  홍콩 수출건, TV광고건, 매출 분석건
문종민
480
대구시
  경영 혁신안 작성, 대리점 계획
권성직
625
안동시
  노조 협상건

 

  🔎 월급, 주소 필드에 단일값을 저장, 상호 아무런 연관이 없기 때문에 두필드는 원자성을 확보

  🔎 프로젝트는 한 직원이 여러 건을 맡을 수 있어 콤마로 구분하여 나열

        ➡️ 저장의 목적은 달성했지만 원자성이 없어 문제가 발생

    * 프로젝트 개수에 제한이 없어 필드 길이를 충분히 할당 해야

       신입사원은 당장 맡은 프로젝트가 없지만 앞으로 맡을 프로젝트를 위해 최대 길이만큼 미리 확보해야 하니 저장 공간을 낭비

    * 단일값이 아니어서 = 연산자로 비교할 수 없으며 WHERE project LIKE '%홍콩수출건%' 식으로 부분 문자열을 검색해야 함

    * 수정도 문제가 됨

 

  🔎 김상형 직원이 담당하는 TV 광고건을 문종민 직원에게 이관하려면 중간에 낀 문자열을 빼 뒤쪽에 추가하는 문자열 조작을 해야 함

        ➡️ 구분자인 콤마까지 정확히 추출해야 함, UPDATE, DELETE 쿼리로는 이런 조작이 불가능

        ➡️ 필드값은 더 이상 나눌 수 없는 단일값(Scalar)이어야 관리하기 편함

 

⬇️

 

  💡 프로젝트 필드를 여러 개 선언하고 각 필드에 프로젝트 하나를 저장하면 어떨까?

 
이름
월급
주소
프로젝트1
프로젝트2
프로젝트3
김상형
650
이천시
홍콩 수출건
TV광고건
매출 분석건
문종민
480
대구시
경영 혁신안 작성
대리점 계획
NULL
권성직
625
안동시
노조 협상건
NULL
NULL

 

 

    🔎 이 구조는 원자성은 확보했지만 직원 한명당 프로젝트를 3개까지만 담당해야 한다는 제한이 생김

    🔎 핵심은 1) 낭비없이 얼마든지 2) 많은 프로젝트를 저장할 수 있어야 함

          ➡️ 이런 경우 정석이 바로 제 1 정규화이며 다음과 같이 두 개의 테이블로 분할

 

 

직원 테이블에는 이름, 월급, 주소 등의 직원 정보만 두고 프로젝트 필드는 제거

프로젝트 테이블을 따로 만들어 누가 어떤 프로젝트를 담당하는지 기록 (1:다 관계)

 

 

 

 

    🔎  양쪽 테이블은 각자의 기본키가 있음. 직원 테이블은 이름이 PK이며 프로젝트 테이블은 일련번호를 PK로 정의

    🔎  프로젝트는 PK 외에 담당자를 가리키는 별도의 필드가 더있음

 

 

  💡 이처럼 외부 테이블에 대한 참조를 가지는 키를 외래키(Foreign Key)라고 부르며 양 테이블의 연결고리 역할을함

         ➡️  직원과 프로젝트는 담당자 외래키로 연결되며 두 테이블 간에 "담당한다"는 관계가 성립

         ➡️  담당자 외래키가 없다면 직원과 프로젝트는 완전히 따로 놀며 누가 어떤 프로젝트를 맡고 있는지 알 방법이 없음

 

 

 👾  레코드는 얼마든지 자유롭게 늘릴 수 있어 담당 프로젝트 개수에 제한 x

 👾  신규 프로젝트는 새 레코드를 삽입(INSERT)하고, 완료되었으면 삭제(DELETE)하고, 수정(UPDATE)하면 됨

 

 

  📌 김상형 직원이 담당하는 TV 광고건을 문종민 직원에게 이관건은 다음 쿼리 하나로 해결

UPDATE tProject SET 담당자='문종민' WHERE 프로젝트='TV 광고건';
 
 
 

  📌 특정 직원이 맡은 프로젝트를 조사하려면 외래키를 직원명으로 검색

SELECT 프로젝트명 FROM tProject WHERE 담당자= '김상형';
 

 


 

 

🌙  관계를 맺는 테이블은 누가 업무의 중심인가에 따라 부모, 자식 관계를 형성

      📌  업무의 주체가 되는 쪽을 부모 또는 마스터(Master)라고 하며 반대쪽을 자식 또는 디테일(Detail)이라고 함.

      📌  위의 예시 경우 직원이 프로젝트를 수행하므로 직원 테이블이 마스터이고 프로젝트 테이블이 디테일

 

🌙  정규화에 의해 테이블을 분할하면 양쪽의 정보를 조합하여 얻기는 조금 번거로워짐

       ➡️ 하지만 분할된 테이블에서 관련 정보를 검색하는 서브쿼리(SubQuery)나 조인(Join) 등의 방법으로 해결 가능

 

 


 

 

2) 제 2 정규화

 

🌙  제 2 정규화는 일반 필드를 모두 기본키에 종속시키는 것

       ➡️ 종속된다는 것은 기본키를 알면 나머지 정보도 알 수 있다는 뜻

 

 

📌 tCity 테이블에서 기본키인 도시명과 나머지 일반 필드는 종속 관계

📌 일반 필드끼리는 종속 관계가 없으며 상호 독립적. 독립적이라는 것은 각자 따로 논다는 뜻.

     ➡️ 경기도에 있다고 해서 광역시는 아니며 면적을 안다고 해서 인구를 알 수는 없음

📌 tCity는 모든 필드가 기본키에 종속적이므로 제 2 정규화를 만족

 

 

 

 

 

💡 만약 프로젝트 테이블을 다음과 같이 만들었다면?

 
프로젝트 번호
프로젝트
담당자
주소
월급
1
홍콩 수출건
김상형
이천시
650
2
TV 광고건
김상형
이천시
650
3
매출 분석건
김상형
이천시
650
4
경영혁신안
문종민
대구시
480
5
대리점 계획
문종민
대구시
480
6
노조 협상건
권성직
안동시
625

 

 

    🔎  프로젝트 번호에 따라 프로젝트 이름과 담당자가 결정되므로 이들은 종속 관계

    🔎  담당자의 주소나 월급은 프로젝트와는 직접적인 상관이 없어 이 테이블에 있을 필요가 없음

           ➡️ 프로젝트 테이블에 담당자만 기록해 두면 나머지 정보는 직원 테이블에서 언제든지 조사할 수 있음

  📌 단일 기본키에서는 이런 실수가 금방 눈에 띄지만 복합키에서는 헷갈리는 상황 발생

  📌 모든 필드는 복합키 전체에 종속되어야 하며 일부에만 종속되서는 안됨

 

 

 

 

💡  다음 테이블은 주문 처리 내역을 기록하는 주문 테이블

 
고객명
주문번호
주문 일자
품목
개수
주소
회원등급
보라돌이
1
5월 5일
하드디스크
1
춘천
우수
보라돌이
2
5월 6일
노트북
2
춘천
우수
뚜비
1
5월 7일
팥빙수
1
이천
신입
뚜비
2
5월 8일
카네이션
2
이천
신입
뚜비
3
5월 15일
수박
5
이천
신입
보라돌이
3
5월 28일
분유
1
춘천
우수

 

 

   📌  고객 한명이 여러 건의 주문을 낼 수 있어 고객명과 주문번호를 복합키로 설정하여 어떤 고객이 몇 번째 낸 주문이라는 식으로 구분

   📌  주문일자나 품목, 개수는 주문 내역인 고객명+주문번호에 종속적

          ➡️ 누가 몇 번째 낸 주문인가에 따라 날짜, 품목, 개수가 결정됨

   📌  주소와 회원등급은 복합키가 아닌 고객명에 종속되어 있으며 누가 주문을 냈는가에 따라 결정

          ➡️ 주문번호가 다르다고 해서 주소나 등급이 바뀌는 것은 아님

 

👾  이 구조에서 주소, 회원등급 필드는 테이블의 기억 장소를 불필요하게 낭비

👾  기본키 전체에 종속되지 않는 필드는 별도의 테이블로 분리하여 제 2 정규화를 함

 

 

[ 주문 테이블 ]

 

고객명
주문번호
주문 일자
품목
개수
보라돌이
1
5월 5일
하드디스크
1
보라돌이
2
5월 6일
노트북
2
뚜비
1
5월 7일
팥빙수
1
뚜비
2
5월 8일
카네이션
2
뚜비
3
5월 15일
수박
5
보라돌이
3
5월 28일
분유
1

 

[ 고객 테이블 ]

 
고객명
주소
회원등급
보라돌이
춘천
우수
보라돌이
춘천
우수
뚜비
이천
신입
뚜비
이천
신입
뚜비
이천
신입
보라돌이
춘천
우수

 

 

👾  주문 내역 테이블에는 복합키에 종속적인 필드만 포함했으며 고객 정보는 별도의 테이블을 구성

👾  한 고객이 수천 번의 주문을 낸다 하더라도 주소, 회원등급 정보는 딱 하나씩만 있으면 됨

 

 

 

3) 제 3 정규화

 

🌙  제 3 정규화는 기본키가 아닌 일반 필드끼리 서로 종속되지 않도록 하는 것

      📌 일반 필드끼리는 독립적이어야 한다는 규칙

 

 

💡  다음은 직원 테이블에 직원이 가입한 사내 동호회 관련 필드를 추가한 것

 

직원명
월급
동호회
방번호
회원수
이인재
700
독서
203
32
김영석
680
독서
203
32
김연경
430
게임
306
18
조기봉
690
독서
203
32
차일환
320
게임
306
18

 

 

  📌  월급과 가입한 동호회 이름은 기본키인 직원명에 종속적

  📌  동호회의 방번호와 회원수는 기본키가 아닌 동호회 필드에 종속적

  📌  같은 동호회에 가입한 직원끼리는 방번호, 회원수 등의 중복 정보를 가짐

 

 

💡 이 구조의 문제점

 

  ① 기억 장소가 낭비

      · 만약 동호회 회장 이름, 설립년도, 예산 등의 상세 정보가 추가되고 동호회별로 회원수가 수십명이라면 낭비가 더 심해짐

  ② 이 테이블의 엔티티는 직원이지 동호회가 아니다.

      · 미가입 직원의 동호회 관련 정보는 모두 NULL일 수 밖에 없음

      · 회원이 없는 신규 동호회는 표현할 방법이 없음

  ③ 중복 정보를 변경하기 번거로움

      · 예를 들어 독서 동호회가 203호에서 505호로 이사갔다면 이 동호회에 속한 모든 회원의 정보를 다 수정해야 함

  ④ 정보의 불일치가 발생할 수 있음

      · 같은 독서 동호회인데도 이인재 직원의 총 회원수는 32로 기록하고 김영석 직원의 총 회원수는 31로 기록할 수도있음

 

 

💡 이런 문제가 발생하는 이유는 분석 단계부터 엔티티 추출에 문제가 있었기 때문

       ➡️ 문제를 해결하려면 정규화를 통해 테이블을 분할.

 

 

  📌  월급, 동호회는 기본키인 사원명에 종속적

  📌  반면 일반 필드인 월급과 동호회는 아무 상관이 없으며 독립적 따라서 이 둘의 관계는 정규화를 위반 x

  📌  동호회와 방번호, 회원수의 관계는 종속적. 동호회를 알면 방번호나 회원수도 알 수 있음

         ➡️ 일반 필드끼리는 독립적이어야 하는데 그렇지 않아 중복이 발생

         ➡️ 이럴 경우 직원 테이블과 동호회 테이블을 따로 분리하여 중복을 제거

 

 

[직원 테이블]

 
직원명
월급
동호회
이인재
700
독서
김영석
680
독서
김연경
430
게임
조기봉
690
독서
차일환
320
게임

 

[동호회 테이블]

 
동호회
방번호
회원수
독서
203
32
독서
203
32
게임
306
18
독서
203
32
게임
306
18

 

 

   👾 직원 테이블에는 소속 동호회의 이름만 기록하고 각 동호회에 대한 정보는 별도의 테이블로 분리

   👾 직원 테이블의 동호회 필드는 동호회 테이블을 참조하는 외래키

   👾 직원 테이블의 월급과 동호회는 기본키인 직원명에 종속적

   👾 새로 분할한 동호회 테이블에서 방번호와 회원수는 기본키인 동호회에 종속적

 

 

 

💡 둘 다 3 정규화가 완료 ➡️ 중복으로 인한 모든 문제가 해결

💡 동호회 정보가 딱 하나여서 기억 장소의 낭비가 없고 데이터의 불일치가 발생할 여지 x

💡 가입 회원이 없는 신규 동호회도 표현가능

 


 

 

2.  역정규화

 

👩🏻‍💻 정규화 과정을 거치면 중복이 제거되고 구조가 치밀해짐

👩🏻‍💻 양쪽 정보를 조합하여 읽을 때는 여러 테이블을 읽어야 하므로 속도는 느려짐

👩🏻‍💻 프로그래밍의 대원칙 중 하나는 속도와 크기는 항상 반비례

👩🏻‍💻 SQL은 조인(Join)이라는 효율적인 기능을 제공하여 속도 감소가 심하지 않음

 

💡  정규화를 너무 과하게 하여 이중 삼중의 조인이 필요하면 성능 저하가 발생하는데, 이럴 때는 의도적으로 데이터를 중복시켜 속도 향상을 꾀하기도 한다. 원칙을 반대로 적용하여 속도 향상을 위해 용량상의 이득을 포기하는 것

      ➡️ 이를 정규화의 반대 개념인 역정규화라고 하는데 구현 단계의 튜닝 과정에서 주로 수행

 

  ex.  제 2 정규화에서 예를 든 주문 정보 테이블에서 배송 주소를 구하려면 주문자를 먼저 찾고 고객 테이블에서 주소를 조사하는 번거로운 과정을 거쳐야 함. 이럴 때 주소 필드를 주문 내역 테이블에 다시 포함시키면 곧바로 배송지 주소를 구할 수 있음

 

 

 

 

 

 

[ 내용 참고 : IT 학원 강의 ]

 


1.  프로젝트의 진행단계

👩🏻‍🚀 프로젝트 project란 대규모의 프로그램을 작성하기 위한 전체과정

  ⚡️ 분석과 설계 작업을 등한시하는 소프트웨어 분야의 고질적인 문제점 때문에 소프트웨어 개발 방법론이 제시됨

👩🏻‍🚀 소프트웨어 공학에서 제시하는 소프트웨어 개발 모델로 '폭포수 모델 Waterfall Model' 이 있다.

  ⚡️ 장점 : 단계가 명확히 구분되어서 프로젝트의 진행단계가 명확해짐

  ⚡️ 단점 : 문제점이 발생될 경우 다시 앞 단계로 거슬러 올라가기 어렵다.

 

< 폭포수 모델 >

 

 

👩🏻‍🚀 이 모델에서 가장 핵심적인 단계는 '업무 분석과 시스템 설계'


 

1) 프로젝트 계획

- 어떤 프로그램을 누가 어떻게 작성할지 계획을 세우며 요구 기능, 예산, 개발 기간을 결정.

 

2) 업무 분석

- DB 응용 프로그램은 원래 사람이 하던 일을 전산화한 것. 따라서 현실 세계의 수작업을 면밀히 분석하는 업무 파악 부터 시작.

- 업무의 개요와 목적, 수행 방법, 규칙과 절차 등의 비즈니스 로직(Business Logic)을 문서로 정리

    ex. 쇼핑몰 시스템을 구축하려면 회원 관리, 가격 책정, 배송 및 반품, 마일리지 체계 등의 업무부터 알아야 함

- 은행이나 증권사, 병원의 업무는 고도의 전문 지식이 필요해 실무자와 같이 분석.

 

3) 시스템 설계

- 설계 단계에서는 분석 문서를 참조하여 구현 방법을 결정

- 규모에 맞는 하드웨어와 소프트웨어를 선정하고 업무에 필요한 사물을 추출

- 비즈니스 로직에 따라 실세계의 사물을 테이블로 정의하고 관계를 설정하는 작업을 모델링이라고 함

 

  📌 모델링은 건축의 설계도에 해당. 구현 과정이 신속 정확하려면 모델링이 완벽해야 함

  📌 모델링은 일반화된 모범 답안이 없어 숙련된 노하우를 요하는 고급 기술

 

4) 프로그램 구현

- 모델링한 데이터를 테이블로 구체화하고 효율적인 운영을위한 인덱스, 프로시저 등을 정의하는 단계

- 데이터를 실제 사용하고 입출력하는 클라이언트 응용 프로그램을 개발하고 사용자를 위한 설명서와 유지/보수를위한 문서까지 작성

 

5) 유지보수

- 구현 완료한 프로그램을 실제 업무에 사용하는 단계

- 미처 예상하지 못한 문제나 버그가 발견되고 기능 개선요구도 발생할 수 있다.

- 현실 세계가 계속 변하기 때문에 개발 완료한 프로젝트도 지속적인 관리가 필요함

 

 

2.  데이터베이스 모델링

1) 개념

 

  - 현 세계에서 사용되는 작업이나 사물들을 DBMS의 데이터베이스 개체로 옮기기 위한 과정

  - 데이터베이스 모델링은 상당히 어려운 작업, 구현하고자 하는 업무에 대한 폭넓고 정확한 지식이 필요하기 때문

  - 일반적으로 모델링 담당하는 사람은 프로젝트 경험이 많거나 데이터베이스 관련 지식이 있는 사람

 

출처 :&nbsp; https://segment.com/blog/data-modeling/
 

 

 

2) 데이터 베이스 모델링 분류

- 데이터베이스 모델링은 개념적 모델링, 논리적 모델링, 물리적 모델링으로 나눌 수 있다.

⚡️ 개념적 모델링은 주로 업무 분석단계에 포함

      ➡️ 비즈니스 개념과 규칙을 구성하고 범위를 지정하고 정의

⚡️ 논리적 모델링은 업무 분석의 후반부와 시스템 설계의 전반부에 걸쳐 진행

      ➡️ 규칙 및 데이터 구조의 기술 맵을 개발하는 단계

⚡️ 물리적 모델링은 시스템 설계의 후반부에 주로 진행

      ➡️ 데이터베이스의 실제 구현 단계

 

출처 :&nbsp; https://www.gooddata.com/
 

 

 
 
출처 :&nbsp; https://segment.com/
 


 

 

3.  엔티티

 

출처 :&nbsp;https://www.gooddata.com/

 

👩🏻‍🚀  모델링은 제작과정에서 분석 다음 단계인 설계에서 해야하는 작업

👩🏻‍🚀  모델링의 첫 단계는 데이터베이스에 저장할 대상인 엔티티를 정의하고 추출(속성 추출)하는 것

  ⚡️  엔티티(Entity)는 정보에 해당하는 모든 실체이며 전산화의 대상

        ex. 고객, 상품, 날짜, 가격 같은 명사 뿐만 아니라 구매, 가입, 대여 같은 추상적인 동사도 엔티티

👩🏻‍🚀  분석 단계에 오류가 있으면 엉뚱한 엔티티를 정의하는 실수를 하기도 함.

 

  📍 다음은 쇼핑몰의 업무 분석 결과

       [ 고객은 상품을 주문하고 쇼핑몰은 주문받은 상품을 배송한다. ]

         ➡️ 이 분석의 결과로부터 '고객/상품' 같은 사물과 '주문/배송' 동작을 엔티티로 추출

         ➡️ 엔티티는 보통 하나의 테이블로 구체화

         ➡️ 회원, 상품, 주문 등의 정보를 각각의 테이블에 저장

  📍 다음은 엔티티를 구성하는 속성을 정의

       [ 상품 : 이름, 가격, 재고, 할인율 ]

       [ 고객 : 아이디, 주소, 전화번호, 예치금, 마일리지 ]

          ➡️ 속성은 테이블의 필드로 구체화

          ➡️ 추출한 속성 중에 엔티티를 대표하는 기본키를 선정

              ( 레코드끼리 구분 가능한 고유성이 있고 자주 검색하는 속성을 선정해야 함 )

 

4.  관계

👩🏻‍🚀  각각의 엔티티는 독립적으로 존재하지 않고 업무적으로 서로 연관

   ⚡️ 고객은 상품을 주문하며 쇼핑몰은 주문받은 상품을 배송하는 것

👩🏻‍🚀  엔티티를 정의한 후에는 비즈니스 로직에 따라 엔티티간의 관계를 설정

👩🏻‍🚀  관계(Relation)는 엔티티간의 연결 형태이며 관계형 DB의특징을 결정하는 핵심이다.

   ⚡️ 관계는 양쪽 엔티티의 인스턴스 개수에 따라 1) 1:1관계 2) 1:다 관계, 3) 다:다 관계로 분류

   ⚡️ 반드시 연결해야 하는 필수 관계와 필요할 때만 연결하는 선택 관계가 있음


 

1) 1:1 관계

👾 1:1 관계는 양쪽 테이블의 인스턴스를 하나씩 연결

👾 1:1 관계의 현실적인 예는 엔티티의 정보가 너무 많아 테이블을 분할할 때

      ➡️ 필드가 많으면 보기에도 정신없고 레코드가 거대해져 성능도 떨어짐

      ➡️ 자주 참조하는 정보와 부가적인 정보로 분할하면 참조하지 않는 속성은 필요할 때만 읽으면 되니 성능이 향상됨

           ex. 회원 데이터의 경우 자주 사용하는 로그인 관련 정보와 상대적으로 사용이 적은 상세 정보를 분리

 

 

    ⚡️ 이때 두 테이블의 레코드끼리 짝을 찾을 수 있는 연결 고리가 필요

    ⚡️ 관계형 DB에서 레코드의 순서는 의미가 없어 순서로 연결 x

    ⚡️ 양쪽 테이블에 똑같은 키를 만들어 두면 연결 관계를 알 수 있는데 이런 용도로는 학번이 적합

           ➡️ 상세 정보를 알고 싶을 때 학생 테이블의 학번을 조사한 후 상세 테이블에서 학번이 같은 레코드를 참조

                ex. 김학생의 체중을 알고 싶다면 학번인 1234로 상세 테이블을 검색

                        두 테이블은 학번이라는 외래키로 연결되어 있음.

 


 

 

2) 1:다 관계

👩🏻‍🚀  가장 흔하게 존재하는 관계는 1:다 관계이며 부서와 직원의 관계가 대표적이다.

       ➡️ 직원은 한 부서에 소속되며 한 부서에는 여러 명의 직원이 있음

 

 

    📌 김유신과 정몽주는 총무부 소속이고 안중근과 허난설헌은 인사과 소속

    📌 직원 테이블의 소속 부서 필드가 부서 테이블의 기본키를 가리킴

 

 

3) 다:다 관계

👩🏻‍🚀  다:다 관계도 현실에서는 흔하며 양쪽 엔티티가 서로 복수개의 엔티티로 연결

👩🏻‍🚀  다음 예는 학생과 과목간의 수강 관계를 표현한 것

 

      📌  학생 한 명이 여러 과목을 수강할 수 있으며 또한 한 과목을 여러 학생이 수강할 수 있어 학생과 과목의 관계는 다:다 관계

 

 

    📌  관계형 DB로는 다:다 관계를 표현할 수 없어 두 개의 1:다 관계로 변환하여 표현

    📌  중간에 수강 엔티티를 삽입하여 두 개의 다:1 , 1:다 관계로 변환

 


💡 모델링의 목적은 데이터 구조를 중복없이 효율적으로 디자인하는 것이며 이를 위해 하나의 테이블을 여러 개로 분할

💡 분할된 테이블끼리의 연관성은 관계를 통해 설정, 관계는 외래키 제약에 의해 구체화

 



[ 내용 참고 : 책 '이것이 MySQL 이다' 및 IT 학원 강의 ]

 


 

 

1.  일련번호 필드

 

🍊 기본키는 레코드의 유일성을 보장할 뿐만 아니라 관계를 형성하는 중요한 역할

🍊 그러나 기본키가 강제 규정은 아니어서 의무적으로 만들어야 하는 것은 아니며 기본키로 쓸만한 필드가 없는 경우도 발생

 

🔖 다음은 슈퍼마켓의 매출 데이터이며 누가 어떤 상품을 구매했는지 기록

손님
상품
단군
지팡이
고주몽
고등어
박혁거세
계란
대조영
라면
박혁거세
화장지
고주몽
라면

 

  📍 이 경우 중복되지 않는 필드가 없어 기본키로 쓸만한 적당한 필드가 없음

  📍 정보 저장은 가능하지만 레코드를 식별하는 수단이 없어 검색이나 참조가 번거롭다.

     🔎  고주몽이 라면을 반품했다면 구입 기록을 삭제해야 하나 고주몽을 찾아 삭제하면 고등어도 같이 반품되고,

            라면을 찾아 삭제하면 대조영이 산 라면도 삭제 된다.

     🔎  손님과 상품을 모두 점검해서 레코드를 찾아야 하는데, 이 방법도 고주몽이 라면을 어제도 사고 오늘도 샀다면 완벽하지 않음.

 

⚡️ 억지로라도 기본키를 만드는 것이 좋은데 이때 유용한 값이 일련번호

⚡️ 계속 증가하는 일련번호를 붙이면 NULL도 아니고 고유성도 부여할 수 있음

 

 

🔖 위 테이블에 판매번호 필드를 추가

 

판매번호
손님
상품
1
단군
지팡이
2
고주몽
고등어
3
박혁거세
계란
4
대조영
라면
5
박혁거세
화장지
6
고주몽
라면

 

  📍 이제 일련번호만 알면 레코드를 특정할 수 있음

 

 

⚡️ 정수값 하나로 레코드를 식별할 수 있어 다른 조건은 더 필요 없다

⚡️ 판매번호를 PK로 지정하면 인덱스까지 생성하여 검색 속도가 빨라지고 다른 테이블과 관계를 맺을 수도 있음

⚡️ 일련번호를 붙이는 방법이 DBMS별로 조금씩 달라 각각 따로 익혀야 하는 불편함이 있음

 


 

 

2.  시퀀스

 

🍊 오라클과 SQL Server는 일련번호를 관리하는 시퀀스(SEQUENCE) 객체를 지원

      ➡️ 시퀀스는 객체를 초기화해 놓기만 하면 요청할 때마다 증가한 값을 자동으로 돌려줌

 

 

 

3.  자동 생성 시퀀스

 

🍊 시퀀스는 보통 특정 테이블의 필드 하나를 위해 생성하며 공유하는 경우가 적다.

🍊 따로 객체를 만드는 것보다 테이블을 만들 때 아예 시퀀스 필드로 지정하는 것이 효율적

    💡 단, 이 기능은 오라클만 지원

 


 

 

4.  ID 필드

 

🍊 SQL Server는 일련번호를 붙이는 더 간편한 방법을 제공한다.

🍊 자동 생성 시퀀스는 지원하지 않지만 비슷한 기능의 IDENTITY 속성이 있다.

 

 

 

5.  AUTO_INCREMENT

 

💡 MariaDB는 시퀀스를 지원하지 않는다.

💡 대신 IDENTITY와 거의 유사한 AUTO_INCREMENT 구문을 지원한다.

💡 필드 선언문에 AUTO_INCREMENT라고 선언하면 자동 증가하는 일련번호가 매겨진다.

CREATE TABLE tSale
(
saleno INT AUTO_INCREMENT PRIMARY KEY,
customer NCHAR(10),
product NCHAR(30)
);
 

 

INSERT INTO tSale (customer, product) VALUES ('단군', '지팡이');
INSERT INTO tSale (customer, product) VALUES ('고주몽', '고등어');
 

 

📌 saleno는 1부터 1씩 증가하는 일련번호이며 초기값, 증가값은 지정할 수 없다.

📌 삽입할 때 saleno는 빼고 나머지 필드의 값만 지정

 

 

 

DELETE FROM tSale WHERE saleno = 2;
INSERT INTO tSale (customer, product) VALUES ('박혁거세', '계란');
 

 

📌퀀스나 IDENTITY와 마찬가지로 삭제한 일련번호는 재사용하지 않음

📌 2번을 지운 후 삽입하면 3번으로 들어감

 

 

 

 

⚡️ 일련번호를 특정값으로 지정하는 것은 항상 가능하다.

⚡️ 다만 PK 제약상 기존 일련번호와 중복되지는 말아야

 

INSERT INTO tSale (saleno, customer, product) VALUES (2,'고주몽', '고등어');
 

 

 

📌 2번 레코드를 제자리에 다시 삽입.

 

 

 

 


 

 

💡 일련번호를 특정값으로 리셋할 때는 ALTER 명령을 사용

💡 일련번호를 리셋하면 이후 삽입하는 레코드는 리셋한 번호부터 순서대로 증가하는 값을 받는다.

💡 최후값을 알아낼 때는 LAST_INSERT_ID() 함수를 사용

 

ALTER TABLE tSale AUTO_INCREMENT = 100;
INSERT INTO tSale (customer, product) VALUES ('왕건', '너구리');
 

 

 

// 다음은 왕건이 구입한 너구리의 구매 기록을 찾아 짜파게티로 변경.
UPDATE tSale SET product = '짜파게티' WHERE saleno = LAST_INSERT_ID();
 

 


 

💡 자동 증가하는 일련번호 필드를 만드는 방법이 DBMS마다, 버전에 따라 다름

💡 단순히 자동 증가 필드를 만드는 것만이 목적이라면 다음 방법을 쓰는 것이 무난하다.

 


 *
오라클 : saleno INT GENERATED AS IDENTITY PRIMARY KEY

 * SQL Server : saleno INT IDENTITY PRIMARY KEY

 * MariaDB : saleno INT AUTO_INCREMENT PRIMARY KEY

 

⚡️ 이렇게만 해도 자동 증가열의 이점을 충분히 누릴 수 있고, 용어와 구문이 다를 뿐 개념은 같음

 

 

 

 

 

[ 내용 참고 : IT 학원 강의 ]

 

 


 

1.  식별자

🍋 테이블의 특정 레코드를 읽거나 변경하려면 레코드끼리 구분할 수 있는 고유의 키 (=식별자)가 필요

🍋 키는 1) 값이 꼭 있어야 하며, 2) 구분을 위해 고유값을 가져야 함

 

적합한 키
 주민등록번호, 학번, 사번, IP, ISBN
부적합한 키
 키, 몸무게, 제품 색상, 직급
애매한 키
 이름, 핸드폰, 이메일

 

🍋 조건을 만족하는 필드를 후보키 Candidate Key라고 하며 한 테이블에 여러 개의 후보키가 있음

🍋 이 중 레코드를 가장 잘 대표하는 키 하나를 골라 기본키 Primary Key (PK)로 선정

  💡 기본키가 충족해야 할 조건

       1)  대표성 : 레코드를 상징하는 값이어야 한다.

       2)  자주 참조하는 속성 : 기본키에는 기본적으로 인덱스가 생성되어 검색 효율이 좋다.

       3)  가급적 짧은 속성 : 테이블 간의 연결고리가 되므로 비교 속도가 빨라야 한다.

INSERT INTO tCity VALUES ('춘천',1116,27,'n','강원');
 

  📌  iCity는 도시명인 name이 기본 키이며, 위의 쿼리문은 에러가 생김.

  📌  춘천이 테이블에 이미 있는데 또 삽입하면 어떤 레코드가 진짜 춘천에 대한 정보인지 구분 x

         ➡️ 모호함이 생기면 무결성이 깨지므로 기본키에 대해서는 중복을 허락하지 않음

🍋 DBMS는 기본키를 특별하게 관리한다. NULL 금지와 중복 방지는 물론이고 인덱스를 생성하여 검색 속도를 높임.

🍋 기본키는 검색시 조건문에 활용하며 테이블간의 관계를 구성하는 연결고리로 사용

 

💡 테이블에 지정된 키를 보려면 다음 쿼리문을 입력,실행

 

SHOW KEYS FROM 테이블이름;

 


 

2.  기본키 설정

👩🏻‍💻 제약을 선언하는 위치에 따라 컬럼 제약과 테이블 제약이 있다.

👩🏻‍💻 컬럼 제약은 컬럼 선언 뒤에 위치하며 테이블 제약은 모든 컬럼 선언이 끝난 후 마지막 위치에 옴.

 

CREATE TABLE 테이블 ( 
   필드 선언, <- 이 위치에 오면 컬럼 제약 
   필드 선언, 
   필드 선언, 
            <- 이 위치에 오면 테이블 제약 
)

 

 

⚡️ NULL 허용 여부나, 기본값 등 컬럼에 대한 속성은 컬럼 제약으로 지정.

⚡️ 기본키는 컬럼 제약으로 선언할 수 도 있고 테이블 제약으로 선언할 수도 있음.

 

💡 각 제약의 형식

    * 컬럼 기본키 제약 : [CONSTRAINT 이름] PRIMARY KEY

    * 테이블 기본키 제약 : [CONSTRAINT 이름] PRIMARY KEY(대상 필드)

⚡️ PRIMARY KEY 제약은 NOT NULL 속성을 겸함.

⚡️ 제약의 이름을 생략하면 서버가 자동으로 이름을 붙임.

 

CREATE TABLE tCity ( 
  name CHAR(10), 
  area INT NULL , 
  popu INT NULL , 
  metro CHAR(1) NOT NULL, 
  region CHAR(6) NOT NULL, 
  CONSTRAINT PK_tCity_name PRIMARY KEY(name) 
 );

 


 

3.  복합키

👩🏻‍💻 기본키 필드가 꼭 하나여야 한다는 법은 없으며 하나의 필드만으로 레코드를 특정하기 어려운 경우가 있다.

👩🏻‍💻 이런 경우 두 개 이상의 필드를 묶어 기본키로 지정. 이것을 복합키 Composite Key라고 한다.

  ⚡️ tCity 테이블의 경우, 도시명이 중복되지 않는다는 가정을 하고 있지만 현실은 다름

  ⚡️ 경기도 광주, 전라도 광주 처럼 도시 이름이 중복되는 경우가 있는데 tCity 테이블의 구조로는 두 도시 모두 입력할 수 없음

         ➡️ 이름만으로 도시를 특정할 수 없으니 지역과 함께 묶어서 기본키로 정의해야 함.

 

CREATE TABLE tCity ( 
   name CHAR(10) PRIMARY KEY, 
   region CHAR(6) PRIMARY KEY, 
   area INT NULL , 
   popu INT NULL , 
   metro CHAR(1) NOT NULL 
);

 

    📌  name, region 두 개의 필드에 PRIMARY KEY를 지정하면 에러가 남 (테이블당 기본키는 하나만 지정 가능)

    📌  복합키로 지정할 때는 테이블 제약으로 기본키를 지정하며 괄호 안에 필드 목록을 콤마로 구분하여 나열한다.

 

CREATE TABLE tCityCompoKey ( 
  name CHAR(10) NOT NULL, 
  region CHAR(6) NOT NULL, 
  area INT NULL , 
  popu INT NULL , 
  metro CHAR(1) NOT NULL, 
  CONSTRAINT PK_tCity_name_region PRIMARY KEY (name, region) 
);

INSERT INTO tCityCompoKey VALUES ('광주', '전라', 123, 456,'y'); 
INSERT INTO tCityCompoKey VALUES ('광주', '경기', 123, 456,'n');

 

📌 겹치는 name 이 있어도 에러없이 정상적으로 실행

📌 복합키도 중복값을 허용하지 않지만 복합키를 구성하는 개별 키는 중복해도 무방

       ➡️  두 필드가 동시에 같지만 않으면 됨.

 


 

4.  유니크

👩🏻‍💻 유니크 UNIQUE 제약은 필드의 중복값을 방지하여 모든 필드가 고유한 값을 가지도록 강제

👩🏻‍💻 유니크는 기본키를 보조하는 중복 방지 제약

 💡 기본키 제약과의 차이점

      1)  기본키는 NULL을 허용하지 않지만 유니크는 NULL을 허용

            ➡️  단 NULL끼리도 중복 할 수 없어 딱 하나의 NULL만 존재할 수 있음

      2)  UNIQUE와 NOT NULL을 동시에 지정하면 기본키와 유사해짐.

            ➡️  기본키는 테이블당 하나만 지정할 수 있지만 유니크는 개수에 상관없이 얼마든지 지정 가능

      3)  기본키는 자동으로 인덱스를 생성하여 레코드의 정렬 순서를 결정하지만 유니크는 그렇지 않다

             ➡️  인덱스를 생성하더라도 기본키의 인덱스와는 종류와 효율이 다름

  📌  만일 tCity 테이블이 도시끼리 인구수가 같아서는 안된다는 규칙이 있다면 popu필드에 대해 UNIQUE 제약을 설정

 

CREATE TABLE tCityUnique ( 
   name CHAR(10) PRIMARY KEY, 
   area INT NULL , 
   popu INT UNIQUE NULL, 
   metro CHAR(1) NOT NULL, 
   region CHAR(6) NOT NULL 
);

 

  📌  이 테이블의 도시끼리는 인구가 같아서는 안됨. NULL은 가능하지만 하나만 가능.

  📌  NULL을 허가하지 않으려면 UNIQUE NOT NULL로 지정. 두 개 이상의 키를 묶어 복합 UNIQUE 제약을 걸 수도 있음

 

CREATE TABLE tCityUnique ( 
   name CHAR(10) PRIMARY KEY, 
   area INT NULL , 
   popu INT NULL, 
   metro CHAR(1) NOT NULL, 
   region CHAR(6) NOT NULL, 
   CONSTRAINT Unique_tCity_area_popu UNIQUE(area, popu) 
 );

  

  📌  이 테이블의 도시는 이름이 고유해야 하며 area와 popu가 모두 같아서는 안됨. 둘 중 하나라도 달라야 함.

 


 

5.  체크

👩🏻‍💻  체크 제약은 필드의 값 종류를 제한

👩🏻‍💻  모든 속성은 유의미한 범위가 있고 상식적으로 가능한 값과 그렇지 않은 값이 있음.

       ➡️ 예를 들어 도시가 아무리 거대해도 인구 100억을 넘길 수는 없고 면적이 음수가 될 수 없음.

       ➡️ 체크 제약은 이런 무의미한 값을 걸러냄

👩🏻‍💻 타입은 물리적인 형식을 점검하는데 비해 체크는 논리적인 값의 형식을 점검

  ⚡️ 필드 선언문에 CHECK 키워드와 함께 필드값으로 가능한 값을 조건문으로 지정

  ⚡️ WHERE 절의 조건을 지정하는 모든 문법을 쓸 수 있다.

 

CREATE TABLE tCheckTest ( 
   gender CHAR(3) NULL CHECK(gender = '남' OR gender = '여'), 
   grade INT NULL CHECK(grade >= 1 AND grade <= 3), 
   origin CHAR(3) NULL CHECK(origin IN ('동','서','남','북')), 
   name CHAR(10) NULL CHECK(name LIKE '김%') 
);

 

 

  📌  각 필드에 조건문으로 체크 제약을 지정

  📌  성별을 저장하는 gender 필드는 '남', 아니면 '여'만 가능

         ➡️ gender가 둘 중 하나임을 명시하여 이 두 값 이외에는 입력을 금지

  📌  grade는 중고등학교의 학년을 표현하는데 1 ~ 3까지만 유효

         ➡️ 초등학교라면 6까지, 대학교라면 4까지 범위를 늘이면 됨.

         ➡️ 일정 범위내일 때는 최소값, 최대값을 지정하는 대신 BETWEEN AND 조건문이 편리

              ex. grade INT NULL CHECK (grade BETWEEN 1 AND 3)

  📌  origin 필드는 방위를 나타내는데 동서남북 넷 중 하나만 가능

         ➡️ 여러 개의 임의값 중 하나를 지정할 때는 IN 연산자가 편리

  📌  name 필드는 성씨가 김씨인 경우만 받아들임

         ➡️ 부분 문자열을 점검할 때는 LIKE 연산자를 사용

 

 
// 에러 없이 실행 가능한 쿼리문들 
INSERT INTO tCheckTest (gender) VALUES ('여'); 
INSERT INTO tCheckTest (grade) VALUES (1); 
INSERT INTO tCheckTest (origin) VALUES ('동'); 
INSERT INTO tCheckTest (name) VALUES ('김좌진');

// 제약조건을 위반하여 에러 처리가 되는 쿼리문들 
INSERT INTO tCheckTest (gender) VALUES ('노'); 
INSERT INTO tCheckTest (grade) VALUES (0); 
INSERT INTO tCheckTest (origin) VALUES ('중'); 
INSERT INTO tCheckTest (name) VALUES ('청산리');

 

  ⚡️ 삽입할 때 뿐만 아니라 UPDATE할 때도 체크 제약 조건을 점검

 
UPDATE tCheckTest SET grade = 4 WHERE grade IS NOT NULL;

 

  📌 4학년은 체크 조건에 위배되어 아무 레코드도 갱신하지 않음

 

// region 필드는 반드시 체크 제약 조건에 지정한 지역 중의 하나여야 한다. 
region CHAR(6) NOT NULL CHECK (region IN ('경기', '충청', '강원','경상', '전라', '제주'));
INSERT INTO tCity VALUES ('울릉',72,1,'n','우산'); // 이 문장은 에러처리
 

  📌 '우산'이라는 지역명을 인정하지 않기 때문에 에러

  📌 서버는 레코드를 삽입, 변경할 때마다 제약 조건을 점검하며 클라이언트에서 무슨 짓을 하더라도 이 계약 조건을 어길 수 없음

        ∴  따라서 tCity에 있는 모든 도시는 6개 지역 중 하나에 소속됨을 보장 할 수 있음

[ 내용 참고 : IT 학원 강의 ]

 


 

 

1. 무결성 관리

 

👩🏻‍💻 모든 데이터가 결함없이 완벽한 상태를 무결성 Intergrity 이라고 함

 

  📍 시스템이 완벽해도 무결성이 깨지는 경우가 종종 발생

        ➡️수나 에러로 잘못된 데이터를 입력할 수도 있고 불가항력적인 요인으로 멀쩡하던 데이터가 파괴되기도 함

  📍 무결성이 깨지면 데이터를 사용하는 응용 프로그램도 제대로 돌아가지 x

 

 

    🔎  예를 들어 컴퓨터 부품을 파는 쇼핑몰의 주문 테이블을 살펴보자

 

// 날짜 회원 제품 개수
2020-03-04 가희원 하이넥스 메모리 DDR 64G 2
2020-03-09 나종원 엘지 그램 노트북 15 32767
2042-12-25 다민기 ATI 그래픽 카드 -8242
2020-04-01 라진규 NULL 3

  📍 첫 번째 레코드만 정상이고 이후의 레코드는 이상해 보임

        ➡️ 나종원의 노트북 32,767개는 정상 주문으로 보기 힘들고,

             다민기의 경우 주문날짜도 2042년이고 주문 개수도 음수

             라진규는 주문한 제품의 이름을 입력하지 않았음.

 

 

👩🏻‍💻  무결성이 깨지는 원인은 다양. 디스크가 기계적으로 손상되거나 네트워크의 불안정으로 데이터가 손실되는 경우, 가장 큰 원인은 사람의 실수

👩🏻‍💻  발생 가능한 모든 오류에 대처하는 방어 체계로 데이터를 처리하는 단계별로 서버측과 클라이언트측이 각자의 무결성 관리 정책을 제공

  📍 '클라이언트측'에서는 입력할 정보에 꼭 맞는 컨트롤로 오입력을 원천 차단, 유효한 값만 서버로 보냄.

  📍 '서버측'의 무결성 처리는 DBMS가 규칙을 기억하고 있다가 비정상 데이터의 입력을 거부하는 방식

 

 

[ 적용 범위에 따른 무결성 구분 ]

 *  컬럼 무결성 컬럼 하나에 저장되는 원자적인 값을 점검하며 도메인 무결성이라고도함
      ➡️ 타입 지정, 널 허용 여부, 체크, 기본값 등의 제약이 있음.

 *  엔티티 무결성 레코드끼리 중복값을 가지지 않도록 하여 유일한 식별자를 관리.
     예를 들어 주민등록번호가 같은 두 명이 존재하지 않아야 함. 기본키와 유니크 제약이 있음.

 *  참조 무결성 테이블간의 관계를 구성하는 키가 항상 유효하도록 관리하며 외래키 제약으로 관리.

 

 

INSERT INTO tCity VALUES ('대구', '꽤 넓음', '200만명', 'n', '대한민국'); // 실행시 에러가 남
 

  📍 인구나 면적은 수치형이어서 123 같은 숫자만 가능하며 문자열은 저장할 수 없음. 타입이 맞더라도 지정한 길이를 초과 x

  📍 타입은 물리적인 형태만 지정할 뿐 논리적인 값까지 점검하지는 못 함  ex. 음수 or 지나치게 큰 수

        ➡️ 논리적인 값까지 저장할 수 있는 장치가 필요. DBMS는 주로 제약을 사용

 

👩🏻‍💻  '제약 Constraint'은 조건을 위반하는 데이터를 방지하여 완결성을 보장하는 규칙

  📍 제약이 없으면 무결성이 쉽게 깨짐

  📍 편집창에서 필드의 입력값 형태를 미리 지정해두는 것이 바로 제약이며 이 규칙에 맞는 데이터만 입력받음

        ➡️ 쿼리문에서는 NULL, NOT NULL, PRIMARY KEY 같은 속성으로 제약을 지정

 

 


 

2.  NULL 허용

 

👩🏻‍💻 NULL은 아무것도 입력되지 있지 않은 것이며 알 수 없거나 결정되지 않은 특수한 상태를 의미

👩🏻‍💻 필드의 NULL 허용 속성은 NULL 상태가 존재하는지를 지정

 

  ⚡️ 반드시 입력해야 하는 필수 필드는 NULL을 허용 x, 없어도 괜찮은 필드는 NULL을 허용 o

         ➡️ 필수 정보 : 아이디, 이름, 비밀번호, 이메일

        ➡️ 필수 정도 아닌 것 : 홈페이지, 주소, 전화번호

  ⚡️ Nullable (Null 허용), Not Nullable(Null 허용 x)

 

// tCity 테이블의 생성 스크립트
CREATE TABLE tCity (
 name CHAR(10) PRIMARY KEY,
 area INT NULL ,
 popu INT NULL ,
 metro CHAR(1) NOT NULL,
 region CHAR(6) NOT NULL
);
 

  📌  name, metro, region 필드는 PRIMARY KEY 또는 NOT NULL 속성이 있어 NULL을 허용하지 않음을 지정

  📌  반면 area와 pupu 필드는 NULL 속성으로 지정되어 있어 생략 가능

 

 

👩🏻‍💻  INSERT 명령으로 레코드를 삽입할 때 NOT NULL로 지정한 필드는 반드시 입력해여 함

       ➡️ 필수 정보 중 하나라도 빼 먹으면 정상적인 정보가 아니라고 판단하여 레코드 삽입을 거부

// 다음 두 쿼리문은 정상 실행.
INSERT INTO tCity (name, popu, metro, region) VALUES ('울산', 114, 'y', '경상');
INSERT INTO tCity (name, metro, region) VALUES ('삼척', 'n', '강원');
 

 

  📌 울산은 면적을 모르는 상태여서 area 필드는 빼고 입력 ( 생략한 area는 NULL이 됨 )

  📌 삼척은 인구, 면적을 다 생략

 

 


 

// 다음 두 쿼리문은 에러가 남.
INSERT INTO tCity (area, popu, metro, region) VALUES (456, 123, 'n', '충청');
INSERT INTO tCity (name, area, popu) VALUES ('의정부', 456,123);
 

📌 충청도에 있는 어느 도시인데 이름은 없음. 필수 값이 누락되어 에러

📌 의정부는 있지만 소속과 광역시 여부가 없어 역시 무효한 정보이면 DBMS는 이런 무효한 레코드의 삽입을 거부

 


 

⚡️ NULL 허용 여부를 생략해 버리면 어떻게 될까?

 

CREATE TABLE tNullable (
 name CHAR(10) NOT NULL,
 age INT
);
 

  📌  name은 필수 필드임이 분명하나 age는 정수형이라는 것만 밝혔을 뿐 NULL 허용 여부에 대한 지정이 없음

  📌  NULL 허용 여부를 생략하면 DBMS의 디폴트 허용 여부를 따른다.

 

INSERT INTO tNullable (name, age) values ('흥부', 36);
INSERT INTO tNullable (name) values ('놀부');
INSERT INTO tNullable (age) values (44);
 

📌 모든 필드값을 다 입력한 흥부는 에러 없이 입력이 된다.

📌 나이를 생략한 놀부는 DBMS의 설정에 따라 입력될 수도 있고 아닐 수도 있다.

      ➡️ 대부분의 DBMS는 NULL 허용이 디폴트여서 이상 없이 삽입될 확률이 높음

📌 이름을 생략하고 나이 44세만 입력하면 에러

 

 


 

⚡️ NULL 허용 속성은 디폴트는 DBMS마다 다르며 데이터베이스나 연결 수준에서 변경할 수도 있다.

⚡️ 그러나 설정에 영향을 받는 명령은 일관성이 없어 위험함

⚡️ 속성을 생략하지 말고 필드 선언문 끝에 항상 NULL, NOT NULL을 분명히 명시하는 것이 바람직하다.

 

 


 

3.  기본값

 

👩🏻‍💻 NULL 허용 속성은 데이터베이스의 성능을 저해하는 주범

       ➡️ 항상 NULL 상태를 감안하여 필드값이 존재하는지 점검해야 하고 보통의 값과는 다루는 방식이 달라 느릴 수 밖에 없다.

👩🏻‍💻 NULL 허용 대신 기본값 DEFAULT을 사용하는 것이 성능상 유리.

👩🏻‍💻 기본값은 필드값을 지정하지 않을 때 자동으로 입력할 값

      ➡️ 보통 무난한 값을 지정하는데 수치형은 0이 적당하고 문자열을 비워 두거나 'N/A'등을 많이 사용

 

 

⚡️ 도시중 광역시는 몇 개 되지 않으며 대부분 지역에 소속

⚡️ 이럴 때 metro 필드 속성에 DEFAULT 키워드와 함께 디폴트 값을 'n'으로 지정

⚡️ DEFAULT 키워드는 NULL 허용 속성보다 앞에 와야 함 (MariaDB는 순서를 강제하지 않지만 오라클은 순서를 바꾸면 에러 처리)

CREATE TABLE tCityDefault (
 name CHAR(10) PRIMARY KEY,
 area INT NULL ,
 popu INT NULL ,
 metro CHAR(1) DEFAULT 'n' NOT NULL, // 값을 괄호로 감싸 DEFAULT('n')이라고 적어도 됨
 region CHAR(6) NOT NULL
);
 
 

  📌 새로운 레코드를 삽입할 때 metro 필드를 지정하지 않으면 자동으로 'n'을 적용

  📌 기본값은 입력하지 않을 때만 적용하는 것, 값을 직접 지정하면 기본값을 무시하고 지정한 값을 적용

 

INSERT INTO tCityDefault (name, area, popu, region) 
  VALUES ('진주', 712, 34, '경상');
INSERT INTO tCityDefault (name, area, popu, metro, region)
  VALUES ('인천', 1063, 295, 'y', '경기');
 

 

📌 진주시는 면적, 인구, 지역만 지정하고 광역시 여부는 생략.

      ➡️ 필드 목록에 metro가 아예 없는데 이 경우 디폴트가 적용되어 광역시가 아닌 것으로 삽입

📌 인천시는 모든 필드의 값을 다 제공하여 광역시로 등록

 


 

⚡️ 기본값 설정도 편집 가능해서 테이블 생성시와 달라질 수 있다.

 

INSERT INTO tCityDefault VALUES ('강릉', 1111, 22, '강원'); -- 에러
INSERT INTO tCityDefault VALUES ('강릉', 1111, 22, DEFAULT, '강원'); -- 정상 실행
 

  📌 필드 목록을 생략하면 값 목록이 완전해야 함

  📌 metro에 기본값이 있더라도 값 목록에 이 자리는 비우면 안된다.

  📌 값 목록을 완전히 적되 기본값을 적용할 필드값에 DEFAULT라고 적음. 기본값으로 변경할 때도 DEFAULT 키워드를 사용.

 

// 다음 명령을 인천의 metro 필드를 기본값인 'n'으로 변경.
 UPDATE tCityDefault SET metro = DEFAULT WHERE name = '인천'
 

 

 

 

⚡️ 기본값의 유무와 NULL 허용 여부는 완전히 별개의 속성임을 주의

⚡️ 기본값이 지정되어 있더라도 NULL을 직접 입력할 수 있고, UPDATE 명령으로 NULL로 바꿀 수도 있음.

⚡️ 기본값은 생략시 적용할 값일 뿐이지 NULL 허용 여부까지 통제하는 것은 아님.

 

 

 

 

[ 내용 참고 : IT 학원 강의 ]

 

+ Recent posts