본문 바로가기

Web Development/SIST

[Oracle|PL/SQL|Java] 5주차 수업: 2024.03.18 - 2024.03.22

2024.03.18

*인덱스

-인덱스는 데이터 검색을 빨리 하기 위해 사용

-데이터의 양이 별로 없거나 데이터값이 몇 종류 안 되어 선택도가 높으면 인덱스가 없는 것이 더 빠를 수 있음

-테이블의 컬럼에 대한 제약 조건을 설정할 때 PRIMARY KEY나 UNIQUE로 설정하면

 오라클은 자동으로 이 컬럼에 대해 UNIQUE INDEX를 설정

 

-인덱스 생성이 필요한 경우

- 열에 광범위한 값이 포함된 경우
- 열에 널 값이 많이 포함된 경우
- WHERE 절 또는 조인 조건에서 하나 이상의 열이 함께 자주 사용되는 경우

-인덱스를 생성하지 않아야 할 경우

- 테이블이 작은 경우
- 열이 질의의 조건으로 자주 사용되지 않는 경우
- 테이블이 자주 갱신되는 경우

-유일한 값을 가지는 컬럼에 인덱스 설정: UNIQUE INDEX

-UNIQUE 제약 조건을 부여하면 자동적으로 설정되므로 실제로 잘 사용하지 않음

CREATE UNIQUE INDEX dname_idx ON dept(dname);

 

*동의어

-동의어(객체의 다른 이름)를 생성하여 객체 액세스를 단순화

CREATE SYNONYM emp20 FOR emp20_view;

 

*관리자 계정

-오라클 데이터베이스 생성시 관리자 계정인 SYS와 SYSTEM 계정이 자동으로 생성됨

-SYS 계정은 데이터베이스에서 발생하는 모든 문제를 처리할 수 있는 권한을 가짐.

-SYSTEM 계정은 오라클 데이터베이스를 유지, 보수할 때 사용하는 계정.  SYS와 달리 데이터베이스를 생성할 권한을 가지지 않음

 

*사용자 계정

-사용자 계정은 데이터베이스에 접근하여 데이터를 조작(삽입, 삭제, 수정, 검색)하고 관리하는 계정

 

*사용자 생성

[오라클 11버전]

Run SQL Command Line 선택

SQL>conn sys/1234(비밀번호) as sysdba;

SQL>create user user002 identified by 1234(비밀번호); ← 계정 생성

SQL>GRANT resource,connect TO user002; ← 접속 권한 부여

 

*DBA

-DBA는 사용자에게 권한을 부여할 수 있음

-롤은 사용자에게 부여할 수 있는 관련 권한을 하나로 묶어 명명한 그룹으로서

 롤을 사용하면 권한 취소 및 유지 관리를 쉽게 수행할 수 있음

 

 

 

[PL/SQL]

 

*PL/SQL?

-'Procedural language extension to Structured Query Language(SQL)’의 약자

-SQL을 확장한 순차적 처리 언어

-데이터베이스 질의어인 SQL과 일반 프로그래밍 언어의 특성을 결합한 언어

-PL/SQL을 사용하면 조건문이나 반복문, 변수나 상수를 선언해서 사용할 수 있을 뿐만 아니라 SQL도 사용할 수 있음

 

*PL/SQL의 기본 구조

-PL/SQL의 기본 단위는 블록(block)
  1) 선언부(declarative part) : 사용할 변수나 상수를 선언(선언부에만 변수와 상수 선언 가능)
  2) 실행부(executable part) : 실제 처리할 로직을 담당하는 부분 
  3) 예외처리부(exception-building part) : 실행부에서 로직을 처리하던 중 발생할 수 있는 각종 오류들에 대해 처리

     프로시저를 만들 때는 의무적으로 들어감

 

-익명블록으로 DBMS 출력창에 Hello World 출력하기

실행부만 명시한 형태
BEGIN
    DBMS_OUTPUT.PUT_LINE('Hello World!');
END;
변수 사용
DECLARE
--변수를 선언할 수 있는 선언부
     message VARCHAR2(100);
BEGIN
--실행부에 사용할 변수는 선언부에서 미리 선언되어야 함
    message := 'Hello World!!!';
    DBMS_OUTPUT.PUT_LINE(message);
END;

 

SQL에서 =는 비교연산자, 대입연산자는 :=임

변수 선언과 초기화를 동시에
DECLARE
    message VARCHAR2(100) := 'Hell o world?';
BEGIN
    DBMS_OUTPUT.PUT_LINE(message);
END;

 

-선언부, 실행부, 예외처리부

DECLARE
    counter INTEGER;
BEGIN
    counter := counter + 1;
    IF counter IS NULL THEN 
        DBMS_OUTPUT.PUT_LINE('result: counter is null');
    END IF;
    DBMS_OUTPUT.PUT_LINE(counter);
END;

 

※INTEGER는 PL/SQL에서 별도로 사용 가능한 자료형

※변수 선언 후 초기화를 해주지 않으면 NULL이 됨. (NULL일 때는 출력이 안됨)

DECLARE
    --변수 선언
    counter INTEGER;
    i INTEGER;
BEGIN
    FOR i IN 1..9 LOOP
        counter := 2 * i;
        DBMS_OUTPUT.PUT_LINE('2 * ' || i || ' = ' || counter);
    END LOOP;
END;

※for문 안에서 i가 초기화 됨

 

-예외처리부

EXCEPTION WHEN 예외1 THEN 예외처리1
                         WHEN 예외2 THEN 예외처리2
                         WHEN OTHERS THEN 예외처리

DECLARE
    --변수 선언
    counter INTEGER;
BEGIN
    --변수 초기화
    counter := 10;
    counter := counter/0;
    EXCEPTION WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('errors');
END;

 

*상수

nYear CONSTANT INTEGER:=30; --(O)
nYear CONSTANT INTEGER; --(X)

 

*%TYPE

-참조할 테이블에 있는 컬럼의 데이터 타입을 자동으로 가져옴

nSal emp.sal%TYPE;

 

*%ROWTYPE

-%TYPE은 하나의 값에 대해 적용되지만, %ROWTYPE은 하나 이상의 값에 적용됨

 

콜렉션

*VARRAY
-VARRAY는 VARIABLE ARRAY의 약자로 고정 길이(Fixed Number)를 가진 배열 

-변수 배열 타입을 먼저 생성해줘야 함

DECLARE
    TYPE VARRAY_TEST IS VARRAY(3) OF INTEGER;
    varray1 VARRAY_TEST; --위에서 선언한 VARRAY_TEST 타입을 사용하는 변수
BEGIN
    varray1 := VARRAY_TEST(10, 20, 30);
    
    DBMS_OUTPUT.PUT_LINE(varray1(1)); --인덱스는 1번부터 시작
    DBMS_OUTPUT.PUT_LINE(varray1(2));
    DBMS_OUTPUT.PUT_LINE(varray1(3));
END;

 

*중첩테이블

-VARRAY와 흡사하지만 중첩 테이블은 선언 시에 전체 크기를 명시할 필요가 없음

DECLARE
    TYPE NESTED_TEST IS TABLE OF VARCHAR2(10);
    nested1 NESTED_TEST;
BEGIN
    nested1 := NESTED_TEST('A','B','C','D');
    
    DBMS_OUTPUT.PUT_LINE(nested1(1));
    DBMS_OUTPUT.PUT_LINE(nested1(2));
    DBMS_OUTPUT.PUT_LINE(nested1(3));
    DBMS_OUTPUT.PUT_LINE(nested1(4));
END;

 

*ASSOCIATIVE ARRAY (Index-by Table): 연관배열
-실제로는 배열이 아닌 맵 형태로 키와 값의 쌍으로 구성된 콜렉션

-하나의 키는 하나의 값과 연관

DECLARE
    TYPE ASSOC_ARRAY_NUM_TYPE IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
    --NUMBER는 value의 타입, PLS_INTEGER는 key의 타입
    assoc1 ASSOC_ARRAY_NUM_TYPE;
BEGIN
    --키와 값 저장
    --키는 3, 값은 33
    assoc1(3) := 33; 
    
    DBMS_OUTPUT.PUT_LINE(assoc1(3));
END;

 

*레코드

-데이터를 테이블에 적용하기 쉽도록 고안된 구조

-테이블의 컬럼들이 서로 다른 유형의 데이터 타입으로 구성되듯이

 레코드는 각각의 필드(레코드에서는 요소란 말 대신 필드란 용어를 사용)들이 각기 다른 데이터 타입을 가질 수 있음

DECLARE
    --TYPE으로 선언한 레코드
    TYPE RECORD1 IS RECORD(deptno NUMBER NOT NULL := 50,
                           dname VARCHAR2(14),
                           loc VARCHAR2(13));
    --위에 선언한 RECORD1을 받은 변수 선언
    rec1 RECORD1;
BEGIN
    --RECORD1 타입의 rec1의 dname 필드에 값 할당
     rec1.dname := 'RECORD';
     rec1.loc := 'SEOUL';
     
     --rec1 레코드 값을 dept 테이블에 insert
    INSERT INTO dept VALUES rec1;
    COMMIT;
    
    EXCEPTION WHEN OTHERS THEN
        ROLLBACK;
END;

 

 

PL/SQL 문장과 커서

*IF문

DECLARE
    grade CHAR(1);
BEGIN
    --변수의 초기화
    grade := 'B';
    
    IF grade = 'A' THEN
        DBMS_OUTPUT.PUT_LINE('Excellent');
    ELSIF grade = 'B' THEN
        DBMS_OUTPUT.PUT_LINE('Good');
    ELSIF grade = 'C' THEN
        DBMS_OUTPUT.PUT_LINE('Fair');
    ELSIF grade = 'D' THEN
        DBMS_OUTPUT.PUT_LINE('Poor');
    END IF;
END;

 

*CASE문

DECLARE
    grade CHAR(1);
BEGIN
    grade := 'C';
    
    CASE grade 
    WHEN 'A' THEN
        DBMS_OUTPUT.PUT_LINE('Excellent');
    WHEN 'B' THEN
        DBMS_OUTPUT.PUT_LINE('Good');
    WHEN 'C' THEN
        DBMS_OUTPUT.PUT_LINE('Fair');
    WHEN 'D' THEN
        DBMS_OUTPUT.PUT_LINE('Poor');
    ELSE
        DBMS_OUTPUT.PUT_LINE('Not Found');
    END CASE;
END;

 

*LOOP문

-특정 조건에 도달하기 전까지 무한반복

 

-IF문으로 조건체크

DECLARE
    --변수 선언
    test_number INTEGER;
    result_number INTEGER;
BEGIN
    test_number := 1;
    
    LOOP
        result_number := 2 * test_number; 
        IF result_number > 20 THEN
            EXIT;
        ELSE
            DBMS_OUTPUT.PUT_LINE(result_number);
        END IF;
        test_number := test_number + 1;
    END LOOP;
    --loop 블럭을 빠져나오면 아래 코드를 실행함
    DBMS_OUTPUT.PUT_LINE('프로그램 끝');
END;

 

-EXIT WHEN 형태로 조건체크

DECLARE
    test_number INTEGER;
    result_num INTEGER;
BEGIN
    test_number := 1;
    LOOP 
        result_num := 2 * test_number;
        
        EXIT WHEN result_num > 20;
        
        DBMS_OUTPUT.PUT_LINE(result_num);
        test_number := test_number + 1;
    END LOOP;
END;

 

*WHILE-LOOP문

DECLARE
    test_number INTEGER;
    result_num INTEGER;
BEGIN
    test_number := 1;
    result_num := 0;
    
    WHILE result_num < 20 LOOP --여기서 result_num 값을 사용해야 하기 때문에 미리 초기화
        result_num := test_number * 2;
        DBMS_OUTPUT.PUT_LINE(result_num);
        test_number := test_number + 1;
    END LOOP;
END;

 

*FOR-LOOP문

DECLARE
    test_number INTEGER;
    result_num INTEGER;
BEGIN
    FOR test_number IN 1..10 LOOP
        result_num := test_number * 2;
        DBMS_OUTPUT.PUT_LINE(result_num);
    END LOOP;
END;

 

*FOR-LOOP문 REVERSE

DECLARE
    test_number INTEGER;
    result_num INTEGER;
BEGIN
    FOR test_number IN REVERSE 1..10 LOOP
        result_num := test_number * 2;
        DBMS_OUTPUT.PUT_LINE(result_num);
    END LOOP;
END;

 

*커서(CURSOR)

-쿼리에 의해 반환되는 결과는 메모리 상에 위치하게 되는데

 PL/SQL에서는 바로 커서(CURSOR)를 사용하여 이 결과집합에 접근할 수 있음

DECLARE
        --커서 선언: 커서에 이름을 주고, 이 커서가 접근하려는 쿼리를 정의 
        CURSOR emp_crs IS
        SELECT empno
        FROM emp
        WHERE deptno=10;
        
        emp_no emp.empno%TYPE;
BEGIN
    --커서 열기: 커서로 정의된 쿼리를 실행하는 역할
    OPEN emp_crs;
    LOOP
        FETCH emp_crs INTO emp_no;
        --%NOTFOUND 커서에서만 사용 가능한 속성. 더 이상 패치(할당)할 로우가 없음을 의미
        EXIT WHEN emp_crs%NOTFOUND;
        DBMS_OUTPUT.PUT_LINE(emp_no);
    END LOOP;
    
    --커서 닫기: 패치 작업이 끝나면 사용된 커서를 닫는다
    CLOSE emp_crs;
END;

 

*커서의 속성

속성 정의
%FOUND PL/SQL 코드가 마지막으로 얻은 커서의 결과 SET에 레코드가 있다면 참
%NOTFOUND %FOUND 연산자와 반대의 의미
%ROWCOUNT 커서에서 얻은 레코드의 수를 반환
%ISOPEN 커서가 열렸고, 아직 닫히지 않은 상태라면 참

 

*PL/SQL 서브프로그램

-PL/SQL 서브프로그램은 파라미터와 고유의 이름을 가진 PL/SQL 블록

-데이터베이스 객체로 존재(저장되어 필요할 때마다 호출 가능)

-서브프로그램에는 내장 프로시저(stored procedure)와 함수(function)가 있음

 

*함수

-함수는 반드시 반환하는 데이터가 있어야 함

-반환 데이터가 없는 것은 프로시저

 

-함수 구문

CREATE OR REPLACE FUNCTION 함수명(파라미터1 데이터타입,
                                  파라미터2 데이터타입,
                                  …)
   RETURN 데이터타입 IS [AS]
     변수 선언…;
BEGIN
  처리내용…;
  RETURN 리턴값;
END;

 

-입력받은 값으로부터 10%의 세율을 얻는 함수

CREATE OR REPLACE FUNCTION tax(p_value IN NUMBER) --IN은 생략 가능
    RETURN NUMBER IS
BEGIN
    RETURN p_value * 0.1;
END;

 

-함수의 호출

SELECT TAX(100) FROM dual;
SELECT empno, ename, sal, TAX(sal) tax FROM emp;

 

-급여와 커미션을 합쳐서 세금 계산

CREATE OR REPLACE FUNCTION tax2(p_sal IN emp.sal%TYPE, p_comm emp.comm%TYPE)
    RETURN NUMBER IS
BEGIN
    RETURN (p_sal+NVL(p_comm, 0)) * 0.1;
END;

 

-조건체크가 있는 함수

CREATE OR REPLACE FUNCTION tax3(p_sal IN emp.sal%TYPE, p_comm IN emp.comm%TYPE)
    RETURN NUMBER IS
    l_tax NUMBER;
    l_sum NUMBER := p_sal + NVL(p_comm, 0);
BEGIN   
    IF l_sum < 1000 THEN
        l_tax := l_sum * 0.05;
    ELSIF l_sum BETWEEN 1000 AND 2000 THEN
        l_tax := l_sum * 0.1;
    ELSE
        l_tax := l_sum * 0.2;
    END IF;
    RETURN l_tax;
END;

 

-사원번호를 통해서 급여를 알려주는 함수

CREATE OR REPLACE FUNCTION getsal(emp_no IN emp.empno%TYPE)
    RETURN NUMBER IS
    nSalaries NUMBER(9);
BEGIN
    SELECT sal 
    --결과행이 단일행일 경우 커서를 사용하지 않고 INTO를 사용해 읽어온 값을 변수에 담을 수 있음
    INTO nSalaries 
    FROM emp 
    WHERE empno = emp_no; 
    RETURN nSalaries;
END;


--함수 호출
SELECT GETSAL(7839) FROM dual;

※결과행이 단일행일 경우 커서를 사용하지 않고 INTO를 사용해 읽어온 값을 변수에 담을 수 있음

※인자를 emp 테이블에서 읽어오는 것이 아니기 때문에 FROM dual 이라고 작성

 

 

2024.03.19

[실습 문제]

3.emp테이블의 입사일을 입력하면 근무연차를 구하는 함수를 정의하시오. (소수점 절삭, get_info_hiredate)

[IN CLASS]
CREATE OR REPLACE FUNCTION get_info_hiredate(p_hiredate emp.hiredate%TYPE)
    RETURN NUMBER IS
BEGIN
    RETURN TRUNC(MONTHS_BETWEEN(SYSDATE, p_hiredate)/12);
END;

-쿼리에 의해서 반환되는 결과집합을 구하는 것이 아니라 단순히 입력값에 따라 계산된 결과값을 구하는 것이므로 커서를 사용할 필요가 없음.

 

4.emp테이블을 이용해서 사원번호를 입력하면 해당 사원의 관리자 이름을 구하는 함수를 정의하시오. (get_mgr_name)

-서브쿼리 방식

[IN-CLASS] --서브쿼리
CREATE OR REPLACE FUNCTION get_mgr_name(emp_no emp.empno%TYPE)
    RETURN VARCHAR2 IS
    m_name VARCHAR2(10);
BEGIN
    SELECT ename
    INTO m_name
    FROM emp
    WHERE empno = (SELECT mgr FROM emp 
                   WHERE empno=emp_no);
    RETURN m_name;
END;

 

-조인 방식

[IN-CLASS] --조인 (내 답변과 동일)
CREATE OR REPLACE FUNCTION get_mgr_name(emp_no emp.empno%TYPE)
    RETURN VARCHAR2 IS
    m_name VARCHAR2(10);
BEGIN
    SELECT m.ename
    INTO m_name
    FROM emp e, emp m
    WHERE e.mgr=m.empno
    AND e.empno=emp_no;
    RETURN m_name;
END;

 

5.emp테이블을 이용해서 사원번호를 입력하면 급여 등급을 구하는 함수를 정의하시오. (get_sal_grade)

-CASE문 (직접 급여등급 만들기)

[IN-CLASS] --CASE문 (직접 급여등급 만들기)
CREATE OR REPLACE FUNCTION get_sal_grade(emp_no emp.empno%TYPE)
    RETURN CHAR IS
    sgrade CHAR(1);
BEGIN
    SELECT CASE WHEN sal >= 4000 THEN 'A'
                WHEN sal >= 3000 AND sal < 4000 THEN 'B'
                WHEN sal >= 2000 AND sal < 3000 THEN 'C'
                WHEN sal >= 1000 AND sal < 2000 THEN 'D'
                ELSE 'F'
           END grade
    INTO sgrade 
    FROM emp
    WHERE empno=emp_no;
    
    RETURN sgrade;
END;

 

-조인 (내 답변과 동일)

[IN-CLASS] --조인 (내 답변과 동일)
CREATE OR REPLACE FUNCTION get_sal_grade(emp_no emp.empno%TYPE)
    RETURN NUMBER IS
    sgrade NUMBER;
BEGIN
    SELECT s.grade
    INTO sgrade
    FROM emp e, salgrade s
    WHERE e.sal BETWEEN s.losal AND s.hisal
    AND e.empno=emp_no;
    
    RETURN sgrade;
END;

 

6.사원번호를 입력하면 근무지를 구하는 함수(find_loc)

-조인

[MY ANSWER] --조인
CREATE OR REPLACE FUNCTION find_loc(emp_no emp.empno%TYPE)
    RETURN VARCHAR2 IS
    location VARCHAR2(14);
BEGIN
    SELECT d.loc
    INTO location
    FROM dept d JOIN emp e
    ON d.deptno=e.deptno
    WHERE e.empno=emp_no;
    
    RETURN location;
END;

 

-서브쿼리

[MY ANSWER] --서브쿼리
CREATE OR REPLACE FUNCTION find_loc(emp_no emp.empno%TYPE)
    RETURN VARCHAR2 IS
    location VARCHAR2(14);
BEGIN
    SELECT loc
    INTO location
    FROM dept
    WHERE deptno = (SELECT deptno FROM emp
                    WHERE empno= emp_no);
    
    RETURN location;
END;

 

 

*생성된 함수 확인하기

-데이터 사전(DATA Dictionary)을 통해 검색
-데이터 사전에 저장된 모든 값은 대문자로 저장되기 때문에 대문자로 검색

SELECT object_name, object_type FROM user_objects
WHERE object_type='FUNCTION';

 

-작성된 함수의 소스 코드 확인

SELECT text FROM user_source
WHERE type='FUNCTION' AND name='TAX';

 

 

*프로시저

-반환하는 데이터가 없기 때문에 반복적으로 데이터의 삽입, 삭제, 갱신(DML문)이 일어날 때 주로 사용 

CREATE OR REPLACE PROCEDURE hello_world
IS
    --변수 선언
    message VARCHAR2(100);
BEGIN
    message := 'Hello World!';
    DBMS_OUTPUT.PUT_LINE(message);
END;

CREATE OR REPLACE PROCEDURE hello_oracle(p_message IN VARCHAR2)
IS
BEGIN
    DBMS_OUTPUT.PUT_LINE(p_message);
END;

-인자가 없으면 정의할 때 소괄호를 넣지 않음

 

*프로시저 실행

EXECUTE HELLO_WORLD;
EXEC HELLO_WORLD;

EXEC HELLO_ORACLE('Hello Oracle!');

 

*작성된 Stored Procedure 확인

SELECT object_name, object_type
FROM user_objects
WHERE object_type='PROCEDURE';

 

-작성된 Procedure의 소스코드 확인

SELECT text FROM user_source
WHERE type='PROCEDURE' AND name='HELLO_WORLD';

 

[예제]

-부서테이블에 부서정보를 입력하는 프로시저를 생성

CREATE OR REPLACE PROCEDURE add_department(p_deptno IN dept.deptno%TYPE, 
                                           p_dname IN dept.dname%TYPE, 
                                           p_loc IN dept.loc%TYPE)
IS
BEGIN
    --파라미터 변수에 입력 받은 값으로 부서(dept)테이블의 각 컬럼에 데이터를 추가 정상적으로 transaction 종료
    INSERT INTO dept VALUES(p_deptno, p_dname, p_loc);
    COMMIT;
    
    EXCEPTION WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE(p_dname || ' register is failed');
        ROLLBACK;
END;

 

-SALES 부서에 속한 사원의 정보 보기 (커서를 이용)

CREATE OR REPLACE PROCEDURE emp_info(p_dept dept.dname%TYPE)
IS
    --커서 선언
    CURSOR emp_cur IS
    SELECT empno, ename 
    FROM emp e JOIN dept d
    ON e.deptno=d.deptno
    WHERE dname = UPPER(p_dept);
    
    --변수 선언
    e_empno emp.empno%TYPE;
    e_ename emp.ename%TYPE;
BEGIN
    OPEN emp_cur;
    --커서로부터 데이터 읽기
    LOOP
        FETCH emp_cur INTO e_empno, e_ename;
        EXIT WHEN emp_cur%NOTFOUND;
        DBMS_OUTPUT.PUT_LINE(e_empno || ', ' || e_ename);
    END LOOP;
    
    CLOSE emp_cur;
END;

EXEC EMP_INFO('sales');

 

[실습 문제]

-사원번호와 새 업무를 입력하면 emp 테이블의 해당 사원의 업무를 갱신할 수 있는 프로시저를 작성하시오.

CREATE OR REPLACE PROCEDURE update_job(p_empno emp.empno%TYPE, p_job emp.job%TYPE)
IS
BEGIN
    UPDATE emp SET job=UPPER(p_job) WHERE empno=p_empno;
    COMMIT;
    
    EXCEPTION WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE(p_empno || ' update is failed');
        ROLLBACK;
END;

 

-동일한 도서가 있는지 점검한 후 동일한 도서가 없으면 삽입하고 동일한 도서가 있으면 가격을 업데이트

[IN-CLASS]
CREATE OR REPLACE PROCEDURE book_info(mybookid book.bookid%TYPE,
                                      mybookname book.bookname%TYPE, 
                                      mypublisher book.publisher%TYPE,
                                      myprice book.price%TYPE)
IS
    mycount NUMBER;
BEGIN
    SELECT COUNT(*) INTO mycount FROM book 
    WHERE bookname = mybookname;
    
    IF mycount!=0 THEN
        --동일한 도서 존재
        UPDATE book SET price = myprice WHERE bookname = mybookname;
    ELSE
        --동일한 도서 미존재
        INSERT INTO book VALUES(mybookid, mybookname, mypublisher, myprice);
    END IF;
    COMMIT;
    
    EXCEPTION WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('ERROR!!');
        ROLLBACK;
END;

 

 

 

2024.03.20

 

*패키지

-업무와 관련된 Stored Procedure 및 Stored Function을 관리하고, 이를 패키지 단위로 배포할 때 유용하게 사용

-패키지는 선언부와 본문으로 구분

 

-선언부 생성

CREATE OR REPLACE PACKAGE employee_pkg AS
    PROCEDURE print_ename(p_empno NUMBER);
    PROCEDURE print_sal(p_empno NUMBER);
END employee_pkg;

 

-본문 생성

CREATE OR REPLACE PACKAGE BODY employee_pkg AS
    PROCEDURE print_ename(p_empno NUMBER) IS
        e_name emp.ename%TYPE;
    BEGIN
        SELECT ename
        INTO e_name
        FROM emp
        WHERE empno=p_empno;
        DBMS_OUTPUT.PUT_LINE(e_name);
        EXCEPTION WHEN NO_DATA_FOUND THEN
            DBMS_OUTPUT.PUT_LINE('Invalid Employee Number');
    END print_ename;
    
    PROCEDURE print_sal(p_empno NUMBER) IS
        e_sal emp.sal%TYPE;
    BEGIN
        SELECT sal
        INTO e_sal
        FROM emp
        WHERE empno = p_empno;
        DBMS_OUTPUT.PUT_LINE(e_sal);
        EXCEPTION WHEN NO_DATA_FOUND THEN
            DBMS_OUTPUT.PUT_LINE('Invalid Employee Number');
    END print_sal;
END employee_pkg;

 

 

*트리거(Trigger)

-트리거는 데이터의 변경(INSERT,DELETE,UPDATE)문이 실행될 때 자동으로 같이 실행되는 프로시저
-오라클은 기본적으로 실행 전 BEFORE 과 실행 후 AFTER 트리거를 지원

 

-트리거를 생성한 후 dept 테이블에 데이터를 추가하면 등록된 트리거가 동작하면서
'dept 테이블에 정상적으로 데이터가 추가되었습니다.'를 출력함

CREATE OR REPLACE TRIGGER print_message
AFTER INSERT ON dept
BEGIN
    DBMS_OUTPUT.PUT_LINE('dept 테이블에 정상적으로 데이터가 추가되었습니다.');
END;
CREATE OR REPLACE TRIGGER afterinsertbook
--FOR EACH ROW는 매번 추가되는 행의 수만큼 TRIGGER가 발생함
AFTER INSERT ON book FOR EACH ROW
BEGIN
    INSERT INTO book_log
    -- :new.컬럼이름의 형식으로 추가, 수정할 때 해당 컬럼의 새로운 값을 저장함
    VALUES (:new.bookid, :new.bookname, :new.publisher, :new.price);
    COMMIT;
    DBMS_OUTPUT.PUT_LINE('삽입한 데이터를 book_log 테이블에 백업했습니다.');
    
    EXCEPTION WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('ERRORS!');
        ROLLBACK;
END;

 

※FOR EACH ROW는 매번 추가되는 행의 수만큼 TRIGGER가 발생함

※:new.컬럼이름의 형식으로 추가, 수정할 때 해당 컬럼의 새로운 값을 저장함

 

 

 

[Java - Oracle 연동]

 

*JDBC(Java Database Connectivity)

-자바를 이용하여 데이터베이스에 접근하여 각종 SQL문을 수행할 수 있도록 제공하는 API

-크게 JDBC 인터페이스와 JDBC 드라이버로 구성

 

*Java - Oracle 연동 방법

-클라우드에서 ojdbc8.jar(드라이버) 설치 -> 프로젝트에서 new source 생성(libs) -> libs 폴더에 jar 파일 붙여넣기

-프로젝트 우클릭 -> Build Path -> Configure Build Path

                                -> Libraries -> ModulePath -> Add JARs -> ojdbc8.jar 파일 찾아서 등록

 

*JDBC를 이용한 데이터베이스 연결 방법

-1단계 : 드라이버를 로드한다.

-2단계 : Connection 객체를 생성한다.

-3단계 : Statement 객체를 생성한다.

-4단계 : SQL문에 결과물이 있다면 ResultSet 객체를 생성한다.

-5단계 : 모든 객체를 닫는다.

 

*1단계: 오라클 드라이버 로드

public class DriverMain {
	public static void main(String[] args) {
		try {
			Class.forName("oracle.jdbc.OracleDriver");
			System.out.println("오라클 드라이버가 메모리에 정상적으로 로드되었습니다.");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
}

※oracle.jdbc.driver.OracleDriver는 옛날에 사용하던 것

 

*2단계: Connection 객체 생성 (오라클 접속을 위한 인증)

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class ConnectionMain {
	public static void main(String[] args) {
		String db_driver = "oracle.jdbc.OracleDriver";
		
		                 // thin -> 우리가 사용하는 드라이버가 thin 드라이버라는 의미
		                 // @localhost -> 원격 환경이면 @localhost 대신 @IP주소
		                 // 1521 -> 오라클의 포트
		                 // xe -> 오라클의 식별자
		String db_url = "jdbc:oracle:thin:@localhost:1521:xe";
		String db_id = "user01";
		String db_password = "1234";
		try {
			//JDBC 수행 1단계 : 드라이버 로드
			Class.forName(db_driver);
			//JDBC 수행 2단계 : Connection 객체 생성(오라클 접속을 위한 인증)
			Connection conn = DriverManager.getConnection(db_url, db_id, db_password);
			System.out.println("Connection 객체가 생성되었습니다.");
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
}

 

※db_url = "jdbc:oracle:thin:@localhost:1521:xe"

-thin -> 우리가 사용하는 드라이버가 thin 드라이버라는 의미
-@localhost -> 원격 환경이면 @localhost 대신 @IP주소
-1521 -> 오라클의 포트
-xe -> 오라클의 식별자

 

*3단계: Statement 객체 생성 (우리가 만든 SQL 문장을 전달)

-DDL, DML문 사용 가능

-자바와 연동하여 DDL문을 사용하는 경우는 드묾

 

*4단계: SQL문 수행

-데이터베이스를 연동할 때는 필수적으로 자원정리를 해주어야 함

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class CreateTableMain {
	public static void main(String[] args) {
		String db_driver = "oracle.jdbc.OracleDriver";
		String db_url = "jdbc:oracle:thin:@localhost:1521:xe";
		String db_id = "user01";
		String db_password = "1234";
		
		Connection conn = null;
		Statement stmt = null;
		String sql = null;
		
		try {
			//JDBC 수행 1단계 : 드라이버 로드
			Class.forName(db_driver);
			//JDBC 수행 2단계 : Connection 객체 생성 (오라클 접속을 위한 인증)
			conn = DriverManager.getConnection(db_url, db_id, db_password);
			
			System.out.println("test1 테이블을 생성합니다.");
			/*
			 * 테이블을 생성하는 SQL문
			 * 접속한 계정에 테이블명이 중복되면 에러가 발생하기 때문에
			 * 동일 계정에서는 한 번만 수행함
			 */
			sql = "CREATE TABLE test1(id VARCHAR2(10), age NUMBER)";
			
			//JDBC 수행 3단계 : Statement 객체 생성
			stmt = conn.createStatement();
			
			//JDBC 수행 4단계 : SQL문을 실행해서 DB에 테이블을 생성
			stmt.executeUpdate(sql);
			
			System.out.println("테이블이 정상적으로 생성되었습니다.");
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} finally {
			//자원정리
			if (stmt != null) {
				try {
					stmt.close();
				} catch(SQLException e) {}
			}
			
			if (conn != null) {
				try {
					conn.close();
				} catch(SQLException e) {}
			}
		}
	}
}

 

*DML문 실행하기

public class InsertMain {
	public static void main(String[] args) {
		String db_driver = "oracle.jdbc.OracleDriver";
		String db_url = "jdbc:oracle:thin:@localhost:1521:xe";
		String db_id = "user01";
		String db_password = "1234";
		
		Connection conn = null;
		Statement stmt = null;
		String sql = null;
		
		try {
			//JDBC 수행 1단계
			Class.forName(db_driver);
			
			//JDBC 수행 2단계: Connection 객체 생성 (오라클 접속 인증)
			conn = DriverManager.getConnection(db_url, db_id, db_password);
			
			//SQL문 작성
			sql = "INSERT INTO test1(id, age) VALUES('she''g', 30)";
			
			//JDBC 수행 3단계: Statement 객체 생성
			stmt = conn.createStatement();
			
			//JDBC 수행 4단계 : SQL문을 실행해서 하나의 행을 삽입
			//                : 삽입 작업 후 삽입한 행의 개수를 반환
			int count = stmt.executeUpdate(sql);
			System.out.println(count + "개의 행을 추가했습니다.");
			
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if (stmt != null) try { stmt.close();} catch (SQLException e) {}
			if (conn != null) try { conn.close();} catch (SQLException e) {}
		}
	}
}

 

*테이블에서 데이터 읽어오기

-SQL문을 실행해서 테이블로부터 레코드(행)을 전달받아서 결과집합을 만들고 ResultSet 객체에 담아서 반환

rs = stmt.executeQuery(sql);

 

-ResultSet에 보관된 결과집합에 접근해서 행단위로 데이터를 추출

while(rs.next()) {
				//컬럼명을 통해서 데이터를 반환
//				System.out.print(rs.getString("id"));
//				System.out.print("\t");
//				System.out.println(rs.getInt("age"));
				
				//컬럼 인덱스를 통해서 데이터를 반환
				System.out.print(rs.getString(1));
				System.out.print("\t");
				System.out.println(rs.getInt(2));
			}

 

 

2024.03.21

 

*PreparedStatement

-데이터를 SQL문에 직접 입력하지 않으므로 보안에 유리

-인자를 '?' 플레이스홀더로 대체한 채로 컴파일

-캐싱한 데이터를 재사용하기 때문에 DB에 부하를 낮추고 속도가 빠름

-특수문자를 사용하는 데에 문제가 없음

->실무상에서는 주로 PreparedStatement만을 사용

 

*PreparedStatement 객체를 이용하여 테이블에 데이터 삽입하기

public class InsertMain {
	
	public static void main(String[] args) {
		Connection conn = null;
		PreparedStatement pstmt = null;
		String sql = null;
		
		try {
			//JDBC 수행 1,2 단계
			conn = DBUtil.getConnection();
			
			//SQL문 만들기
			sql = "INSERT INTO test2(id,name,age,reg_date) VALUES(?,?,?,SYSDATE)";
			
			//JDBC 수행 3단계: PreparedStatement 객체 생성
			pstmt = conn.prepareStatement(sql);
			//?에 데이터를 바인딩
			pstmt.setString(1, "Wendy's"); //1번 ?에 데이터 전달
			pstmt.setString(2, "정웬디"); //2번 ?에 데이터 전달
			pstmt.setInt(3, 18); //3번 ?에 데이터 전달
			
			//JDBC 수행 4단계
			int count = pstmt.executeUpdate(); //prepareStatement()에 sql을 전달했기 때문에 여기에서는 전달하지 않음
			
			System.out.println(count + "개 행을 추가했습니다.");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			DBUtil.executeClose(null, pstmt, conn);
		}
	}
}

 

*DBUtil 클래스

-데이터베이스 연동을 위한 상수 저장

-드라이버 로드, 데이터베이스와 연동하는 반복작업 분리

public class DBUtil {
	private static final String DB_DRIVER = "oracle.jdbc.OracleDriver";
	private static final String DB_URL = "jdbc:oracle:thin:@localhost:1521:xe";
	private static final String DB_ID = "user01";
	private static final String DB_PASSWORD = "1234";
	
	//Connection 객체를 생성해서 반환
	public static Connection getConnection() throws ClassNotFoundException, SQLException {
		//JDBC 수행 1단계: 드라이버 로드
		Class.forName(DB_DRIVER);
		//JDBC 수행 2단계: Connection 객체 생성
		Connection conn = DriverManager.getConnection(DB_URL, DB_ID, DB_PASSWORD);
		
		return conn;
	}
	
	//자원정리
	//PreparedStatement용
	public static void executeClose(ResultSet rs, PreparedStatement pstmt, Connection conn) {
		//rs가 없을 시 null을 넘겨주면 됨
		if (rs!=null) try {rs.close();} catch (SQLException e) {}
		if (pstmt!=null) try {pstmt.close();} catch (SQLException e) {}
		if (conn!=null) try {conn.close();} catch (SQLException e) {}
	}
	
	//CallableStatement용 메서드 오버로딩
	public static void executeClose(ResultSet rs, CallableStatement cstmt, Connection conn) {
		if (rs!=null) try {rs.close();} catch (SQLException e) {}
		if (cstmt!=null) try {cstmt.close();} catch (SQLException e) {}
		if (conn!=null) try {conn.close();} catch (SQLException e) {}
	}
}

 

*콘솔창에서 입력받아 테이블에 데이터 삽입,수정,삭제하기

-테이블, 기본키로 사용할 시퀀스 생성

CREATE TABLE test3 (
	num NUMBER PRIMARY KEY,
	title VARCHAR2(60) NOT NULL,
	name VARCHAR2(30) NOT NULL,
	memo VARCHAR2(4000) NOT NULL,
	email VARCHAR2(30),
	reg_date DATE NOT NULL
);

CREATE SEQUENCE test3_seq;

 

-콘솔창에서 입력받은 데이터 삽입하기

public class InsertMain {
	public static void main(String[] args) {
		BufferedReader br = null;
		
		Connection conn = null;
		PreparedStatement pstmt = null;
		String sql = null;
		
		try {
			br = new BufferedReader(new InputStreamReader(System.in));
			System.out.print("제목: ");
			String title = br.readLine();
			System.out.print("이름: ");
			String name = br.readLine();
			System.out.print("메모: ");
			String memo = br.readLine();
			System.out.print("이메일: ");
			String email = br.readLine();
			
			//JDBC 수행 1,2단계
			conn = DBUtil.getConnection();
			
			//SQL문 작성
			sql = "INSERT INTO test3(num, title, name, memo, email, reg_date)"
					+ "VALUES(test3_seq.nextVal, ?, ?, ?, ?, SYSDATE)";
			
			//JDBC 수행 3단계 : PreparedStatement 객체 생성
			pstmt = conn.prepareStatement(sql);
			//?에 데이터 바인딩
			pstmt.setString(1, title);
			pstmt.setString(2, name);
			pstmt.setString(3, memo);
			pstmt.setString(4, email);
			
			//JDBC 수행 4단계
			int count = pstmt.executeUpdate();
			
			System.out.println(count + "개 행을 추가했습니다.");
			
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			//자원정리
			DBUtil.executeClose(null, pstmt, conn);
			if (br != null) try {br.close();} catch (IOException e) {}
			//데이터베이스 자원정리는 무조건, BufferedReader 자원정리는 생략되기도 함
		}
	}
}

 

*테이블 읽어와서 데이터 목록 만들기

//JDBC 4단계 수행
rs = pstmt.executeQuery();
			
System.out.println("번호\t작성자\t등록일\t\t제목");
			
while (rs.next()) {
	System.out.print(rs.getInt("num"));
	System.out.print("\t");
	System.out.print(rs.getString("name"));
	System.out.print("\t");
	System.out.print(rs.getDate("reg_date"));
	System.out.print("\t");
	System.out.print(rs.getString("title"));
	System.out.println();
}

 

*if문으로 조건체크해서 기본키가 일치하는 데이터 검색

-단일행의 데이터를 읽어올 때는 while문 사용하지 않고 if문 사용

if (rs.next()) {
	System.out.println("번호: " + rs.getInt("num"));
	System.out.println("제목: " + rs.getString("title"));
	System.out.println("작성자: " + rs.getString("name"));
	System.out.println("내용: " + rs.getString("memo"));

	// 이메일 null값 처리
	String email = rs.getString("email");
	if (email == null)
		email = "";

	System.out.println("이메일: " + email);
	System.out.println("등록일자: " + rs.getDate("reg_date"));

} else {
	// 프라이머리키가 올바르지 않을 때
	System.out.println("검색된 데이터가 없습니다.");
}

 

*검색 작업 (LIKE 사용)

-SQL문 작성

//SQL문 작성
sql = "SELECT * FROM test3 WHERE title LIKE '%' || ? || '%'";

 

-if문에서 rs.next()를 사용해서 조건체크 할 경우에는 이미 커서가 ResultSet의 첫번째 행을 가리키고 있으므로

 while문을 사용하지 않고 do-while문 사용

if (rs.next()) {
	System.out.println("번호\t작성자\t등록일\t\t제목");

	do {
		System.out.print(rs.getInt("num"));
		System.out.print("\t");
		System.out.print(rs.getString("name"));
		System.out.print("\t");
		System.out.print(rs.getDate("reg_date"));
		System.out.print("\t");
		System.out.println(rs.getString("title"));
	} while (rs.next());

} else {
	System.out.println("검색된 데이터가 없습니다.");
}

 

*CallableStatement

-프로시저 호출 시 사용

 

-테이블 생성 및 초기 데이터 추가

CREATE TABLE salary(
	name VARCHAR2(10) PRIMARY KEY,
	pay NUMBER NOT NULL
);

INSERT INTO salary VALUES('SMITH', '1000');
INSERT INTO salary VALUES('PETER', '2000');
INSERT INTO salary VALUES('JULIE', '3000');
COMMIT;

 

-프로시저 작성

CREATE OR REPLACE PROCEDURE adjust(n IN VARCHAR2, rate IN FLOAT) 
IS
	newpay FLOAT;
BEGIN
	SELECT pay INTO newpay FROM salary WHERE name=n;
	newpay := newpay + newpay*rate;
	UPDATE salary SET pay=newpay WHERE name=n;
	COMMIT;
	
	EXCEPTION WHEN OTHERS THEN
		DBMS_OUTPUT.PUT_LINE('Error Occurred!');
		ROLLBACK;
END;

 

-프로시저 호출

//JDBC 수행 1,2단계
conn = DBUtil.getConnection();
		
//프로시저 호출 문장 작성
sql = "{call adjust(?,?)}";
			
//JDBC 수행 3단계 : CallableStatement 객체 생성
cstmt = conn.prepareCall(sql);
//?에 데이터 바인딩
cstmt.setString(1, name);
cstmt.setFloat(2, rate);
			
//JDBC 수행 4단계 : 프로시저 호출
cstmt.executeUpdate();
System.out.println("급여 정보를 수정했습니다.");

-EXECTUTE가 아닌 {call}을 통해 호출

 

*하나의 트랜잭션에서 복수의 SQL문 처리하기

-하나의 SQL문일 때에는 동작이 정상적으로 작동하는 한 오토커밋함

-여러개의 SQL문을 하나의 작업단위(트랜잭션)으로 처리할 때에는

 여러 개의 문장 중 하나만 오류가 발생하더라도 전체를 ROLLBACK해야 하므로 오토커밋 기능을 해제해야 함

-오토커밋 기능은 Connection 객체에 기본값으로 설정되어 있으므로 이를 해제

-정상적으로 작동할 경우 수동으로 커밋하고, 예외가 발생할 경우 try-catch문에서 롤백

public class RightTransaction {
	public static void main(String[] args) {
		Connection conn = null;
		PreparedStatement pstmt1 = null;
		PreparedStatement pstmt2 = null;
		PreparedStatement pstmt3 = null;
		String sql = null;

		try {
			//복수의 SQL문을 실행할 경우 오토커밋을 해제하고 수작업으로 트랜잭션 처리함
			//JDBC 수행 1,2단계
			conn = DBUtil.getConnection();
			//오토커밋 해제
			conn.setAutoCommit(false);

			sql = "INSERT INTO test1 VALUES ('ANNA', 10)";
			pstmt1 = conn.prepareStatement(sql);
			pstmt1.executeUpdate();

			sql = "INSERT INTO test1 VALUES ('MARY', 20)";
			pstmt2 = conn.prepareStatement(sql);
			pstmt2.executeUpdate();

			//테스트용으로 잘못된 SQL문 작성
			sql = "INSERT INTO test1 VAUES ('JAMES', 30";
			pstmt3 = conn.prepareStatement(sql);
			pstmt3.executeUpdate();

			//정상적으로 작업이 완료되면 commit
			conn.commit();

			System.out.println("작업 완료");
		} catch (Exception e) {
			//예외가 발생할 경우 rollback
			try {
				conn.rollback();
				System.out.println("예외가 발생하여 데이터를 삽입하지 않습니다.");
			} catch (SQLException e2) {
				e2.printStackTrace();
			}
		} finally {
			//자원정리
			//pstmt 사이의 정리 순서는 상관 없음
			DBUtil.executeClose(null, pstmt3, null);
			DBUtil.executeClose(null, pstmt2, null);
			DBUtil.executeClose(null, pstmt1, conn);
		}
	}
}

 

*DAO : Data Access Object

-데이터베이스의 데이터를 전문적으로 호출하고 제어하는 객체

-데이터베이스 연동 작업에서는 자원정리를 해야하기 때문에 에러를 throw 하지 않고
 메서드 안에서 try-catch-finally를 해주는 것이 원칙

 

*게시판 콘솔 프로그램 만들기

NoteDAO 클래스 -글쓰기, 목록 보기, 상세글 보기, 글수정, 글삭제 메서드 인자 받아와서 작성
-각각의 메서드에서 자원정리를 위한 try-catch-finally문 작성
NoteMain 클래스 -BufferedReader, NoteDAO 변수 생성
-메뉴호출 메서드 작성

*생성자
-BufferedReader, NoteDAO 객체 생성
-메뉴호출 메서드 호출
-IOException 예외처리

*callMenu()
-while문 안에서 NumberFormatException 예외처리

*main()
-생성자 호출