실습을 위한 테이블을 만들어 보자.

CREATE TABLE test1 (
    id VARCHAR2(30) PRIMARY KEY, 
    name VARCHAR2(30) NOT NULL,
    city VARCHAR2(30) NOT NULL,
    memo VARCHAR2(100)
);

CREATE TABLE test2(
    num NUMBER PRIMARY KEY,
    id VARCHAR2(30)NOT NULL,
    score NUMBER(3) NOT NULL,
    FOREIGN KEY ( id ) REFERENCES test1 ( id )
    ON DELETE CASCADE
);
INSERT INTO test1(id, name, city, memo) VALUES ('a', '김자바', '서울', NULL);
INSERT INTO test1(id, name, city, memo) VALUES ('b', '오라클', '경기', NULL);
INSERT INTO test1(id, name, city, memo) VALUES ('c', '스프링', '인천', 'C/C++');
INSERT INTO test1(id, name, city, memo) VALUES ('d', '이기자', '서울', NULL);

INSERT INTO test2 (num, id, score) VALUES (1,'a',80);
INSERT INTO test2 (num, id, score) VALUES (2,'b',70);
INSERT INTO test2 (num, id, score) VALUES (3,'c',90);
INSERT INTO test2 (num, id, score) VALUES (4,'d',85);
COMMIT;

-- 테이블 test1과 test2를 조인해서 뷰를 만들어보자.

CREATE OR REPLACE VIEW testView1
AS
SELECT t1.id, num, name, memo, score
FROM test1 t1
JOIN test2 t2 ON t1.id = t2.id;

-- test1 테이블을 이용하여 작성

CREATE OR REPLACE VIEW testView2
AS
SELECT id, name, city
FROM test1;

-- 뷰가 만들었는지 확인

SELECT * FROM tab;

테이블이 많아서 이것저것 많이나오길래 뷰테이블만 뽑았습니다.

지금 만들어진 뷰 중에 testView1은 두 개의 테이블을 JOIN해서 만든 뷰이다.

 

-- 뷰에 데이터 추가

이 경우에, 뷰를 통한 데이터 추가는 에러가 발생한다.

INSERT INTO testView1(id, num, name, city, memo, score)
    VALUES ('e', 5, '너자바', '부산', null, 80);

단순 뷰(하나의 테이블로 만들어진 뷰)가 아닌 경우 데이터 추가가 불가능하다.

반면, 하나의 테이블로 이루어진 뷰는 데이터 추가가 가능하다.

INSERT INTO testView2(id, name, city) VALUES('e', '너자바', '부산');

e, 너자바, 부산이 추가되었음을 확인할 수 있다.

단순뷰는 제약 조건을 위반하지 않으면 추가가 가능하다. 위 테이블의 경우 id, name, city가 모두 NOT NULL이므로 값이 다 입력되어야 하는 제약조건이 있다. INSERT할 때 제약조건을 만족했으므로 데이터의 추가가 잘 되었다.

INSERT INTO testView2(id, name) VALUES ('f', '안된다');

위 경우는 city가 NULL이 될 수 없기 때문에 에러가 발생한다.

 

-- 뷰를 이용한 데이터 수정

하나의 테이블로 만들어졌던 뷰 testView2는 데이터 수정이 가능하다.

UPDATE testView2 SET name='가나다' WHERE id = 'a';

하지만 테이블 2개를 JOIN했던 testView1은 수정이 불가능하다.

 

-- WITH CHECK OPTION 

 

-- testView2 뷰를 city가 서울인 자료만 갖는 뷰로 수정해보자.

CREATE OR REPLACE VIEW testView2
AS
SELECT id, name, city
FROM test1
WHERE city = '서울';

-- testView2 뷰를 이용하여 id='a'의 city를 '제주'로 변경해보자.

UPDATE testView2 SET city = '제주' WHERE id = 'a';

SELECT * FROM testView2;
SELECT * FROM test1;

ID가 a인 사람의 city가 제주로 변경되었음을 확인할 수 있다.

 

-- testView2 뷰에 WITH CHECK OPTION을 부여해서 서울이 아닌 값으로 수정하지 못하도록 설정

CREATE OR REPLACE VIEW testView2
AS
SELECT id, name, city
FROM test1
WHERE city = '서울'
WITH CHECK OPTION;

UPDATE testView2 SET city = '제주' WHERE id = 'd';

WITH CHECK OPTION이 '서울'인 것을 바꾸지 못하게 설정해놨으므로 city를 바꿀 수 없다. 

UPDATE testView2 SET name = '제주도안가' WHERE id = 'd';

하지만 WITH CHECK OPTION이 걸려있지 않은 name항목은 수정이 가능했다.

 

뷰 삭제

DROP VIEW testView1;
DROP VIEW testView2;

 

뷰 ?

사용자에게 접근이 허용된 자료만을 제한적으로 보여주기 위해 하나 이상의 기본 테이블 또는 뷰를 기반으로 만들어진 가상 테이블(논리 테이블)이다.

뷰는 데이터를 가지고 있지 않으며, SQL만 저장되어 있다.

 

뷰 생성 및 삭제

뷰를 생성하거나 삭제하기 위해서는 사용자는 시스템 권한 중의 하나인 CREATE VIEW 권한을 가져야 한다.

- 사용자의 시스템 권한(privilege) 확인

SELECT * FROM user_sys_privs;

- 사용자에게 CREATE VIEW 권한 설정 (system 또는 sys 계정에서 실시)

GRANT CREATE VIEW TO 사용자;

 

여태껏 판매현황을 출력할 때 마다 JOIN을 이용해서 계속 테이블을 했어야 했는데 이것을 VIEW 뷰로 만들면 뷰만 출력하면 된다. 

 

-- 뷰 만들기

CREATE VIEW panmai
AS 
SELECT b.bCode, bName, bPrice, b.pNum, pName, sDate, s.cNum, cName, qty
FROM book b 
JOIN pub p ON b.pNum = p.pNum
JOIN dsale d ON b.bCode = d.bCode
JOIN sale s ON d.sNum = s.sNum
JOIN cus c ON s.cNum = c.cNum;

-- 뷰 SELECT

SELECT * FROM panmai;

이제 판매내역을 위해 일일히 다 JOIN할 필요 없이 뷰 테이블을 선택하면 된다.

 

-- 뷰 수정

CREATE OR REPLACE VIEW panmai
AS 
SELECT b.bCode, bName, bPrice, b.pNum, pName, sDate, s.cNum, cName, qty, qty*bPrice amt
FROM book b 
JOIN pub p ON b.pNum = p.pNum
JOIN dsale d ON b.bCode = d.bCode
JOIN sale s ON d.sNum = s.sNum
JOIN cus c ON s.cNum = c.cNum;

수정 시에는 CREATE OR REPLACE VIEW를 붙여서 없으면 만들고 있으면 수정한다는 명령어를 붙이면 된다.

수정된 결과가 잘 나왔다.

 

-- 뷰 컬럼 확인

SELECT * FROM col WHERE tname = 'PANMAI';
DESC panmai;

일반 테이블의 컬럼을 확인할때와 마찬가지의 방식이다.

 

-- 뷰 소스 확인

뷰는 SQL만 저장되어있는 것이라고 위에 기술했는데, 그렇다면 그 SQL을 보기 위해서 어떻게 해야할까?

SELECT * FROM user_views;

이 쿼리를 통해 계정이 가지고 있는 뷰를 볼 수 있다. 거기서 TEXT 칼럼을 통해 확인할 수 있다.

뷰는 PURGE 없이 삭제해도 휴지통에 가지않고 바로 삭제된다.

DROP VIEW 뷰테이블명;

 

-- 테이블에 컬럼 추가

ALTER TABLE 테이블명 ADD (컬럼명 자료형(크기));

-- 테이블 컬럼폭 또는 크기 변경

ALTER TABLE 테이블명 MODIFY (컬럼명 자료형(크기));

-- 테이블 컬럼명 변경

 ALTER TABLE 테이블명 RENAME COLUMN 컬럼명 TO 새로운컬럼명

-- 테이블 컬럼 삭제

ALTER TABLE 테이블명 DROP COLUMN 컬럼명

-- ORACLE 11g 에서 페이징 처리 방법(3페이지, 20개 건너뛰고 10개 가져오기)

SELECT * FROM (
    SELECT ROWNUM rnum, tb.* FROM (
        SELECT num, name, subject, hitCount, reg_date
        FROM bbs
        [ WHERE 조건 ]
        ORDER BY num DESC
    ) tb WHERE ROWNUM <= 30
) WHERE rnum >=21;

 

-- ORACLE 12c 이상에서 페이징 처리 방법(3페이지, 20개 건너뛰고 10개 가져오기)

SELECT num, name, subject, hitCount, reg_date
FROM bbs
[ WHERE 조건 ]
ORDER BY num DESC
OFFSET 20 ROWS FETCH FIRST 10 ROWS ONLY;

    VARCHAR2(길이) : 가변 길이 문자 데이터를 저장. 최대 길이는 4000자. 반드시 길이를 명시
    NCHAR, NVARCHAR2 : 다양한 언어의 문자 값(국가별 언어 집합 : NLS)을 저장하고 조회 가능
    NUMBER : 숫자 - NUMBER(P, S)
        P(1~38)는 정밀도로 전체 자리수를 나타내며 기본 값이 38이고 S(-84~127)는 소수점 이하의 자릿수이다.
    DATE : 날짜, 『년/월/일 시:분:초』
    TIMESTAMP : 날짜, 『년/월/일 시:분:초.밀리초』
    CLOB : 대용량의 문자 저장

 

 

-- 예제

 

-- 비회원 판매 현황 : cNum, cName, bCode, bName, sDate, bPrice, qty

단, 고객중 책을 한권도 구매하지 않은 고객도 출력

book(bCode, bPrice)

dsale(sNum, bCode, qty)

sale(sNum, cNum)

cus(cNum, cName)

SELECT c.cNum, cName, d.bCode, bName, sDate, bPrice, qty
FROM book b
JOIN dsale d ON b.bCode = d.bCode
JOIN sale s ON d.sNum = s.sNum
RIGHT OUTER JOIN cus c ON c.cNum = s.cNum;

답 >>

더보기
SELECT c.cNum, cName, NVL(SUM(bPrice * qty), 0) 합
FROM cus c
LEFT OUTER JOIN sale s ON c.cNum = s.cNum
LEFT OUTER JOIN dsale d ON s.sNum = d.sNum
LEFT OUTER JOIN book b ON d.bCode = b.bCode
GROUP BY c.cNum, cName
ORDER BY c.cNum;
     
SELECT c.cNum, cName, NVL(SUM(bPrice * qty), 0) 합
FROM book b
JOIN dsale d ON b.bCode =d.bCode
JOIN sale s ON d.sNum = s.sNum
RIGHT OUTER JOIN cus c ON s.cNum = c.cNum
GROUP BY c.cNum, cName
ORDER BY c.cNum;

 

 

-- 년도별 고객 누적 판매금액이 가장 많은 값 출력

-- 판매년도, cNum, cName, bPrice* qty 합

-- 년도 오름차순으로

SELECT c.cNum, cName, SUM(bPrice * qty) 합
FROM book b
JOIN dsale d ON b.bCode = d.bCode
JOIN sale s ON d.sNum = s.sNum
JOIN cus c ON c.cNum = s.cNum
GROUP BY s.cNum, cName;

이 쿼리로 실행해봐도 자꾸 오류가 발생해서 

비교해보았더니 GROUP BY할때 s.cNum으로 GROUP 화 한거면 SELECT 에도 c.cNum이 아니라 s.cNum으로 써줘야 한다.

SELECT TO_CHAR(sdate, 'YYYY') 년도, s.cNum, cName, SUM(bPrice * qty) 합, 
    RANK() OVER(PARTITION BY TO_CHAR(sdate, 'YYYY') ORDER BY SUM(bPrice * qty) DESC) 순위
FROM book b
JOIN dsale d ON b.bCode = d.bCode
JOIN sale s ON d.sNum = s.sNum
JOIN cus c ON c.cNum = s.cNum
GROUP BY s.cNum, cName, TO_CHAR(sdate, 'YYYY');

여기서 년도별로 1등만 출력하면 된다.

WITH sb AS (
SELECT TO_CHAR(sdate, 'YYYY') 년도, s.cNum, cName, SUM(bPrice * qty) 합, 
    RANK() OVER(PARTITION BY TO_CHAR(sdate, 'YYYY') ORDER BY SUM(bPrice * qty) DESC) 순위
FROM book b
JOIN dsale d ON b.bCode = d.bCode
JOIN sale s ON d.sNum = s.sNum
JOIN cus c ON c.cNum = s.cNum
GROUP BY s.cNum, cName, TO_CHAR(sdate, 'YYYY')
)
SELECT 년도, cNum, cName, 합 FROM sb
WHERE 순위 = 1;

-- 년도의 월별 서적의 판매 수량의 합 구하기 : 년도 오름차순, 책코드 오름차순

-- 년도 책코드 책이름 1월 2월 3월 ... 12월

SELECT TO_CHAR(sDate, 'YYYY')년도, TO_CHAR(sDate, 'MM') 월, b.bCode 책코드, bName 책이름, qty 판매수량
FROM book b
JOIN dsale d ON b.bCode = d.bCode
JOIN sale s ON d.sNum = s.sNum
GROUP BY b.bCode, bName, TO_CHAR(sDate, 'YYYY'), TO_CHAR(sDate, 'MM'), qty
ORDER BY 년도, 책코드;

일단 년도, 월 별로 몇개가 판매되었는지 구해보았다.

 

답 확인 >>

더보기
SELECT TO_CHAR(sDate, 'YYYY') 년도, b.bCode, bName, qty
FROM  book b
JOIN  dsale d ON  b.bCode = d.bCode
JOIN  sale s  ON  d.sNum = s.sNum;

SELECT TO_CHAR(sDate, 'YYYY') 년도, b.bCode, bName, 
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '01', qty)),0) M01,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '02', qty)),0) M02,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '03', qty)),0) M03,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '04', qty)),0) M04,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '05', qty)),0) M05,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '06', qty)),0) M06,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '07', qty)),0) M07,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '08', qty)),0) M08,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '09', qty)),0) M09,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '10', qty)),0) M10,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '11', qty)),0) M11,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '12', qty)),0) M12
FROM  book b
JOIN  dsale d ON  b.bCode = d.bCode
JOIN  sale s  ON  d.sNum = s.sNum
GROUP BY TO_CHAR(sDate, 'YYYY'), b.bCode, bName
ORDER BY 년도, bCode;

-- 년도별 소계
SELECT TO_CHAR(sDate, 'YYYY') 년도, b.bCode, bName, 
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '01', qty)),0) M01,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '02', qty)),0) M02,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '03', qty)),0) M03,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '04', qty)),0) M04,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '05', qty)),0) M05,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '06', qty)),0) M06,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '07', qty)),0) M07,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '08', qty)),0) M08,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '09', qty)),0) M09,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '10', qty)),0) M10,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '11', qty)),0) M11,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '12', qty)),0) M12
FROM  book b
JOIN  dsale d ON  b.bCode = d.bCode
JOIN  sale s  ON  d.sNum = s.sNum
GROUP BY TO_CHAR(sDate, 'YYYY'), ROLLUP((b.bCode, bName))
ORDER BY 년도, bCode;

-- 년도별 소계, 총계
SELECT TO_CHAR(sDate, 'YYYY') 년도, b.bCode, bName, 
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '01', qty)),0) M01,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '02', qty)),0) M02,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '03', qty)),0) M03,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '04', qty)),0) M04,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '05', qty)),0) M05,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '06', qty)),0) M06,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '07', qty)),0) M07,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '08', qty)),0) M08,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '09', qty)),0) M09,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '10', qty)),0) M10,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '11', qty)),0) M11,
	NVL(SUM(DECODE(TO_CHAR(sDate, 'MM'), '12', qty)),0) M12
FROM  book b
JOIN  dsale d ON  b.bCode = d.bCode
JOIN  sale s  ON  d.sNum = s.sNum
GROUP BY ROLLUP(TO_CHAR(sDate, 'YYYY'), (b.bCode, bName))
ORDER BY 년도, bCode;

 

예제를 통해 알아보자.

 

예제를 위한 테이블 생성 및 자료 삽입

CREATE TABLE grade1 
(
    grade VARCHAR2(10) PRIMARY KEY,
    score NUMBER(3)
);
INSERT INTO grade1 ( grade, score ) VALUES ('A', 90);
INSERT INTO grade1 ( grade, score ) VALUES ('B', 80);
INSERT INTO grade1 ( grade, score ) VALUES ('C', 70);
INSERT INTO grade1 ( grade, score ) VALUES ('D', 60);
INSERT INTO grade1 ( grade, score ) VALUES ('F', 0);
COMMIT;

CREATE TABLE score1
(
    hak VARCHAR2(10) PRIMARY KEY,
    score NUMBER(3)
);

INSERT INTO score1 ( hak, score ) VALUES('1', 75);
INSERT INTO score1 ( hak, score ) VALUES('2', 50);
INSERT INTO score1 ( hak, score ) VALUES('3', 90);
INSERT INTO score1 ( hak, score ) VALUES('4', 80);
INSERT INTO score1 ( hak, score ) VALUES('5', 65);
COMMIT;
hak score gscore
1 75 C
2 50 F
3 90 A
4 80 B
5 65 D

위의 테이블형태로 Oracle에서 출력하고 싶다.

 

먼저

hak score gscore
1 75 70
2 50 0
3 90 90
4 80 80
5 65 60

위 형태를 출력해보자.

SELECT hak, score, 
(SELECT MAX(score) FROM grade1 WHERE score <= score1.score) gscore
FROM score1;

SELECT hak, s1.score, grade FROM (
    SELECT hak, score, 
        (SELECT MAX(score) FROM grade1 WHERE score <= score1.score) gscore
    FROM score1
) s1
JOIN grade1 s2 ON s1.gscore = s2.score
ORDER BY hak;

 

ANY : 하나라도 참이면 참

-- 200만원보다 큰 값
SELECT empNo, name, sal
FROM emp
WHERE sal > ANY (2000000, 3000000, 4000000);

뒤에 자료는 생략하겠다.

ALL : 모두 만족해야 참

-- 400만원 보다 sal이 큰 사람
SELECT empNo, name, sal
FROM emp
WHERE sal > ALL (2000000, 3000000, 4000000);

EXISTS : 하나 이상의 레코드가 존재하면 참 

SELECT bName FROM book
WHERE EXISTS (SELECT * FROM dsale WHERE qty >=10);

qty가 >= 10 인 레코드가 존재하므로 모든 bName이 나온다.

SELECT bName FROM book;

결국 위 쿼리와 같은 의미가 된다.

 

+ Recent posts