void 메소드와 유사하고 특정 로직을 처리하고 결과값을 반환하지 않는 것
PL/SQL에도 값을 전달받아서 코드를 실행 후 리턴하는 함수가 존재
매개값이 없는 프로시저
CREATE PROCEDURE ~ IS ~ BEGIN ~ END;
CREATE PROCEDURE p_test IS -- 선언부
v_msg VARCHAR2(30) := 'HI Procedure';
BEGIN -- 실행부
dbms_output.put_line(v_msg);
-- 출력문을 넣었는데 출력이 안됨
END; -- 종료부
-- 프로시저의 이름을 지정하면 밑의 방법으로 프로시저를 호출해서 출력 확인 가능
EXEC p_test; -- 프로시저 호출문
-- IN 입력값을 받는 파라미터
CREATE PROCEDURE my_new_job_proc (
p_job_id IN jobs.job_id%TYPE,
p_job_title IN jobs.job_title%TYPE,
p_min_sal IN jobs.min_salary%TYPE,
p_max_sal IN jobs.max_salary%TYPE
) -- 매개값 작성 (IN 키워드 사용)
IS
BEGIN
INSERT INTO jobs VALUES (
p_job_id,
p_job_title,
p_min_sal,
p_max_sal
);
COMMIT;
-- IN키워드를 사용해서 매개값으로 받은 것을 넣어줌
END;
EXEC my_new_job_proc('job1', 'test', 1000, 5000);
-- 진짜 함수처럼 프로시저 이름과 매개값들을 넣어주네 (이렇게 함수를 하나 만들어서 인수만 넣어주면 쉽겠다)
프로시저가 있으면 수정해주고 없으면 만들어 줌
CREATE OR REPLACE PROCEDURE my_new_job_proc (
p_job_id IN jobs.job_id%TYPE,
p_job_title IN jobs.job_title%TYPE,
p_min_sal IN jobs.min_salary%TYPE,
p_max_sal IN jobs.max_salary%TYPE
) -- 매개값 작성 (IN 키워드 사용)
IS
v_cnt NUMBER := 0;
BEGIN
-- 동일한 job_id가 있는지부터 체크
-- 이미 존재한다면 1, 존재하지 않는다면 0을 주고 그것을 v_cnt에 대입할 예정
SELECT
COUNT(*)
INTO v_cnt
FROM
jobs
WHERE
job_id = p_job_id;
-- 중복이 존재하면 1이 나옴
IF v_cnt = 0 THEN -- 없다면 INSERT
INSERT INTO jobs VALUES (
p_job_id,
p_job_title,
p_min_sal,
p_max_sal
);
ELSE -- 있다면 UPDATE
UPDATE jobs
SET
job_title = p_job_title,
min_salary = p_min_sal,
max_salary = p_max_sal
WHERE
job_id = p_job_id;
END IF;
COMMIT;
-- IN키워드를 사용해서 매개값으로 받은 것을 넣어줌
END;
EXEC my_new_job_proc('job2', 'test job2', 5000, 10000);
EXEC my_new_job_proc('job1', 'test job3', 5000, 10);
-- 값을 새로 만들어서 넣을 수 있고 이미 있다면 값을 갱신할 수도 있음
SELECT
*
FROM
jobs;
-- 매개변수(인수)의 디폴트 값(기본값) 설정 매개변수로 값을 입력 받지 않아도 min,max_sal은 기본값으로 대입
CREATE OR REPLACE PROCEDURE my_new_job_proc (
p_job_id IN jobs.job_id%TYPE,
p_job_title IN jobs.job_title%TYPE,
p_min_sal IN jobs.min_salary%TYPE := 0,
p_max_sal IN jobs.max_salary%TYPE := 1000
) -- 매개값 작성 (IN 키워드 사용)
IS
v_cnt NUMBER := 0;
BEGIN
-- 동일한 job_id가 있는지부터 체크
-- 이미 존재한다면 1, 존재하지 않는다면 0을 주고 그것을 v_cnt에 대입할 예정
SELECT
COUNT(*)
INTO v_cnt
FROM
jobs
WHERE
job_id = p_job_id;
-- 중복이 존재하면 1이 나옴
IF v_cnt = 0 THEN -- 없다면 INSERT
INSERT INTO jobs VALUES (
p_job_id,
p_job_title,
p_min_sal,
p_max_sal
);
ELSE -- 있다면 UPDATE
UPDATE jobs
SET
job_title = p_job_title,
min_salary = p_min_sal,
max_salary = p_max_sal
WHERE
job_id = p_job_id;
END IF;
COMMIT;
-- IN키워드를 사용해서 매개값으로 받은 것을 넣어줌
END;
EXEC my_new_job_proc('job2', 'test_job2');
-- 기본값을 지정한 것이 있기 때문에 기본값으로 지정하고 싶으면 매개변수를 넣지 않아도 됨
SELECT
*
FROM
jobs;
IN
OUT
INOUT
CREATE OR REPLACE PROCEDURE my_new_job_proc (
p_job_id IN jobs.job_id%TYPE,
p_job_title IN jobs.job_title%TYPE,
p_min_sal IN jobs.min_salary%TYPE := 0,
p_max_sal IN jobs.max_salary%TYPE := 1000,
p_result OUT VARCHAR
-- 블록 바깥쪽에서 출력을 하기 위한 변수
) -- 매개값 작성 (IN, OUT 키워드 사용)
IS
v_cnt NUMBER := 0;
v_result VARCHAR2(100) := '값이 존재하지 않아서 INSERT로 처리 되었습니다.';
BEGIN
-- 동일한 job_id가 있는지부터 체크
-- 이미 존재한다면 1, 존재하지 않는다면 0을 주고 그것을 v_cnt에 대입할 예정
SELECT
COUNT(*)
INTO v_cnt
FROM
jobs
WHERE
job_id = p_job_id;
-- 중복이 존재하면 1이 나옴
IF v_cnt = 0 THEN -- 없다면 INSERT
INSERT INTO jobs VALUES (
p_job_id,
p_job_title,
p_min_sal,
p_max_sal
);
ELSE -- 있다면 결과를 추출
SELECT
p_job_id || '의 최대 연봉 : ' || max_salary || ', 최소 연봉: ' || min_salary
INTO v_result
-- 매개변수로 입력받은 id가 기존에 같은 값이 있으면 그사람의 최소,최대 연봉의 내용을 v_result에 저장
-- v_result는 조회 결과가 있으면 블록을 나갈 수 있고 조회 결과가 없으면 블록을 나갈 수 없음
FROM jobs
WHERE job_id = p_job_id;
END IF;
-- OUT 매개변수에 결과를 할당
p_result := v_result;
COMMIT;
-- IN키워드를 사용해서 매개값으로 받은 것을 넣어줌
END;
-- 프로시저에 out이 있으면 새로운 PLSQL을 작성해서 출력할 수 있음
DECLARE
str VARCHAR2(100);
BEGIN
my_new_job_proc('job1', 'test_job1', 2000, 7000, str);
-- out변수가 존재하는 프로시저를 부를 때 out되는 값을 받을 변수를 하나 더 전달해야함
dbms_output.put_line(str);
-- 동일한 id가 있으므로 salary정보를 출력
my_new_job_proc('CEO', 'test_CEO', 10000, 70000, str);
dbms_output.put_line(str);
-- 동일한 id가 없으므로 insert
END;
-- IN OUT 동시 처리
CREATE OR REPLACE PROCEDURE my_parameter_test_proc (
p_var1 IN VARCHAR2,
-- IN : 반환 불가이며 한번 값이 할당되면 바뀌지 않음
p_var2 OUT VARCHAR2,
-- OUT변수는 프로시저가 끝나기 전까지 값의 할당이 안됨
-- 프로시저가 끝나야 OUT 가능
p_var3 IN OUT VARCHAR2
-- IN과 OUT이 둘 다 가능
) IS
BEGIN
dbms_output.put_line('p_var1:' || p_var1); -- 출력가능
dbms_output.put_line('p_var2:' || p_var2); -- 값이 전달이 안됨 (공백)
dbms_output.put_line('p_var3:' || p_var3); -- 출력가능 (IN의 성질을 가지고 있음)
-- p_var1 := '결과1';
-- IN으로 작성된 변수는 값을 할당할 수 없음
p_var2 := '결과2';
p_var3 := '결과3';
-- IN OUT은 IN의 성질과 OUT의 기능을 모두 사용가능
dbms_output.put_line('-----------------------------------');
END;
DECLARE
v_var1 VARCHAR2(10) := 'value1';
v_var2 VARCHAR2(10) := 'value2';
v_var3 VARCHAR2(10) := 'value3';
BEGIN
my_parameter_test_proc(v_var1, v_var2, v_var3);
dbms_output.put_line('v_var1 : ' || v_var1);
dbms_output.put_line('v_var2 : ' || v_var2);
dbms_output.put_line('v_var3 : ' || v_var3);
END;
메소드를 강제 종료시킬 때 사용
CREATE OR REPLACE PROCEDURE my_new_job_proc (
p_job_id IN jobs.job_id%TYPE,
p_result OUT VARCHAR
-- 블록 바깥쪽에서 출력을 하기 위한 변수
) -- 매개값 작성 (IN, OUT 키워드 사용)
IS
v_cnt NUMBER := 0;
v_result VARCHAR2(100) := '값이 존재하지 않아서 INSERT로 처리 되었습니다.';
BEGIN
SELECT
COUNT(*)
INTO v_cnt
FROM
jobs
WHERE
job_id = p_job_id;
-- 중복이 존재하면 1이 나옴
IF v_cnt = 0 THEN -- 없다면 RETURN
dbms_output.put_line(p_job_id || '는 테이블에 존재하지 않습니다.');
RETURN;
-- 프로시저 강제 종료
END IF;
-- job_id와 같은게 존재하지 않으면 위의 코드를 작성하며 프로시저를 강제 종료
-- job_id와 같은게 있으면 밑의 코드를 작성
SELECT
p_job_id
|| '의 최대 연봉 : '
|| max_salary
|| ', 최소 연봉: '
|| min_salary
INTO v_result
-- 매개변수로 입력받은 id가 기존에 같은 값이 있으면 그사람의 최소,최대 연봉의 내용을 v_result에 저장
-- v_result는 조회 결과가 있으면 블록을 나갈 수 있고 조회 결과가 없으면 블록을 나갈 수 없음
FROM
jobs
WHERE
job_id = p_job_id;
-- OUT 매개변수에 결과를 할당
p_result := v_result;
COMMIT;
-- IN키워드를 사용해서 매개값으로 받은 것을 넣어줌
END;
DECLARE
str VARCHAR2(100);
BEGIN
my_new_job_proc('CEO', str);
dbms_output.put_line(str);
my_new_job_proc('student', str);
dbms_output.put_line(str);
-- 데이터가 존재하지 않으면 거기서 RETURN을 통해 프로세스를 종료
END;
-- 예외처리
DECLARE
v_num NUMBER := 0;
BEGIN
v_num := 10 / 0;
EXCEPTION
WHEN OTHERS THEN -- 예외처리
dbms_output.put_line('0으로 나눌 수 없습니다.');
dbms_output.put_line('SQL ERROR CODE : ' || sqlcode);
dbms_output.put_line('ERROR MSG : ' || sqlerrm);
END;
PROCEDURE를 이해하기 위한 연습
/*
프로시저명 guguProc
구구단 단수를 전달받아 해당 단수를 출력하는 procedure을 생성하세요.
*/
CREATE OR REPLACE PROCEDURE guguproc (
p_num IN NUMBER
) IS
BEGIN
FOR i IN 1..9 LOOP
dbms_output.put_line(p_num
|| '*'
|| i
|| '='
|| p_num * i);
END LOOP;
END;
DECLARE BEGIN
guguproc(3);
END;
/*
부서번호, 부서명, 작업 flag(I: insert, U:update, D:delete)을 매개변수로 받아
depts 테이블에
각각 INSERT, UPDATE, DELETE 하는 depts_proc 란 이름의 프로시저를 만들어보자.
그리고 정상종료라면 commit, 예외라면 롤백 처리하도록 처리하세요.
*/
DROP TABLE depts;
CREATE TABLE depts AS SELECT department_id, department_name FROM departments;
ALTER TABLE depts ADD CONSTRAINTS depts_pk PRIMARY KEY(department_id);
SELECT * FROM depts;
CREATE OR REPLACE PROCEDURE depts_proc (
d_num IN depts.department_id%TYPE,
d_name IN depts.department_name%TYPE,
flag IN VARCHAR2
) IS
BEGIN
IF flag = 'I' THEN
INSERT INTO depts VALUES (
d_num,
d_name
);
ELSIF flag = 'U' THEN
UPDATE depts
SET
department_name = d_name
WHERE
department_id = d_num;
ELSIF flag = 'D' THEN
DELETE FROM depts
WHERE
department_id = d_num;
ELSE
dbms_output.put_line('친구야 다른 flag를 넣었어');
END IF;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('예외가 발생했어!');
dbms_output.put_line('예외 메세지 : ' || sqlerrm);
ROLLBACK;
END;
EXEC depts_proc(0, 'OK~', 'I');
EXEC depts_proc(10, '아아아아아아', 'U');
SELECT
*
FROM
depts;
/*
employee_id를 입력받아 employees에 존재하면,
근속년수를 out하는 프로시저를 작성하세요. (익명블록에서 프로시저를 실행)
없다면 exception처리하세요
*/
CREATE OR REPLACE PROCEDURE emp_hire_proc (
e_id IN employees.employee_id%TYPE,
e_year OUT NUMBER
) IS
v_cnt NUMBER := 0;
v_hire_date employees.hire_date%TYPE;
BEGIN
SELECT
hire_date
INTO v_hire_date
FROM
employees
WHERE
employee_id = e_id;
e_year := trunc((sysdate - v_hire_date) / 365);
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(e_id || '(은) 는 없는 데이터 입니다.');
COMMIT;
END;
DECLARE
e_year NUMBER;
BEGIN
emp_hire_proc(136, e_year);
dbms_output.put_line(e_year || '년');
END;
/*
프로시저명 - new_emp_proc
employees 테이블의 복사 테이블 emps를 생성합니다.
employee_id, last_name, email, hire_date, job_id를 입력받아
존재하면 이름, 이메일, 입사일, 직업을 update,
없다면 insert하는 merge문을 작성하세요
*/
CREATE OR REPLACE PROCEDURE new_emp_proc(
p_employee_id IN emps.employee_id%TYPE,
p_last_name IN emps.last_name%TYPE,
p_email IN emps.email%TYPE ,
p_hire_date IN emps.hire_date%TYPE,
p_job_id IN emps.job_id%TYPE
)
IS
BEGIN
MERGE INTO emps a
USING (SELECT p_employee_id AS employee_id FROM dual) b
ON (a.employee_id = b.employee_id)
WHEN MATCHED THEN --조건을 만족하면
UPDATE SET
a.last_name = p_last_name,
a.email = p_email,
a.hire_date = p_hire_date,
a.job_id = p_job_id
WHEN NOT MATCHED THEN
INSERT (a.employee_id, a.last_name, a.email, a.hire_date, a.job_id)
VALUES (p_employee_id, p_last_name, p_email, p_hire_date, p_job_id);
END;
EXEC new_emp_proc(100, 'MOON', 'ABC', sysdate, 'test');
EXEC new_emp_proc(100, 'MOO', 'AB', sysdate, 'test234');
SELECT * FROM EMPS;
테이블에 부착한 형태로 INSERT, UPDATE, DELTE 작업이 수행될 때 자동으로 진행
특정 코드가 자동으로 작동하는 형식
CREATE TABLE tbl_test(
id NUMBER(10),
text VARCHAR2(20));
CREATE OR REPLACE TRIGGER trg_test
AFTER DELETE OR UPDATE -- 삭제, 수정 이후에 동작
ON tbl_test -- 부착할 테이블
FOR EACH ROW -- 각 행에 적용
BEGIN dbms_output.put_line('트리거가 동작'); -- 실행되는 코드를 BEGIN ~ END에 넣음
END;
INSERT INTO tbl_test
VALUES(1,'홍길동');
UPDATE tbl_test
SET text = '홍길동2'
WHERE id = 1;
CREATE TABLE tbl_user (
id VARCHAR2(20) PRIMARY KEY,
name VARCHAR2(20),
address VARCHAR2(30)
);
CREATE TABLE tbl_user_backup (
id VARCHAR2(20),
name VARCHAR2(20),
address VARCHAR2(30),
update_date DATE DEFAULT sysdate, -- 변경 시간
m_type VARCHAR2(10), -- 변경 타입
m_user VARCHAR2(20) -- 변경한 사용자
);
:OLD
:NEW
AFTER
CREATE OR REPLACE TRIGGER trg_user_backup AFTER
UPDATE OR DELETE ON tbl_user
FOR EACH ROW
-- update나 delete 이후 동작
DECLARE -- 사용할 변수를 선언
v_type VARCHAR2(10);
BEGIN
IF updating THEN -- UPDATING은 시스템 자체에서 상태에 대한 내용을 지원하는 빌트인 구문
-- UPDATE 상황이라면
v_type := '수정';
ELSIF deleting THEN
v_type := '삭제';
END IF;
-- 실행 구문 시작. (:OLD는 테이블 DELETE, UPDATE가 적용되기 전 기존 데이터이며 변경 전 데이터를 뜻함)
-- UPDATE와 DELETE가 실행되기 전 변경 전의 데이터를 BACKUP테이블에 넣어주겠다는 뜻
INSERT INTO tbl_user_backup VALUES (
:OLD.id,
:OLD.name,
:OLD.address,
sysdate,
v_type,
user()
);
-- tbl_user_backup에 데이터 변경 전의 데이터가 입력
END;
-- tbl_user가 UPDATE와 DELETE가 진행된 후 TRIGGER 진행하는데 trg_user_backup 테이블에 결과를 넣어줌
-- 트리거 동작 확인
INSERT INTO tbl_user VALUES (
'test01',
'admin',
'서울'
);
INSERT INTO tbl_user VALUES (
'test02',
'강아지',
'서울'
);
INSERT INTO tbl_user VALUES (
'test03',
'고양이',
'서울'
);
SELECT
*
FROM
tbl_user;
SELECT
*
FROM
tbl_user_backup;
UPDATE tbl_user
SET
address = '인천'
WHERE
id = 'test01';
DELETE FROM tbl_user
WHERE
id = 'test01';
SELECT
*
FROM
tbl_user_backup;
INSERT, UPDATE, DELETE 작업 이전에 동작하는 트리거를 의미
CREATE OR REPLACE TRIGGER trg_user_insert
BEFORE INSERT
ON tbl_user
FOR EACH ROW
BEGIN
:NEW.name := SUBSTR(:NEW.name, 1, 1) || '**';
END;
-- 새로 입력받은 name을 넣어줌
-- name의 첫글자만 추출하고 이후 **을 붙임
INSERT INTO tbl_user VALUES('test05', '김메롱', '부산');
SELECT * FROM tbl_user;
/*
-- 트리거의 활용
INSERT -> 주문테이블 -> 주문테이블 INSERT 트리거 실행 (물품 테이블 update)
*/
CREATE TABLE order_history(
history_no NUMBER(5) PRIMARY KEY,
order_no NUMBER(5),
product_no NUMBER(5),
total NUMBER(10),
price NUMBER(10)
)
-- 상품
CREATE TABLE product (
product_no NUMBER(5) PRIMARY KEY,
product_name VARCHAR2(20),
total NUMBER(5),
price NUMBER(5)
);
CREATE SEQUENCE order_history_seq NOCACHE;
CREATE SEQUENCE product_seq NOCACHE;
INSERT INTO product VALUES (
product_seq.NEXTVAL,
'피자',
100,
10000
);
INSERT INTO product VALUES (
product_seq.NEXTVAL,
'치킨',
10,
10500
);
INSERT INTO product VALUES (
product_seq.NEXTVAL,
'햄버거',
100,
5000
);
SELECT
*
FROM
product;
-- 주문 히스토리에 데이터가 들어오면 실행
CREATE OR REPLACE TRIGGER trg_order_history
AFTER INSERT ON order_history FOR EACH ROW
-- order_history에 insert가 들어간 후 trigger 발동
DECLARE
v_total NUMBER;
v_product_no NUMBER;
-- 프로시저에서 사용할 변수 생성
BEGIN
dbms_output.put_line('트리거 실행');
SELECT :NEW.total
-- 입력할 자료 중 total이라는 변수를 select
INTO v_total
FROM dual;
v_product_no := :NEW.product_no;
-- 값을 넣어줌
UPDATE product
SET total = total - v_total
WHERE product_no = v_product_no;
END;
INSERT INTO order_history VALUES (
order_history_seq.NEXTVAL,
200,
1,
5,
5000
);
INSERT INTO order_history VALUES (
order_history_seq.NEXTVAL,
200,
1,
1,
15000
);
SELECT
*
FROM
product;