[문제] 2006년도에 입사한 사원들의 근무 도시이름별 급여의 총액, 평균을 출력.
1)
DECLARE
    CURSOR city_cur IS 
        SELECT city, sum(sumsal) sumsal, avg(avgsal) avgsal
        FROM(SELECT l.city, e.sumsal, e.avgsal
                FROM (SELECT department_id, sum(salary) sumsal, trunc(avg(salary)) avgsal
                        FROM hr.employees
                        WHERE hire_date >= to_date('2006-01-01','yyyy-mm-dd')
                        AND hire_date < to_date('2007-01-01','yyyy-mm-dd')
                        GROUP BY department_id) e, hr.departments d, hr.locations l
                WHERE e.department_id = d.department_id
                AND d.location_id = l.location_id)
        GROUP BY city;
        -- 2006년도 사원 정리 후 JOIN
    v_rec city_cur%rowtype;
BEGIN
    OPEN city_cur;
    LOOP
        FETCH city_cur INTO v_rec;
        EXIT WHEN city_cur%NOTFOUND;
        DBMS_OUTPUT.PUT_LINE(v_rec.city||' 도시에 근무하는 사원들의 총액급여는 '||
                                ltrim(to_char(v_rec.sumsal,'L999,999'))||' 이고, 평균급여는 ' ||
                                ltrim(to_char(v_rec.avgsal,'L999,999'))||' 입니다.');
    END LOOP;
    CLOSE city_cur;
END;
/
2)
DECLARE
    CURSOR city_cur IS 
        SELECT city, sum(sumsal) sumsal, avg(avgsal) avgsal
        FROM(SELECT l.city, e.sumsal, e.avgsal
                FROM (SELECT department_id, sum(salary) sumsal, trunc(avg(salary)) avgsal
                        FROM hr.employees
                        WHERE hire_date >= to_date('2006-01-01','yyyy-mm-dd')
                        AND hire_date < to_date('2007-01-01','yyyy-mm-dd')
                        GROUP BY department_id) e, hr.departments d, hr.locations l
                WHERE e.department_id = d.department_id
                AND d.location_id = l.location_id)
        GROUP BY city;
        -- 2006년도 사원 정리 후 JOIN
    v_rec city_cur%rowtype;
BEGIN
    FOR v_rec IN city_cur LOOP
        DBMS_OUTPUT.PUT_LINE(v_rec.city||' 도시에 근무하는 사원들의 총액급여는 '||
                                ltrim(to_char(v_rec.sumsal,'L999,999'))||' 이고, 평균급여는 '||
                                ltrim(to_char(v_rec.avgsal,'L999,999'))||' 입니다.');
    END LOOP;
END;
/
3)
BEGIN
    FOR v_rec IN (SELECT city, sum(sumsal) sumsal, avg(avgsal) avgsal
                    FROM(SELECT l.city, e.sumsal, e.avgsal
                            FROM (SELECT department_id, sum(salary) sumsal, trunc(avg(salary)) avgsal
                                    FROM hr.employees
                                    WHERE hire_date >= to_date('2006-01-01','yyyy-mm-dd')
                                    AND hire_date < to_date('2007-01-01','yyyy-mm-dd')
                                    GROUP BY department_id) e, hr.departments d, hr.locations l
                            WHERE e.department_id = d.department_id
                            AND d.location_id = l.location_id)
                    GROUP BY city) LOOP
    DBMS_OUTPUT.PUT_LINE(v_rec.city||' 도시에 근무하는 사원들의 총액급여는 '||
                            ltrim(to_char(v_rec.sumsal,'L999,999'))||' 이고, 평균급여는 '||
                            ltrim(to_char(v_rec.avgsal,'L999,999'))||' 입니다.');
    END LOOP;
END;
/

 

DECLARE
    CURSOR parm_cur_80 IS
        SELECT employee_id, last_name, job_id
        FROM hr.employees
        WHERE department_id = 80
        AND job_id = 'SA_MAN';

    CURSOR parm_cur_50 IS
        SELECT employee_id, last_name, job_id
        FROM hr.employees
        WHERE department_id = 50
        AND job_id = 'ST_MAN';

    v_rec1 parm_cur_80%rowtype;
BEGIN
    OPEN parm_cur_80;
    LOOP
        FETCH parm_cur_80 INTO v_rec1;
            EXIT WHEN parm_cur_80%NOTFOUND;
        DBMS_OUTPUT.PUT_LINE(v_rec1.last_name);
    END LOOP;
    CLOSE parm_cur_80;
    
    DBMS_OUTPUT.NEW_LINE;
    
    FOR v_rec2 IN parm_cur_50 LOOP
        DBMS_OUTPUT.PUT_LINE(v_rec2.last_name);
    END LOOP;
END;
/

>> 데이터의 분포가 다를경우 느려짐 방지차 실행계획을 따로 해야한다.
# parameter를 포함한 cursor : 이유는 실행계획을 공유하기 위해서
# parameter 변수 선언시에 size는 표현하지 않습니다.
DECLARE
    CURSOR parm_cur(p_id number, p_job varchar2) IS
        SELECT employee_id, last_name, job_id
        FROM hr.employees
        WHERE department_id = p_id
        AND job_id = p_job;

    v_rec1 parm_cur%rowtype;
BEGIN
    OPEN parm_cur(80,'SA_MAN');
    LOOP
        FETCH parm_cur INTO v_rec1;
            EXIT WHEN parm_cur%NOTFOUND;
        DBMS_OUTPUT.PUT_LINE(v_rec1.last_name);
    END LOOP;
    CLOSE parm_cur;
    
    DBMS_OUTPUT.NEW_LINE;
    
    FOR v_rec2 IN parm_cur(50,'ST_MAN') LOOP
        DBMS_OUTPUT.PUT_LINE(v_rec2.last_name);
    END LOOP;
END;
/

 


 

■ EXCEPTION (예외처리)
- 실행중에 발생한 PL/SQL 오류
- 오라클에 의해 암시적으로 발생
- 프로그램에 의해 명시적으로 발생

DECLARE
    v_id number := 300;     -- 오류 : 없는 데이터
    v_rec hr.employees%rowtype;
BEGIN
    SELECT *
    INTO v_rec
    FROM hr.employees
    WHERE employee_id = v_id;
    DBMS_OUTPUT.PUT_LINE(v_rec.employee_id||' '||v_rec.last_name);    
END;
/
-- ORA-01403: no data found
DECLARE
    v_id number := 300;
    v_rec hr.employees%rowtype;
BEGIN
    SELECT *
    INTO v_rec
    FROM hr.employees
    WHERE employee_id = v_id;
    DBMS_OUTPUT.PUT_LINE(v_rec.employee_id||' '||v_rec.last_name);

EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE(v_id||' 사원은 존재하지 않습니다.');
END;
/
-- PL/SQL 프로시저가 성공적으로 완료되었습니다.

 

DECLARE
    v_id number := 20;
    v_rec hr.employees%rowtype;
BEGIN
    SELECT *
    INTO v_rec
    FROM hr.employees
    WHERE department_id = v_id;     --> 오류 : 1개행 단일 데이터가 아님
    
    DBMS_OUTPUT.PUT_LINE(v_rec.department_id||' '||v_rec.last_name);

EXCEPTION
    WHEN TOO_MANY_ROWS THEN
        DBMS_OUTPUT.PUT_LINE(v_id||' 부서 사원들이 여러명입니다. 명시적인커서를 이용해주세요.');
END;
/
-- ORA-01422: exact fetch returns more than requested number of rows

 

DECLARE
    v_id number := 20;
    v_rec hr.employees%rowtype;
BEGIN
    SELECT *
    INTO v_rec
    FROM hr.employees
    WHERE department_id = v_id;
    
    DBMS_OUTPUT.PUT_LINE(v_rec.department_id||' '||v_rec.last_name);

EXCEPTION     
    WHEN OTHERS THEN        -- 걸리는게 없을 때 마지막에 사용
        DBMS_OUTPUT.PUT_LINE('오류가 발생했습니다.');
        DBMS_OUTPUT.PUT_LINE(SQLCODE);  -- 오류코드
        DBMS_OUTPUT.PUT_LINE(SQLERRM);  -- 오류메세지
END;
/

 

SELECT salary FROM hr.employees WHERE department_id = 20;
13000
6000
DECLARE
    v_rec hr.employees%rowtype;
BEGIN
    UPDATE hr.employees
    SET salary = salary * 1.1
    WHERE department_id = 20;   -- transaction 발생, 진행중인 상태

    SELECT *
    INTO v_rec
    FROM hr.employees
    WHERE department_id = 20;   -- 오류발생
    
    DBMS_OUTPUT.PUT_LINE(v_rec.employee_id||' '||v_rec.last_name);
END;
/
-- ORA-01422: exact fetch returns more than requested number of rows
SELECT salary FROM hr.employees WHERE department_id = 20;   -- 업데이트 안됨
13000
6000
>>> 프로그램이 비정상적인 종료가 수행되면 transaction은 자동 ROLLBACK 된다.
SELECT salary FROM hr.employees WHERE department_id = 20;
13000
6000
DECLARE
    v_rec hr.employees%rowtype;
BEGIN
    UPDATE hr.employees
    SET salary = salary * 1.1
    WHERE department_id = 20;       -- transaction 발생, 진행중인 상태

    SELECT *
    INTO v_rec
    FROM hr.employees
    WHERE department_id = 20;       -- 오류발생
    
    DBMS_OUTPUT.PUT_LINE(v_rec.employee_id||' '||v_rec.last_name);

EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE(' 사원은 존재하지 않습니다.');
    
    WHEN OTHERS THEN        -- 작동
        DBMS_OUTPUT.PUT_LINE(SQLCODE);  -- 오류코드
        DBMS_OUTPUT.PUT_LINE(SQLERRM);  -- 오류메세지
END;
/
SELECT salary FROM hr.employees WHERE department_id = 20;   -- 업데이트 됨
14300
6600

COMMIT;
>>> 프로그램안에서 오류가 발생했지만 그 오류에 따른 예외사항 처리를 수행하면 Transaction 은 살아 있다.
진행중인 상태로 간다. -> 다른 사람은 작업 대기 발생


★ 꼭 Transaction 을 종료(ROLLBACK,COMMIT)를 프로그램 안에서 할지 프로그램 바깥쪽에서 할지를 결정해야 한다.

 

 

# 사용자가 정의한 예외사항

DELETE FROM hr.departments WHERE department_id = 20;
-- ORA-02292: integrity constraint (HR.EMP_DEPT_FK) violated - child record found
-- 데이터가 연결되어있어 삭제 불가하다.

SELECT * 
FROM user_constraints 
WHERE table_name IN ('EMPLOYEES','DEPARTMENTS');

SELECT * 
FROM user_cons_columns
WHERE table_name IN ('EMPLOYEES','DEPARTMENTS');

BEGIN
    DELETE FROM hr.departments WHERE department_id = 20;
EXCEPTION
    WHEN others THEN
        DBMS_OUTPUT.PUT_LINE(SQLCODE);
        DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
/
>> ORA-02292에 해당하는 EXCEPTION 키워드가 없다.
DECLARE
    pk_error EXCEPTION;                         -- 예외사항 이름 선언
    PRAGMA EXCEPTION_INIT(pk_error, -2292);     -- 오라클의 오류번호와 예외사항 이름을 붙이는 작업
    
BEGIN
    DELETE FROM hr.departments WHERE department_id = 20;
    
EXCEPTION
    WHEN pk_error THEN
        DBMS_OUTPUT.PUT_LINE('이 값을 참조하고 있는 행들이 있습니다.');
        
    WHEN others THEN
        DBMS_OUTPUT.PUT_LINE(SQLCODE);
        DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
/

 

BEGIN
    UPDATE hr.employees
    SET salary = salary * 1.1
    WHERE employee_id = 300;    -- 해당하는 값이 없다.
    
    IF sql%NOTFOUND THEN        -- FETCH 한 행이 없으면 TRUE
        DBMS_OUTPUT.PUT_LINE('영향을 받은 행이 없습니다.');
    END IF;
END;
/
DECLARE
    e_invalid EXCEPTION;
    
BEGIN
    UPDATE hr.employees
    SET salary = salary * 1.1
    WHERE employee_id = 300;        -- 오류 : 
    
    IF sql%NOTFOUND THEN
        RAISE e_invalid;            -- RAISE 문을 만나면 무조건 EXCEPTION으로 감
    END IF;

EXCEPTION
    WHEN e_invalid THEN
        DBMS_OUTPUT.PUT_LINE('영향을 받은 행이 없습니다.');
END;
/

 

 

# 유저가 정의한 오류가 발생할 수 있는 프로시저.
    프로그램은 비정상적인 종료, Transaction 작업은 rollback
    RAISE_APPLICATION_ERROR(오류번호,오류메세지,FALSE);
- 오류번호 : -20000 ~ -20999
- TRUE : 오라클의 오류와 내가 만든 오류가 같이 출력
- FALSE : 내가 만든 오류만 출력

특정사원 조회불가
DECLARE
    v_id number := 100;
    v_name varchar2(30);
    v_dept_name varchar2(30);
    emp_raise EXCEPTION;
    
BEGIN
    IF v_id = 100 THEN
        RAISE emp_raise;
    END IF;
    
    SELECT e.last_name, d.department_name
    INTO v_name, v_dept_name
    FROM hr.employees e, hr.departments d
    WHERE e.department_id = d.department_id
    AND e.employee_id = v_id;
    
    DBMS_OUTPUT.PUT_LINE(v_name||' '||v_dept_name);
    
EXCEPTION
    WHEN emp_raise THEN
        DBMS_OUTPUT.PUT_LINE(v_id||' '||' 사원은 조회할 수 없습니다.');

    WHEN no_data_found THEN
        DBMS_OUTPUT.PUT_LINE(v_id||' '||' 사원은 존재하지 않습니다.');
END;
/
DECLARE
    v_id number := 100;
    v_name varchar2(30);
    v_dept_name varchar2(30);
    
BEGIN
    IF v_id = 100 THEN
        RAISE_APPLICATION_ERROR(-20000,'조회할 수 없다.');
    END IF;
    
    SELECT e.last_name, d.department_name
    INTO v_name, v_dept_name
    FROM hr.employees e, hr.departments d
    WHERE e.department_id = d.department_id
    AND e.employee_id = v_id;
    
    DBMS_OUTPUT.PUT_LINE(v_name||' '||v_dept_name);
    
EXCEPTION
    WHEN no_data_found THEN
        RAISE_APPLICATION_ERROR(-20001,'조회된 데이터가 없습니다.');
END;
/
오류 보고 -
ORA-20000: 조회할 수 없다.
DECLARE
    v_id number := 300;
    v_name varchar2(30);
    v_dept_name varchar2(30);
    
BEGIN
    IF v_id = 100 THEN
        RAISE_APPLICATION_ERROR(-20000,'조회할 수 없다.');
    END IF;
    
    SELECT e.last_name, d.department_name
    INTO v_name, v_dept_name
    FROM hr.employees e, hr.departments d
    WHERE e.department_id = d.department_id
    AND e.employee_id = v_id;
    
    DBMS_OUTPUT.PUT_LINE(v_name||' '||v_dept_name);
    
EXCEPTION
    WHEN no_data_found THEN
        RAISE_APPLICATION_ERROR(-20001,'조회된 데이터가 없습니다.',TRUE);
END;
/
오류 보고 -
ORA-20001: 조회된 데이터가 없습니다.    -- 내가 만든 오류
ORA-01403: no data found            -- 오라클 오류

 

[문제] 전체 사원 들의 사번, 이름, 급여, 입사일, 근무연수를 출력합니다.
또한 근무연수가 15년 이상이고 급여는 10000 미만인 사원들은 예외사항이 발생하도록 한 후 
메시지 출력하고  프로그램 수행이 완료된 후에 분석할수있도록  years 테이블에 정보가 입력이 되도록 프로그램을 작성합니다. 
근무연수는 소수점은 버리세요
<화면 출력>
....
201, Hartstein, 13000, 04/02/17, 12
202, Fay, 6000, 05/08/17, 10
203, Mavris, 6500, 02/06/07, 13
사원 203 근무연수는 13 년이고 급여는 6500 입니다.
204, Baer, 10000, 02/06/07, 13
205, Higgins, 12008, 02/06/07, 13
206, Gietz, 8300, 02/06/07, 13
사원 206 근무연수는 13 년이고 급여는 8300 입니다.
....
/* 테이블 생성 */
CREATE TABLE years(id number, name varchar2(30), sal number, year number);

DECLARE
    e_raise EXCEPTION;

BEGIN
    FOR emp_rec IN (SELECT employee_id, last_name, salary, hire_date, trunc(months_between(sysdate,hire_date)/12) year
                    FROM hr.employees) LOOP
        DBMS_OUTPUT.PUT_LINE(emp_rec.employee_id||', '||emp_rec.last_name||', '||emp_rec.salary||', '||
                                emp_rec.hire_date||', '||emp_rec.year);
        
        /* ★ 서브 블럭에서 RAISE 사용 ★ */
        BEGIN
            IF emp_rec.year >= 15 AND emp_rec.salary < 10000 THEN
                RAISE e_raise;
            END IF;
        
        EXCEPTION
            WHEN e_raise THEN
                DBMS_OUTPUT.PUT_LINE('사원 '||emp_rec.employee_id||' 근무연수는 '||emp_rec.year||
                                        '년이고 급여는 '||emp_rec.salary||' 입니다.');
                INSERT INTO hr.years(id,name,sal,year) VALUES(emp_rec.employee_id,emp_rec.last_name,
                                                                emp_rec.salary,emp_rec.year);
                COMMIT;
        END;
        /* 서브블럭이 끝나면 여기로 와서 다시 FOR문 반복 */
    END LOOP;
END;
/

SELECT *
FROM hr.years;


★  반복사이에 EXCEPTION이 발생하고 계속 반복작업할 경우 서브블럭 활용 ★

 


 

DECLARE
    e_riase EXCEPTION;  -- 선언
    PRAGMA EXCEPTION_INIT(e_riase, -2292);     -- 오라클의 오류번호 예외사항 연결
    
BEGIN
    IF 조건 THEN
        RAISE e_riase;  -- exception으로 점프

EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('데이터 없다');

    WHEN TOO_MANY_ROWS THEN
        DBMS_OUTPUT.PUT_LINE('단일행 데이터가 아니다');

    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('오류발생');
        DBMS_OUTPUT.PUT_LINE(SQLCODE);  -- 오류코드
        DBMS_OUTPUT.PUT_LINE(SQLERRM);  -- 오류메세지

    WEHN e_riase THEN
        DBMS_OUTPUT.PUT_LINE('메세지남겨요');
        
        RAISE_APPLICATION_ERROR(-20000,'내가 만든 오류메세지');
        
END;

 

'Oracle SQL' 카테고리의 다른 글

20 PL/SQL PROCEDURE, FUNCTION  (0) 2023.11.02
19 PL/SQL PROCEDURE  (0) 2023.11.01
17 PL/SQL Nested Table, VARRAY, Explicit Cursor  (0) 2023.10.30
16 PL/SQL RECORD, Array  (0) 2023.10.27
15 PL/SQL WHILE LOOP, FOR, Implicit Cursor  (1) 2023.10.26