테이블 간의 관계

 

먼저 만들 테이블의 ERD이다.

이 다이어그램은 https://www.erdcloud.com/ 에서 만들 수 있다.

 

식별관계에 대해 예시를 통해 먼저 알아보자.

CREATE TABLE member1 (
    id VARCHAR2(30) PRIMARY KEY,
    pwd VARCHAR2(100) NOT NULL,
    name VARCHAR2(30) NOT NULL
);

-- 관계 -> 1:1(식별관계, 기본키이면서 참조키)
CREATE TABLE member2 (
    id VARCHAR2(30),
    birth DATE,
    tel VARCHAR2(30),
    CONSTRAINT pk_member2_id PRIMARY KEY(id),
    CONSTRAINT fk_member2_id FOREIGN KEY(id)
        REFERENCES member1(id)
);

1:1의 식별관계일 때, member1 테이블에 id 데이터가 꼭 있어야member2테이블에서는 그 id를 참조키로 하여금 birth와 tel을 식별할 수 있다. 또 member2의 기본키도 id이다.

기본키이면서 참조키일 때 식별관계라고 한다.

 

비식별관계는 단순히 참조만 할 때 비식별관계라고한다.

예시

-- 관계 -> 1:다(비식별관계, 단순한 참조키)
CREATE TABLE guest (
    num NUMBER PRIMARY KEY,
    id VARCHAR2(30) NOT NULL,
    content VARCHAR2(4000) NOT NULL,
    reg_date DATE DEFAULT SYSDATE,
    FOREIGN KEY(id)
        REFERENCES member1(id)
);

guest 테이블에서는 member1의 id컬럼은 단순한 참조키이다. 

 

1:다, 식별관계

-- 두개의 컬럼을 기본키로 설정. 참조키 2개 식별관계
-- 관계 member1 : guestLikst => 1:다, guest : guestLike => 1:다
CREATE TABLE guestLike ( -- LIKE 예약어이기 때문에 단독으로 이름으로 줄 수 없음.
    num NUMBER,
    id VARCHAR2(30),
    PRIMARY KEY(num, id),
    FOREIGN KEY(num)
        REFERENCES guest(num),
    FOREIGN KEY(id)
        REFERENCES member1(id)
);

 

1:다, 비식별관계

-- 동일한 컬럼을 두 번 참조 : 예 -> 쪽지 테이블
-- 1:다, 비식별
CREATE TABLE note (
    num NUMBER PRIMARY KEY,
    sendId VARCHAR2(30) NOT NULL,
    receiveID VARCHAR2(30) NOT NULL,
    content VARCHAR2(4000) NOT NULL,
    FOREIGN KEY(sendId)
        REFERENCES member1(id),
    FOREIGN KEY(receiveId)
        REFERENCES member1(id)
);

 

테이블을 확인할 때 알아두면 좋은 쿼리들 >

더보기

1) 부와 자 관계의 모든 테이블 출력하는 쿼리

SELECT fk.owner, fk.constraint_name,
    pk.table_name parent_table, fk.table_name child_table
FROM all_constraints fk, all_constraints pk 
WHERE fk.r_constraint_name = pk.constraint_name AND fk.constraint_type = 'R'
ORDER BY fk.table_name;

2) 『테이블명』을 참조하는 모든 테이블 목록 출력(자식 테이블 목록 출력)

SELECT fk.owner, fk.constraint_name , fk.table_name 
FROM all_constraints fk, all_constraints pk 
WHERE fk.r_constraint_name = pk.constraint_name 
    AND fk.constraint_type = 'R' 
    AND pk.table_name = UPPER('테이블명')
ORDER BY fk.table_name;

3) 『테이블명』이 참조하고 있는 모든 테이블 목록 출력(부모 테이블 목록 출력)

SELECT table_name FROM user_constraints
WHERE constraint_name IN (
    SELECT r_constraint_name 
    FROM user_constraints
    WHERE table_name = UPPER('테이블명') AND constraint_type = 'R'
);

4) 『테이블명』의 부모 테이블 목록 및 부모 컬럼 목록 출력

--  부모 2개 이상으로 기본키를 만든 경우 여러번 출력 됨

SELECT fk.constraint_name, fk.table_name child_table, fc.column_name child_column,
    pk.table_name parent_table, pc.column_name parent_column
FROM all_constraints fk, all_constraints pk, all_cons_columns fc, all_cons_columns pc
WHERE fk.r_constraint_name = pk.constraint_name
    AND fk.constraint_name = fc.constraint_name
    AND pk.constraint_name = pc.constraint_name
    AND fk.constraint_type = 'R'
    AND pk.constraint_type = 'P'
    AND fk.table_name = UPPER('테이블명');

접은 글에 있는 쿼리를 통해 확인 할 수 있다.

 

테이블 삭제는 테이블 생성한 반대로 삭제하면 된다.

-- 삭제
DROP TABLE note PURGE;
DROP TABLE guestLike PURGE;
DROP TABLE guest PURGE;
DROP TABLE member2 PURGE;
DROP TABLE member1 PURGE;

 

참조키에 ON DELETE CASCADE 옵션을 넣었을 때

 

CREATE TABLE test1 (
    code VARCHAR2(50) PRIMARY KEY,
    subject VARCHAR2(100) NOT NULL
);

CREATE TABLE test2 (
    num NUMBER PRIMARY KEY,
    code VARCHAR2(50) NOT NULL,
    qty NUMBER(10) NOT NULL,
    FOREIGN KEY(code) 
        REFERENCES test1 (code) ON DELETE CASCADE
);

-- 데이터 추가
INSERT INTO test1 (code, subject) VALUES('a', 'a');
INSERT INTO test1 (code, subject) VALUES('b', 'b');
INSERT INTO test1 (code, subject) VALUES('c', 'c');

INSERT INTO test2 (num, code, qty) VALUES (1, 'a', 10);
INSERT INTO test2 (num, code, qty) VALUES (2, 'b', 10);
INSERT INTO test2 (num, code, qty) VALUES (3, 'a', 15);
INSERT INTO test2 (num, code, qty) VALUES (4, 'c', 10);
INSERT INTO test2 (num, code, qty) VALUES (5, 'a', 20);

SELECT * FROM test1;
SELECT * FROM test2;

DELETE FROM test1 WHERE code = 'a';
    -- ON DELETE CASCADE 옵션으로 test1 테이블과 test2 테이블 모두 삭제됨
SELECT * FROM test1;
SELECT * FROM test2;

데이터 추가까지 완료한 모습. 

ON DELETE CASCADE가 참조키에 있는 경우, 자식 테이블에서 참조하는 값을 부모테이블에서 지우게 되면 해당하는 값을 가진 모든 데이터는 부모, 자식 테이블에서 삭제된다.


이 ERD에 해당하는 테이블을 만들어보자. 

CREATE TABLE dept_test (
    dept_id VARCHAR2(30),
    dept_name VARCHAR2(50) NOT NULL,
    manager_id VARCHAR2(30) NOT NULL,
    PRIMARY KEY (dept_id )
);

CREATE TABLE emp_test (
    emp_id VARCHAR2(30),
    name VARCHAR2(50) NOT NULL,
    email VARCHAR2(50) NOT NULL,
    dept_id VARCHAR2(30) NOT NULL,
    PRIMARY KEY ( emp_id ),
    CONSTRAINT fk_empTest_deptId
    FOREIGN KEY (dept_id) REFERENCES dept_test ( dept_id)
);

ALTER TABLE dept_test ADD CONSTRAINT fk_deptTest_managerId FOREIGN KEY (manager_id) 
    REFERENCES emp_test( emp_id );

두 테이블이 서로의 컬럼을 참조키로 가지고 있어서 테이블 생성 후 ALTER TABLE로 FOREIGN KEY를 주었다.

 

이런 테이블을 삭제할 때는 

DROP TABLE emp_test CASCADE CONSTRAINTS PURGE;
DROP TABLE dept_test CASCADE CONSTRAINTS PURGE;

이렇게 CASCADE CONSTRAINTS 를 넣고 삭제해야한다.

 

참조키를 비활성화/활성화 시키는 방법을 알아보자.

 

실습을 위한 예시 생성

CREATE TABLE dept_test (
    dept_id VARCHAR2(30)  PRIMARY KEY,
    dept_name VARCHAR2(50)  NOT NULL,
    manager_id VARCHAR2(30)  NOT NULL
);

CREATE TABLE emp_test (
    emp_id VARCHAR2(30) PRIMARY KEY,
    name VARCHAR2(50) NOT NULL,
    email VARCHAR2(50) NOT NULL,
    dept_id VARCHAR2(50) NOT NULL
);

-- 참조키 제약 조건
ALTER TABLE dept_test
    ADD CONSTRAINT fk_deptTest_managerId FOREIGN KEY (manager_id) REFERENCES emp_test(emp_id);

ALTER TABLE emp_test
    ADD CONSTRAINT fk_empTest_deptId FOREIGN KEY (dept_id) REFERENCES dept_test(dept_id);

SELECT * FROM tab;

-- 데이터 추가
INSERT INTO dept_test(dept_id, dept_name, manager_id) VALUES ('a1', '영업부', '1001');
    -- 에러 ORA-02291 : 참조키가 존재하지 않음
INSERT INTO emp_test(emp_id, name, email, dept_id) VALUES ('1001','김영업', 'kim@a.com', 'a1');
    -- 에러 ORA-02291 : 참조키가 존재하지 않음

서로의 컬럼을 참조키로 가지고 있기 때문에 이 경우에는 다른 테이블의 참조키를 비활성화 하고 데이터를 추가해야한다.

-- 참조키 비활성화 시키기 (삭제는 안하고 잠시 사용하지 못하게 막음)
ALTER TABLE dept_test DISABLE CONSTRAINT fk_deptTest_managerId CASCADE;
ALTER TABLE emp_test DISABLE CONSTRAINT fk_empTest_deptId CASCADE;
    -- CASCADE : 부모 테이블과 자식 테이블의 설정이 되어 있을 때 
    -- 부모 테이블의 제약 조건을 비활성화하면 이를 참조하는 자식 테이블의 제약조건까지 비활성화

CASCADE ? 부모 테이블과 자식 테이블 설정이 되어있을 때, 부모 테이블의 제약 조건을 비활성화하면 이를 참조하는 자식 테이블의 제약조건까지 비활성화된다.

 

비활성화 확인해보자.

-- 비활성화 확인
SELECT * FROM USER_CONSTRAINTS WHERE table_name = UPPER('dept_test');
SELECT * FROM USER_CONSTRAINTS WHERE table_name = UPPER('emp_test');
    -- status 컬럼

참조키를 비활성화 하면 참조키 제약 조건 위반 데이터를 추가할 수 있다.

INSERT INTO dept_test(dept_id, dept_name, manager_id) VALUES ('a1', '영업부', '1001');
INSERT INTO dept_test(dept_id, dept_name, manager_id) VALUES ('b1', '개발부', '2001');
INSERT INTO dept_test(dept_id, dept_name, manager_id) VALUES ('c1', '총무부', '3001');
SELECT * FROM dept_test;

INSERT INTO emp_test(emp_id, name, email, dept_id) VALUES ('1001','김영업', 'kim@a.com', 'a1');
INSERT INTO emp_test(emp_id, name, email, dept_id) VALUES ('2001','나개발', 'na@a.com', 'b1');
SELECT * FROM emp_test;

이 후 다시 참조키를 활성화 하면 에러가 발생하는데, 제약조건 위반 데이터가 존재하기 때문이다. 

dept_test 테이블에는 manager_id로 3001이 있는데 이는 emp_test의 테이블의 emp_id를 참조하는 키 이기 때문이다. 따라서 emp_test 테이블에 emp_id가 3001인 자료가 있어야한다.

 

INSERT INTO emp_test(emp_id, name, email, dept_id) VALUES ('3001','홍총무', 'hong@a.com', 'c1');

ALTER TABLE dept_test ENABLE CONSTRAINT fk_deptTest_managerId ;
ALTER TABLE emp_test ENABLE CONSTRAINT fk_empTest_deptId ;

emp_id가 3001인 자료를 추가하고 나서 제약조건을 활성화 시키는 명령어를 입력하자, 

잘 시행됨을 볼 수 있다.

참조키 (외래키, FOREIGN KEY)

  • 테이블 간의 데이터의 일관성을 보장하기 위한 제약조건이다.
  • 한 테이블의 PRIMARY KEY나 UNIQUE 컬럼을 다른 테이블에 추가하여 두 테이블 간 연결을 설정한다.
  • 부모 테이블이 먼저 생성된 후 자식 테이블(FOREIGN KEY를 포함하는 테이블)이 생성되어야 한다.

 

예제를 통해 알아보자.

-- std : 부모 테이블
CREATE TABLE std (
    hak VARCHAR2(30) PRIMARY KEY,
    name VARCHAR2(30) NOT NULL,
    email VARCHAR2(50) NOT NULL,
    tel VARCHAR2(30) NOT NULL
);

-- std_score : 자식 테이블
    -- 부모의 PRIMARY KEY 또는 UNIQUE 만 참조 가능
CREATE TABLE std_score(
    num NUMBER PRIMARY KEY,
    hak VARCHAR2(30) NOT NULL,
    gubun NUMBER(1) NOT NULL,
    java NUMBER(3) NOT NULL,
    oracle NUMBER(3) NOT NULL,
    CONSTRAINT fk_stdscore_hak FOREIGN KEY ( hak )
        REFERENCES std(hak)
);

SELECT * FROM USER_CONSTRAINTS WHERE table_name = 'STD';
SELECT * FROM USER_CONSTRAINTS WHERE table_name = 'STD_SCORE';
    -- P : 기본키, R : 참조키
SELECT * FROM USER_CONS_COLUMNS WHERE table_name = 'STD';
SELECT * FROM USER_CONS_COLUMNS WHERE table_name = 'STD_SCORE';

CREATE TABLE 문에서 FOREIGN KEY를 추가할 때는 FOREIGN KEY (컬럼) REFERENCES 참조하는테이블(참조하는컬럼) 을 통해 추가한다.

기본키는 CONSTRAINT TYPE 이 P로 되어있고 참조키는 R로 되어있다.

데이터를 추가해보자.

INSERT INTO std_score(num, hak, gubun, java, oracle) VALUES (1, '11', 1, 100, 90);
    -- 에러 ORA-02291 : 부모 테이블에 존재하지 않는 hak은 추가 불가
    
INSERT INTO std(hak, name, email, tel) VALUES ('11', 'aa', 'aa', '11');
INSERT INTO std(hak, name, email, tel) VALUES ('22', 'bb', 'bb', '22');
INSERT INTO std(hak, name, email, tel) VALUES ('33', 'cc', 'cc', '33');

INSERT INTO std_score(num, hak, gubun, java, oracle) VALUES (1, '11', 1, 100, 90);
INSERT INTO std_score(num, hak, gubun, java, oracle) VALUES (2, '11', 2, 90, 90);

INSERT INTO std_score(num, hak, gubun, java, oracle) VALUES (3, '33', 1, 90, 90);
COMMIT;

SELECT * FROM std;
SELECT * FROM std_score;

부모테이블에 아직 아무데이터도 있지 않은 상태이다. 이 상태에서 자식테이블에 값을 추가하면 에러(ORA-02291)가 발생한다. 

참조하는 키가 먼저 부모테이블에 존재해야 자식테이블에 값도 추가할 수 있다.

먼저 부모 테이블에 데이터를 입력하고 자식테이블에도 입력을 하면 잘 들어가는 것을 확인할 수 있다.

 

입력된 데이터의 값을 수정하거나 삭제해보자.

DELETE FROM std WHERE hak = '11';
    -- 에러 ORA-02292 : 참조하는 레코드가 있음.

UPDATE std SET hak = '13' WHERE hak = '11';
    -- 에러 ORA-02292 : 참조하는 레코드가 있음

UPDATE std SET hak = '23' WHERE hak = '22';
    -- 가능 : 참조하는 레코드가 없음
SELECT * FROM std;

DELETE FROM std WHERE hak = '23';
    -- 가능

부모테이블에 있는 PRIMARY KEY인 HAK을 바꿔보려 했으나 이 값을 참조키로 자식테이블에서 데이터를 추가했기 때문에 오류가 발생한다.(ORA-02292) 하지만 HAK 22의 경우 이 값으로 하여금 자식 테이블이 가지고 있는 값이 아무 것도 없기 때문에 23으로 변경할 수 있었다. 또 삭제도 가능했다.

 

 

자식이 부모테이블에있는 컬럼을 참조키로 가지고 있는 경우, 부모 테이블은 여태까지 했던 방식으로는 불가하다.

DROP TABLE std PURGE;
    -- 에러 ORA-02449 : 참조하는 테이블이 있으면 삭제 불가
    -- 모든 자식 테이블을 먼저 삭제하고 삭제해야 함

에러 (ORA - 02449)가 발생한다. 

강제로 삭제하는 방법이 존재한다. 참조키를 삭제해 부와 자의 관계를 없앤다.

-- 강제로 삭제. 참조키가 삭제 됨(부와 자의 관계가 없어짐)
DROP TABLE std CASCADE CONSTRAINTS PURGE;
SELECT * FROM tab;

SELECT * FROM USER_CONSTRAINTS WHERE table_name = 'STD_SCORE';
    -- R(참조키)이 제거됨

DROP TABLE std_score PURGE;

명령어

DROP TABLE std CASCADE CONSTRAINTS PURGE;

통해서 부모테이블을 강제로 삭제했다. 그 후 테이블 제약조건을 확인해보니 원래 HAK은 CONSTRAINT_TYPE 이 FOREIGN KEY 였기 때문에 F 였으나 이제는 NOT NULL의 CHECK조건을 가지고 있으므로 C로 바뀌어있는 것을 확인할 수 있다. 참조키를 가지고 있지 않기 때문에 평소 했던 방법으로 테이블을 삭제할 수 있다.

DROP TABLE std_score PURGE;

 

참조하는 컬럼과 참조당하는 컬럼은 타입과 크기가 같아야한다. 하지만 컬럼명은 다를 수 있다.

 

예시를 통해 알아보자.

-- std : 부모 테이블
CREATE TABLE std (
    hak VARCHAR2(30) PRIMARY KEY,
    name VARCHAR2(30) NOT NULL,
    email VARCHAR2(50) NOT NULL,
    tel VARCHAR2(30) NOT NULL
);

-- std_score : 자식 테이블
CREATE TABLE std_score(
    num NUMBER PRIMARY KEY,
    shak VARCHAR2(30),
    gubun NUMBER(1) NOT NULL,
    java NUMBER(3) NOT NULL,
    oracle NUMBER(3) NOT NULL,
    FOREIGN KEY ( shak )
        REFERENCES std(hak)
);

SELECT * FROM USER_CONSTRAINTS WHERE table_name = 'STD';
SELECT * FROM USER_CONSTRAINTS WHERE table_name = 'STD_SCORE';
SELECT * FROM USER_CONS_COLUMNS WHERE table_name = 'STD';
SELECT * FROM USER_CONS_COLUMNS WHERE table_name = 'STD_SCORE';

테이블에 데이터를 넣어보자.

INSERT INTO std_score(num, shak, gubun, java, oracle) VALUES (1, NULL , 1, 100, 90);
    -- 가능. 부모에 존재하지 않아도 참조키가 NULL이 가능하므로 NULL은 추가 가능
INSERT INTO std_score(num, shak, gubun, java, oracle) VALUES (2, '11', 1, 80, 90);
    -- 에러 ORA-02291 : 부모의 hak에 11이 없음.

자식테이블에 외래키에 NOT NULL 조건을 붙이지 않았으므로 NULL이 가능하다.

하지만 자식테이블의 참조키에 NULL 이외의 값으로는 데이터를 추가할 수 없다. 부모테이블에 값이 있어야 추가가 가능하다.

 

DEFAULT 설정  : 값을 입력하지 않으면 뒤에 값을 등록한다.

 

예시를 통해 알아보자.

 

CREATE TABLE test1(
    num NUMBER PRIMARY KEY,
    name VARCHAR2(30) NOT NULL,
    subject VARCHAR2(500) NOT NULL,
    content VARCHAR2(4000) NOT NULL,
    reg_date DATE DEFAULT SYSDATE,
    hitCount NUMBER DEFAULT 0 NOT NULL
);

reg_date와 hitCount는 값을 입력하지 않으면 각각 SYSDATE 와 0을 입력한다.

 

데이터를 입력해서 확인해보자.

INSERT INTO test1(num, name, subject, content) VALUES(1, 'a', 'a', 'a');
INSERT INTO test1(num, name, subject, content) VALUES(2, 'b', 'b', 'b');
SELECT * FROM test1;

INSERT INTO test1(num, name, subject, content, reg_date) 
	VALUES(3, 'c', 'c', 'c', TO_DATE('2000-10-10', 'YYYY-MM-DD'));
SELECT * FROM test1;

INSERT INTO test1(num, name, subject, content, reg_date, hitCount) 
	VALUES(4, 'd', 'd', 'd', SYSDATE, 10);
SELECT * FROM test1;

결과를 보면 입력시 reg_date 와 hitCount를 넣지 않았더니DEFALUT로 설정한 SYSDATE 와 0이 들어간 것을 확인 할 수 있다.

reg_date와 hitCount를 직접 입력했을 때는 입력한 값이 들어감을 확인 할 수 있다.

 

※ DEFALUT가 있어도 행을 추가할 때 값이 들어가지 않을 수도 있기 때문에 자바로 코딩할 때 DEFALUT가 없다고 생각하고 값을 넣어주자!

DEFALUT의 값을 확인 방법

명령어를 이용해서 확인할 수 있다.

SELECT * FROM cols WHERE table_name = 'TEST1'

SELECT * FROM user_tab_columns WHERE table_name = 'TEST1'

SELECT * FROM cols WHERE table_name = 'TEST1'; -- DEFAULT 확인 가능
SELECT * FROM user_tab_columns WHERE table_name = 'TEST1'; -- DEFAULT 확인 가능

DATA_DEFAULT 컬럼에 보면 SYSDATE 와 0이 들어가있는 것을 확인 할 수 있다.

 

이미 존재하는 테이블 칼럼에 DEFAULT를 추가하려면 ALTER TABEL 명령을 이용한다.

ALTER TABLE test1 MODIFY 컬럼명 DEFAULT 값;

DEFAULT 제거하는 방법

ALTER TABLE 테이블명 MODIFY 컬럼 DEFAULT NULL;

ALTER TABLE 테이블명 MODIFY 컬럼 DEFAULT NULL; 을 통해서 DEFAULT를 제거한다. DELETE를 사용하지 않는다.


CHECK 제약조건에 대해 알아보자.

 

테이블 생성을 통한 예시로 알아보자.

CREATE TABLE test2(
    num NUMBER PRIMARY KEY,
    name VARCHAR2(30) NOT NULL,
    com NUMBER(3) NOT NULL,
    excel NUMBER(3) CHECK( excel BETWEEN 0 AND 100),
    CONSTRAINT ch_test2_com CHECK ( COM BETWEEN 0 AND 100)
);


SELECT * FROM cols WHERE table_name = 'TEST2'; 
SELECT * FROM user_tab_columns WHERE table_name = 'TEST2';

excel을 입력할 때 0~100사이여야 입력이 가능하다. com의 경우 테이블레벨에서 CHECK제약을 주었다. 이렇게 CHECK 제약조건을 줄 수 있다. 하지만 이렇게 CHECK 제약을 주는 것보다 후에는 자바에서 제약을 주는 것이 속도면에서 효율적이다.

 

테이블에 값을 입력해보자.

INSERT INTO test2(num, name, com, excel) VALUES (1, 'a', 100, 100);

INSERT INTO test2(num, name, com, excel) VALUES (2, 'b', 120, 100);
    -- 에러 ORA-02290 : CHECK 제약 위반

com과 excel 모두 0~100까지의 값만을 넣을 수 있기 때문에 ORA-02290 : CHECK제약조건위반으로 값이 입력되지 않는다.

 

문제풀이 : 여태껏 배운 것을 바탕으로 테이블을 만들어보자 > 

더보기
테이블명 : test3num NUMBER PRIMARY KEYname VARCHAR2(50) NOT NULLgender VARCHAR2(10) NOT NULLsdate DATE NOT NULLedate DATE NOT NULL

 

-- gender 컬럼은 '남', '여'만 등록될 수 있도록 설정한다.-- 다음의 CHECK 제약을 추가한다 : sdate<= edate

 

CREATE TABLE test3 (
    num NUMBER PRIMARY KEY,
    name VARCHAR2(50) NOT NULL,
    gender VARCHAR2(10) NOT NULL CHECK(gender IN('남', '여')),
    -- '남', '여'만 등록될 수 있도록 설정
    -- gender VARCHAR2(10) CHECK(gender IN('남', '여')) DEFAULT '여' 이거는 안된다.
    -- DEFAULT와 CHECK를 동시에 설정할 경우는 DEFAULT를 먼저
    -- gender VARCHAR2(10) DEFAULT '여' CHECK(gender IN('남', '여')) 로 해야함.
    sdate DATE NOT NULL,
    edate DATE NOT NULL
);

ALTER TABLE test3 ADD CHECK ( sdate<= edate );
-- 다른 컬럼을 이용하여 CHECK 제약을 만드는 경우에는 테이블 레벨 또는
-- ALTER TABLE로 만든다.

gender VARCHAR2(10) NOT NULL CHECK(gender = '남' OR gender = '여') 를 gender IN('남', '여') 로 줄 수 있다.

다른 컬럼을 이용하여 CHECK 제약을 만드는 경우에는 테이블 레벨 또는 ALTER TABLE을 통해 제약조건을 만든다.

 

데이터를 추가해보자.
INSERT INTO test3(num, name, gender, sdate, edate) VALUES (1, 'a', '남', SYSDATE, SYSDATE);

INSERT INTO test3(num, name, gender, sdate, edate) 
    VALUES (2, 'b', '남', SYSDATE, TO_DATE('01/01/01', 'RR/MM/DD') );
    -- 에러 CHECK 제약 위반

 edate는 sdate와 같거나 더 뒤의 날짜여야 하므로 CHECK 제약에 위반 (ORA-02290) 되므로 두번째 쿼리는 오류를 발생한다.

UNIQUE 제약 조건 ?

  • 유일성 제약 조건
  • 하나의 테이블에 두 개 이상 만들 수 있다.
  • 두 개 이상의 컬럼으로 UNIQUE를 만들 수 있다.
  • NULL을 허용하면 NULL추가가 가능하다.

 

예시를 통해 알아보자.

CREATE TABLE test1 (
    id VARCHAR2(50),
    pwd VARCHAR2(100) NOT NULL,
    name VARCHAR2(30) NOT NULL,
    email VARCHAR2(50),
    CONSTRAINT pk_test1_id PRIMARY KEY(id),
    CONSTRAINT uq_test1_email UNIQUE(email)
);
SELECT * FROM USER_CONSTRAINTS WHERE table_name= 'TEST1';
    -- P : PRIMARY KEY, U : UNIQUE (유일성 제약 조건)
SELECT * FROM USER_CONS_COLUMNS WHERE table_name= 'TEST1';

예시용 테이블을 생성하고, 기본키와 UNIQUE 제약조건을 주었다.

CONSTRAINT [제약조건명] 을 통해 기본키와 UNIQUE에 이름을 주어서 SELECT * FROM USER_CONSTRAINT WHERE table_name = 'TEST1'; 을 통해 알아볼 수 있었다. 이 명령어를 통해 보면 CONSTRAINT_TYPE에 P가 PRIMARY KEY를 나타내고, U가 UNIQUE를 나타낸다. C는 CHECK 제약조건.

 

이 테이블에 데이터를 추가해보자.

INSERT INTO test1(id, pwd, name, email) VALUES ('1','1','1','1');
INSERT INTO test1(id, pwd, name, email) VALUES ('2','2','2','1');
    -- 에러. ORA-00001 : email 유일성(UNIQUE) 제약 위반
INSERT INTO test1(id, pwd, name, email) VALUES ('2','2','2',NULL);
    -- UNIQUE 이어도 NULL을 허용하면 NULL 추가 가능하고 NULL은 중복 가능

email 컬럼에 UNIQUE를 주었기 때문에 위에 추가된 id 1이 가지고 있는 이메일과 중복되어 에러가 뜨고 데이터는 추가되지 않는다.

email에 컬럼에 NOT NULL을 주지 않았기 때문에 NULL은 추가가 가능하다. 

 


NOT NULL 제약 조건 ?

  • NULL을 허용하지 않는다.
  • '(길이가 0 인 문자열)' 도 NULL 로 취급한다.

존재하는 테이블에 NOT NULL 제약 조건을 설정하려면

ALTER TABLE 테이블명 MODIFY 컬럼 NOT NULL;

ALTER TABLE 테이블명 ADD [ CONSTRAINT 제약조건 이름] CHECK(컬럼 IS NOT NULL);

ALTER TABLE 명령어를 이용한다.

 

NOT NULL 제약 조건을 삭제하려면

ALTER TABLE 테이블명 MODIFY 컬럼 NULL;

ALTER TABLE 테이블명 DROP CONSTRAINT 제약조건이름;

을 이용한다.

 

새롭게 테이블을 만들어서 실습해보자.

CREATE TABLE test1(
    id VARCHAR2(30),
    pwd VARCHAR2(30),
    name VARCHAR2(30),
    email VARCHAR2(30)
);

-- test1 테이블 id에 기본키 추가

ALTER TABLE test1 ADD PRIMARY KEY ( id );
ALTER TABLE test1 ADD CONSTRAINT pk_test1_id PRIMARY KEY ( id );
-- 두 개 다 가능. 제약조건에 이름을 주는 것이 나중에 확인이 편하다.

-- test1 테이블의 email에 UNIQUE 추가

ALTER TABLE test1 ADD UNIQUE (email);
ALTER TABLE test1 ADD CONSTRAINT uq_test1_email UNIQUE (email);

-- test1 테이블의 pwd, name, email  에 NOT NULL 제약 추가

ALTER TABLE test1 MODIFY pwd NOT NULL;
ALTER TABLE test1 MODIFY name NOT NULL;
ALTER TABLE test1 MODIFY email NOT NULL;

ALTER TABLE test1 MODIFY (pwd NOT NULL, name NOT NULL, email NOT NULL);

--test1 테이블 pwd 폭을 100으로 변경

ALTER TABLE test1 MODIFY pwd VARCHAR2(100);

--test1 테이블 email의 NOT NULL 제약 제거

ALTER TABLE test1 MODIFY email NULL;
ALTER TABLE test1 DROP CONSTRAINT uq_test1_email;

 

어제 두 개의 컬럼으로 기본키를 부여한 것으로 실습을 해보자.

-- 두 개의 컬럼으로 기본키 부여
CREATE TABLE test5 (
    id VARCHAR2(30),
    code NUMBER,
    pwd VARCHAR2(30) NOT NULL,
    name VARCHAR2(30) NOT NULL,
    birth DATE,
    CONSTRAINT pk_test5_id PRIMARY KEY( id, code )
    -- 복합키로 기본키를 구성할 때는 꼭 테이블 레벨로 작성해야함
);
SELECT * FROM USER_CONSTRAINTS WHERE table_name= 'TEST5';
SELECT * FROM USER_CONS_COLUMNS WHERE table_name= 'TEST5';

INSERT INTO test5(id, code, pwd, name) VALUES('a', 1, '1', '1');
SELECT* FROM test5;

INSERT INTO test5(id, code, pwd, name) VALUES('a', 1, '2', '2');
    -- 에러 : ORA-00001 기본키 제약 위반
INSERT INTO test5(id, code, pwd, name) VALUES('a', 2, '2', '2');
SELECT* FROM test5;

INSERT INTO test5(id, code, pwd, name) VALUES('b', NULL, '3', '3');
    -- 에러 : ORA-01400 기본키는 NULL이 될 수 없다.
    -- 기본키는 NOT NULL을 안줘도 NOT NULL

UPDATE test5 set code = 3 WHERE id = 'a' AND code = 2; 
-- 이럴때 95퍼 이상 WHERE은 기본키로 준다.
-- 제약 조건을 위반하지 않으면 기본키 수정 가능
SELECT* FROM test5;

 


기본키가 없는 테이블을 생성해서 생성된 테이블에 기본키를 추가해보자.

CREATE TABLE test6 (
    id VARCHAR2(30),
    pwd VARCHAR2(30) NOT NULL,
    name VARCHAR2(30) NOT NULL
);
SELECT * FROM tab;

INSERT INTO test6(id, pwd, name) VALUES ('1', '1', '1');
INSERT INTO test6(id, pwd, name) VALUES ('1', '2', '2');
SELECT * FROM test6;

SELECT * FROM USER_CONSTRAINTS WHERE table_name= 'TEST6';
SELECT * FROM USER_CONS_COLUMNS WHERE table_name= 'TEST6';
-- 기본키가 없음을 확인할 수 있다.

테이블 생성 및 자료 추가

존재하는 테이블에 기본키 추가

ALTER TABLE test6 ADD CONSTRAINT pk_test6_id PRIMARY KEY ( id );
    -- 에러 : ORA-02437. 기본키 위반의 데이터가 존재하므로 추가할 수 없다.

ID를 기본키로 하려고 했으나 이미 들어가 있는 테이블에 ID가 중복적인 것이 존재하므로 기본키 제약조건 위반으로 설정할 수 없다. 따라서 테이블 생성 -> 기본키 추가 -> 테이블에 자료 추가 로 해야함.

 

여기서는 테이블 생성 -> 테이블에 자료 추가 를 먼저 했기 때문에 자료를 다 삭제하고 기본키를 주어야한다.

-- 모든 데이터 삭제
DELETE FROM test6;
COMMIT;
SELECT* FROM test6;

ALTER TABLE test6 ADD CONSTRAINT pk_test6_id PRIMARY KEY ( id );
-- 다시 기본키 추가
SELECT * FROM USER_CONSTRAINTS WHERE table_name= 'TEST6';
SELECT * FROM USER_CONS_COLUMNS WHERE table_name= 'TEST6';
-- 기본키가 추가되었음을 알 수 있다.

생성된 테이블에 기본키를 추가할 때는 ALTER TABLE 테이블명 ADD [ CONSTRAINT 제약조건명] PRIMARY KEY(기본키 설정하고 싶은 컬럼명); 으로 준다.


기본키 제약 조건을 삭제하려면 ?

ALTER TABLE test6 DROP PRIMARY KEY;
SELECT * FROM USER_CONSTRAINTS WHERE table_name= 'TEST6';
SELECT * FROM USER_CONS_COLUMNS WHERE table_name= 'TEST6';
-- 지워졌음을 알 수 있다.

ALTER TABLE 테이블명 DROP PRIMARY KEY; 로 삭제할 수 있다.

기본키(PRIMARY KEY)에 대해 알아보자.

  • 테이블의 각 행을 구분하는 유일성 제약 조건이다.
  • 테이블에 하나만 만들 수 있다.
  • 두 개 이상의 컬럼을 조합해서 하나의 기본키를 만들 수 있다.
  • 중복값을 가질 수 없으며, NULL을 가질 수 없다.

 

컬럼 레벨 방식으로 기본키를 부여해보자.

컬럼 레벨 방식으로 기본키를 부여하면 하나의 컬럼으로만 기본키를 만들 수 있다.

CREATE TABLE test1 (
    id VARCHAR2(30) PRIMARY KEY,
    pwd VARCHAR2(30) NOT NULL,
    name VARCHAR2(30) NOT NULL,
    birth DATE
);

SELECT * FROM USER_CONSTRAINTS WHERE table_name= 'TEST1';

을 통해 제약 조건을 확인해보자

SELECT * FROM USER_CONSTRAINTS WHERE table_name= 'TEST1';

 

이 명령어를 통해서는 어떤 컬럼에 제약 조건이 있는지는 확인이 불가능하고 제약조건이 있다는 것만 알 수 있다. 이 명령어를 통해 나타나는 항목 CONSTRAINT_NAME는 이름을 부여하지 않으면 SYS_...로 표기된다. CONSTRAINT_TYPE은 제약 조건의 타입을 알 수 있다. P : 기본키, C : NOT NULL 등의 check 제약을 나타낸다.

 

SELECT * FROM USER_CONS_COLUMNS WHERE table_name= 'TEST1';

을 통해 제약조건이름과 컬럼명을 확인해보자.

SELECT * FROM USER_CONS_COLUMNS WHERE table_name= 'TEST1';

이 명어를 통해서는 제약조건이름(CONSTRAINT_NAME)과 컬럼명(COLUMN_NAME)을 확인할 수 있으나, 어떤 제약 조건인지는 확인이 불가능하다.

 

제약조건 및 컬럼을 확인하기 위해서는 어떻게 해야할까?

-- 제약조건 및 컬럼 확인
SELECT u1.table_name, column_name, constraint_type, u1.constraint_name, search_condition
FROM user_constraints u1
JOIN user_cons_columns u2 ON u1.constraint_name = u2.constraint_name
WHERE u1.table_name = UPPER('TEST1');

이 명령어를 통해 제약조건 및 컬럼을 확인할 수 있다. 이 명령어의 동작 원리는 다음시간에 자세히 알아보도록 하겠다.

 

컬럼 방식 기본키를 통해 테이블을 만들어 보자. ( 제약조건명 부여)

-- 컬럼 방식 기본키, 제약조건명 부여. 제약조건명은 중복적인 이름을 가질 수 없다.
CREATE TABLE test2 (
    id VARCHAR2(30) CONSTRAINT pk_test2_id PRIMARY KEY,
    pwd VARCHAR2(30) NOT NULL,
    name VARCHAR2(30) NOT NULL,
    birth DATE
);

SELECT * FROM USER_CONSTRAINTS WHERE table_name= 'TEST2';

id 에 PRIMARY KEY를 주고 제약조건명을 pk_test2_id 라고 주었다. 이러면 다음에 확인할 때 pk (PRIMARY KEY의 Acronym)이고 테이블명 컬럼을 주었다. 이것을 USER_CONSTRAINTS 명령어를 입력했을 때 CONSTRAINT_NAME에서 확인할 수 있어서 어떤 컬럼인지 원래는 알 수 없으나 이름을 주었기 때문에 컬럼명으로 유추할 수 있다.

 

테이블 레벨 방식으로 기본키를 부여해보자. 

-- 테이블 레벨 방식으로 기본키 부여(제약조건이름은 부여하지 않음)
CREATE TABLE test3 (
    id VARCHAR2(30),
    pwd VARCHAR2(30) NOT NULL,
    name VARCHAR2(30) NOT NULL,
    birth DATE,
    PRIMARY KEY( id )
);
SELECT * FROM USER_CONSTRAINTS WHERE table_name= 'TEST3';

-- 테이블 레벨 방식으로 기본키 부여(제약조건이름 부여)
CREATE TABLE test4 (
    id VARCHAR2(30),
    pwd VARCHAR2(30) NOT NULL,
    name VARCHAR2(30) NOT NULL,
    birth DATE,
    CONSTRAINT pk_test4_id PRIMARY KEY( id )
);
SELECT * FROM USER_CONSTRAINTS WHERE table_name= 'TEST4';

-- 두 개의 컬럼으로 기본키 부여
CREATE TABLE test5 (
    id VARCHAR2(30),
    code NUMBER,
    pwd VARCHAR2(30) NOT NULL,
    name VARCHAR2(30) NOT NULL,
    birth DATE,
    CONSTRAINT pk_test5_id PRIMARY KEY( id, code )
    -- 복합키로 기본키를 구성할 때는 꼭 이렇게 작성해야함
);

SELECT * FROM USER_CONSTRAINTS WHERE table_name= 'TEST5'; -- 확인용
SELECT * FROM USER_CONS_COLUMNS WHERE table_name= 'TEST5'; -- 확인용

마지막에 PRIMARY KEY(컬럼명)을 기술함으로서 id에 PRIMARY KEY를 부여해주었다.

두 개 이상의 컬럼으로 기본키를 부여할 때는 꼭 테이블 레벨 방식으로 기본키를 부여해야한다. 

확인용의 쿼리 결과

 

USER_CONSTRAINTS 명령어로는 제약조건을 확인할 수 있으나컬럼의 이름을 알 수 없다. 하지만 CONSTRAINT_NAME을 주어서 ID컬럼이 기본키임을 유추할 수 있다.USER_CON_COLUMNS명령어를 통해 컬럼의 이름을 확인 할 수 있다. code도 PRIMARY KEY를 구성하는 컬럼임을 유추할 수 있다.

 

MERGE를 통해 다른 테이블에 있는 것을 원하는 테이블에 넣어보자.

 

실습용 테이블 생성

CREATE TABLE emp1 AS
    SELECT empNo, name, city, dept, sal FROM emp WHERE city ='인천';

CREATE TABLE emp2 AS
    SELECT empNo, name, city, dept, sal FROM emp WHERE dept ='개발부';

SELECT * FROM emp1; 
SELECT * FROM emp2;

emp1 에는 인천사람들의 empNo, name, city, dept, sal을 뽑아서 만들고 emp2에는 개발부서 사람들의 empNo, name, city, dept, sal를 뽑아 새로운 테이블들을 만들어 주었다.

 

MERGE 

MERGE INTO emp1 e1
        USING emp2 e2
        ON (e1.empNo = e2.empNo)
        WHEN MATCHED THEN
            UPDATE SET e1.sal = e1.sal + e2.sal
        WHEN NOT MATCHED THEN
            INSERT (e1.empNo, e1.name, e1.city, e1.dept, e1.sal) 
            VALUES (e2.empNo, e2.name, e2.city, e2.dept, e2.sal);

SELECT * FROM emp1;
SELECT * FROM emp2;
COMMIT;

 

테이블명에 e1, e2로 별명을 주었다. e1과 e2테이블의 empNo이 같으면 sal을 더해서 넣고, 다르면 값들을 새로 넣어주었다. 확일을 해보면 emp1에 emp2의 데이터들이 들어간 것이고 emp2는 그대로 인 것을 볼 수 있다.

DELETE 는 자료를 삭제할 때 쓰는 명령어이다.

예시를 통해 알아보자.

-- emp_score1 테이블에서 empNo = '1001' 인 자료 삭제
DELETE FROM emp_score1 WHERE empNo = '1001';
SELECT * FROM emp_score1; -- 확인용
COMMIT;

DELETE FROM 테이블명 WHERE 조건; 조건에 해당하는 자료를 삭제한다.

 

실습용 테이블 생성

CREATE TABLE emp1 AS
    SELECT * FROM emp;

CREATE TABLE emp_score1 AS
    SELECT * FROM emp_score;

 

emp_score1 테이블에서 남자만 삭제해보자.

-- 남자만 뽑기
SELECT empNo FROM emp1 WHERE MOD( SUBSTR(rrn, 8, 1), 2) = 1;

DELETE FROM emp_score1 WHERE empNo IN
( SELECT empNo from emp1 WHERE MOD( SUBSTR(rrn, 8, 1), 2) = 1);
SELECT * FROM emp_score1; -- 확인용
COMMIT;

먼저 남자만 뽑고, emp_score1에서 삭제할 때는 IN 뒤에 남자만 뽑은 서브쿼리를 줘서 같은 값을 가지고 있는 empNo들을 삭제하도록 했다.

 

테이블의 모든 자료 삭제 시, 구조는 삭제가 되지 않는다.

DELETE와 TRUNCATE를 이용해 모든 레코드를 삭제할 수 있다.

차이

  • TRUNCATE는 DELETE보다 더 빠르다. 
  • TRUNCATE는 자동 COMMIT이 된다.
DELETE FROM emp1;
SELECT * FROM emp1; -- 다 삭제되었는지 확인용
COMMIT;
DESC emp1; -- 구조 확인

실습용 테이블 삭제

DROP TABLE emp_score1 PURGE;
DROP TABLE emp1 PURGE;

PURGE를 써야 휴지통에 담기지 않고 삭제된다.

PURGE 안 썼을 시 휴지통 비우기 >

 

DELETE를 이용해 실수로 삭제하거나 수정한 경우 복구 방법에 대해 알아보자.

단, 오래전에 삭제한 자료는 불가

DELETE FROM emp WHERE city = '서울';
COMMIT;
SELECT * FROM emp;

실수로 서울에 사는 사람들의 정보를 삭제했다.

-- 30분전의 emp 테이블
SELECT * FROM emp
AS OF TIMESTAMP ( SYSTIMESTAMP - INTERVAL '30' MINUTE);

30분전의 emp 테이블에는 서울에 사는 사람들도 있었기 때문에 30분 전으로 했다. ( 실수로 삭제한 시간보다 전이면 됨)

이 쿼리를 실행하면 삭제 전의 테이블이 나온다.

 

30분전의 emp 테이블로 삭제된 데이터를 복구시켜보자.

-- 30분전의 emp 테이블로 삭제된 데이터 복구
SELECT * FROM emp
AS OF TIMESTAMP ( SYSTIMESTAMP - INTERVAL '30' MINUTE)
WHERE city='서울';

INSERT INTO emp (
    SELECT * FROM emp
    AS OF TIMESTAMP ( SYSTIMESTAMP - INTERVAL '30' MINUTE)
    WHERE city='서울'
);

SELECT * FROM emp; -- 데이터가 잘 들어갔는지 확인용
COMMIT; -- 트랙잭션 완료

내가 삭제한 서울사람들만 추가할 필요가 있으므로 30분 전의 emp 테이블에서 내가 삭제한 자료들만 먼저 출력해보았다. 그 후 INSERT INTO 테이블 ( 값을 가진 컬럼들 ); 을 통해 다시 넣어서 복구시켜주었다.

+ Recent posts