반복문에는 종류가 3가지가 있다.
1. 기본 LOOP 문
2. FOR LOOP 문
3. WHILE LOOP 문
LOOP
반복해야할 실행문장;
EXIT WHEN 탈출조건; -- 탈출조건이 참 이라면 LOOP 를 탈출한다.
END LOOP;
--- *** tbl_looptest_1 테이블에 행을 20000 개를 insert 하는 프로시저를 만든다. *** ---
create or replace procedure pcd_tbl_looptest_1_insert
(p_name in tbl_looptest_1.name%type -- tbl_looptest_1에 있는 name컬럼의 type를 참조한다.
,p_count in number --p_count 에 20000을 넣을 예정
)
is
v_bunho tbl_looptest_1.bunho%type := 0; --타입을 참조하되 초기치를 0으로 한다. -> 변수의 초기화(변수에 값을 처음부터 넣어주기)
begin
LOOP
v_bunho := v_bunho + 1; -- 시퀀스못하나..? 집가서 해보기
EXIT WHEN v_bunho > p_count; -- v_bunho가 p_count(임의로 20000)보다 크면 탈출한다.
insert into tbl_looptest_1(bunho, name) values(v_bunho, p_name||v_bunho); -- 1, 이순신1 / 2, 이순신2 이렇게 증가할 예정
END LOOP;
end pcd_tbl_looptest_1_insert;
exec pcd_tbl_looptest_1_insert('이순신', 20000);
select *
from tbl_looptest_1
order by bunho asc;
select count(*)
from tbl_looptest_1;
rollback;
---- **** 이름이 없는 프로시저(Anonymous Procedure)로 tbl_looptest_1 테이블에 행을 30000 개를 insert 해보겠습니다. **** ---
declare
v_bunho tbl_looptest_1.bunho%type := 0; --변수의 선언 및 초기화
v_name Nvarchar2(10) := '차은우';
begin
LOOP
v_bunho := v_bunho + 1; -- 시퀀스못하나..? 집가서 해보기
EXIT WHEN v_bunho > 30000; -- v_bunho가 p_count(임의로 20000)보다 크면 탈출한다.
insert into tbl_looptest_1(bunho, name) values(v_bunho, v_name||v_bunho); -- 1, 이순신1 / 2, 이순신2 이렇게 증가할 예정
END LOOP;
end;
-- PL/SQL 프로시저가 성공적으로 완료되었습니다. 완료후 바로 들어옴 (exec 없이)
select *
from tbl_looptest_1
order by bunho asc;
select count(*)
from tbl_looptest_1; -- 30000
rollback; --롤백 완료.
-- 어떤 테이블 속에 대량으로 데이터를 넣어주고 테스트 하고 싶을 때 loop문을 사용해서 프로시저로 넣어주면 된다.
--- for loop 문을 더 많이 쓴다 ( 더 간단하기 떄문 )
/*
[문법]
for 변수 in [reverse] 시작값..마지막값 loop
반복해야할 실행문장;
end loop;
-- reverse는 역순으로 실행하는 것.
*/
---- **** 이름이 없는 프로시저(Anonymous Procedure)로 tbl_looptest_1 테이블에 행을 40000 개를 insert 해보겠습니다. **** ---
declare
v_name Nvarchar2(10) := '이혜리';
begin
for i in 1..40000 loop -- 변수 i에 맨처음에는 1 이 들어가고 매번 1씩 증가된 값이 i 에 들어가는데 40000 까지 i 에 들어간다.
insert into tbl_looptest_1(bunho, name) values(i, v_name||i);
end loop;
end;
-- PL/SQL 프로시저가 성공적으로 완료되었습니다. / 완료와 동시에 데이터가 들어간다
declare
v_name Nvarchar2(10) := '유나'; -- 변수의 선언 및 초기화
begin
for i in reverse 1..100 loop -- 변수 i에 맨처음에는 100 이 들어가고 매번 1씩 감소된 값이 i 에 들어가는데 1 까지 i 에 들어간다.
insert into tbl_looptest_1(bunho, name) values(i, v_name||i);
end loop;
end;
select *
from tbl_looptest_1;
select count(*)
from tbl_looptest_1; -- 100
rollback;
/*
[문법]
WHILE 조건 LOOP
반복해야할 실행문장; -- 조건이 참이라면 실행함. 조건이 거짓이 되어지면 반복문을 빠져나간다.
END LOOP;
WHILE NOT 조건 LOOP
반복해야할 실행문장; -- 조건이 참이라면 반복문을 빠져나간다.
END LOOP;
*/
-- not(탈출조건) 탈출조건이 참이라면 전체가 거짓이 되어지므로 반복문을 빠져나간다.
declare
v_cnt number(5) := 1; -- 맨 처음에 1값이 들어옴.
v_name Nvarchar2(10) := '안중근';
begin
while not(v_cnt > 20000) loop -- v_cnt가 20000번을 넘기는 순간 탈출!!
insert into tbl_looptest_1(bunho, name) values(v_cnt, v_name||v_cnt);
v_cnt := v_cnt + 1;
end loop;
end;
select *
from tbl_looptest_1
order by bunho asc;
select count(*)
from tbl_looptest_1;
rollback;
-- 주민번호를 입력받아서 만나이를 알려주는 함수 func_age_3 리팩토링 --
create or replace function func_age_3 (p_jubun in varchar2)
return number
is
error_jubun exception; -- error_jubun 은 사용자가 정의하는 예외절(exception)임을 선언한다.
v_gender_num varchar2(1) := substr(p_jubun, 7, 1);
-- v_gender_num 에는 입력받은 p_jubun 에서 7번째 부터 1개 글자만 넣어준다.
-- 즉, v_gender_num 에는 '1' 또는 '2' 또는 '3' 또는 '4' 가 들어올 것이다.
v_year number(4);
v_age number(3);
i number(2) := 0;
begin
-- if가 존재하면 반드시 end if 쓰기 . 만약 입력받은 p_jubun의 길이가 13자리가 아니라면 잘못된 비밀번호->오류 메시지 띄우기
if length(p_jubun) != 13 then raise error_jubun; -- 13이 아니라면 error_jubun을 띄움
end if;
LOOP
i := i+1;
EXIT WHEN i > 13;
if not(substr(p_jubun, i, 1) between '0' and '9') then -- 0부터 9가 아니라면
raise error_jubun; -- 에러 띄움!!
end if;
END LOOP;
if v_gender_num in('1','2') then v_year := 1900;
elsif v_gender_num in('3','4') then v_year := 2000;
else raise error_jubun; -- 올바르지 않다면 에러 띄우기
end if;
if to_date (to_char(sysdate, 'yyyy') || substr(p_jubun, 3, 4), 'yyyymmdd') - to_date (to_char(sysdate, 'yyyymmdd'), 'yyyymmdd') > 0 -- 오늘보다 클때
then v_age := extract(year from sysdate) - (v_year + to_number(substr(p_jubun, 1, 2))) - 1;
else v_age := extract(year from sysdate) - (v_year + to_number(substr(p_jubun, 1, 2)));
end if;
return v_age; -- 뭘 리턴할건지 적는다
exception -- 개발자가 만든 exception
when error_jubun then -- exception이 error_jubun 이라면
raise_application_error(-20001, '>> 올바르지 않은 주민번호입니다. <<');
-- -20001은 오류 번호로써, 사용자가 정의해주는 EXCEPTION에 대해서는 오류번호를 -20001부터 -20999까지만 사용하도록 오라클에서 비워두었다.
end func_age_3;
select func_age_3('900219211111a')
from dual;
-- >> 올바르지 않은 주민번호입니다. <<
select func_age_3('9002192111111')
from dual;
-- 정상적으로 나이가 나옴
-- 주민번호를 입력받아서 만나이를 알려주는 함수 func_age_4 for문으로 리팩토링 --
create or replace function func_age_4 (p_jubun in varchar2)
return number
is
error_jubun exception; -- error_jubun 은 사용자가 정의하는 예외절(exception)임을 선언한다.
v_gender_num varchar2(1) := substr(p_jubun, 7, 1);
-- v_gender_num 에는 입력받은 p_jubun 에서 7번째 부터 1개 글자만 넣어준다.
-- 즉, v_gender_num 에는 '1' 또는 '2' 또는 '3' 또는 '4' 가 들어올 것이다.
v_year number(4);
v_age number(3);
i number(2) := 0;
begin
-- if가 존재하면 반드시 end if 쓰기 . 만약 입력받은 p_jubun의 길이가 13자리가 아니라면 잘못된 비밀번호->오류 메시지 띄우기
if length(p_jubun) != 13 then raise error_jubun; -- 13이 아니라면 error_jubun을 띄움
end if;
for i in 1..length(p_jubun) loop -- length(p_jubun) = 13
if not(substr(p_jubun, i, 1) between '0' and '9') then -- jubun의 i번째부터 검사
raise error_jubun; -- 에러 띄움!!
end if;
end loop;
if v_gender_num in('1','2') then v_year := 1900;
elsif v_gender_num in('3','4') then v_year := 2000;
else raise error_jubun; -- 올바르지 않다면 에러 띄우기
end if;
if to_date (to_char(sysdate, 'yyyy') || substr(p_jubun, 3, 4), 'yyyymmdd') - to_date (to_char(sysdate, 'yyyymmdd'), 'yyyymmdd') > 0 -- 오늘보다 클때
then v_age := extract(year from sysdate) - (v_year + to_number(substr(p_jubun, 1, 2))) - 1;
else v_age := extract(year from sysdate) - (v_year + to_number(substr(p_jubun, 1, 2)));
end if;
return v_age; -- 뭘 리턴할건지 적는다
exception -- 개발자가 만든 exception
when error_jubun then -- exception이 error_jubun 이라면
raise_application_error(-20001, '>> 올바르지 않은 주민번호입니다. <<');
-- -20001은 오류 번호로써, 사용자가 정의해주는 EXCEPTION에 대해서는 오류번호를 -20001부터 -20999까지만 사용하도록 오라클에서 비워두었다.
end func_age_4;
select func_age_3('900219211111a')
from dual;
-- >> 올바르지 않은 주민번호입니다. <<
select func_age_3('9002192111111')
from dual;
-- 정상적으로 나이가 나옴
-- 주민번호를 입력받아서 만나이를 알려주는 함수 func_age_5 while문으로 리팩토링 --
create or replace function func_age_5 (p_jubun in varchar2)
return number
is
error_jubun exception; -- error_jubun 은 사용자가 정의하는 예외절(exception)임을 선언한다.
v_gender_num varchar2(1) := substr(p_jubun, 7, 1);
-- v_gender_num 에는 입력받은 p_jubun 에서 7번째 부터 1개 글자만 넣어준다.
-- 즉, v_gender_num 에는 '1' 또는 '2' 또는 '3' 또는 '4' 가 들어올 것이다.
v_year number(4);
v_age number(3);
i number(2) := 1;
begin
-- if가 존재하면 반드시 end if 쓰기 . 만약 입력받은 p_jubun의 길이가 13자리가 아니라면 잘못된 비밀번호->오류 메시지 띄우기
if length(p_jubun) != 13 then raise error_jubun; -- 13이 아니라면 error_jubun을 띄움
end if;
WHILE NOT( i = 14 OR (NOT substr(p_jubun, i, 1) between '0' and '9') ) LOOP
-- not: ~면 빠져나간다. -> notnot으로 ~가 아니면 빠져나간다. i = 14까지 해둠으로써 13까지만 증가하도록
-- i는 13까지만..
i := i+1; -- i는 매번 1 증가 -> 14라면 i가 14가 없으므로 실패!!
END LOOP;
--
-- WHILE NOT( i = 13 ) LOOP
-- i := i+1;
-- if not(substr(p_jubun, i, 1) between '0' and '9') then -- jubun의 i번째부터 검사
-- raise error_jubun; -- 에러 띄움!!
-- end if;
-- END LOOP;
-- raise error_jubun; 해줘야 됨
IF i != 14 THEN raise error_jubun;
END IF;
if v_gender_num in('1','2') then v_year := 1900;
elsif v_gender_num in('3','4') then v_year := 2000;
else raise error_jubun; -- 올바르지 않다면 에러 띄우기
end if;
if to_date (to_char(sysdate, 'yyyy') || substr(p_jubun, 3, 4), 'yyyymmdd') - to_date (to_char(sysdate, 'yyyymmdd'), 'yyyymmdd') > 0 -- 오늘보다 클때
then v_age := extract(year from sysdate) - (v_year + to_number(substr(p_jubun, 1, 2))) - 1;
else v_age := extract(year from sysdate) - (v_year + to_number(substr(p_jubun, 1, 2)));
end if;
return v_age; -- 뭘 리턴할건지 적는다
exception -- 개발자가 만든 exception
when error_jubun then -- exception이 error_jubun 이라면
raise_application_error(-20001, '>> 올바르지 않은 주민번호입니다. <<');
-- -20001은 오류 번호로써, 사용자가 정의해주는 EXCEPTION에 대해서는 오류번호를 -20001부터 -20999까지만 사용하도록 오라클에서 비워두었다.
end func_age_5;
select func_age_5('900219211111a')
from dual;
-- >> 올바르지 않은 주민번호입니다. <<
select func_age_5('9002192111111')
from dual;
/*
=== tbl_member_test1 테이블에 insert 할 수 있는 요일명과 시간을 제한해 두겠습니다. ===
tbl_member_test1 테이블에 insert 할 수 있는 요일명은 월,화,수,목,금 만 가능하며
또한 월,화,수,목,금 중에 오후 2시 부터 오후 5시 이전까지만(오후 5시 정각은 안돼요) insert 가 가능하도록 하고자 한다.
만약에 insert 가 불가한 요일명(토,일)이거나 불가한 시간대에 insert 를 시도하면
'영업시간(월~금 15:00 ~ 16:59:59 까지) 아니므로 입력불가함!!' 이라는 오류메시지가 뜨도록 한다.
*/
SELECT TO_CHAR(sysdate,'d') -- sysdate의 주의 일요일부터(지금은 2024/3/4) sysdate(지금은 2024/3/4) 까지 며칠째인지를 알려주는 것(요일을 알 수 있음)
-- '1'(일요일) '2'(월요일) '3'(화요일) '4'(수요일) '5'(목) '6'(금) '7'(토)
FROM dual;
create or replace procedure pcd_tbl_member_test1_insert
(p_userid IN tbl_member_test1.userid%type
,p_passwd IN tbl_member_test1.passwd%type
,p_name IN tbl_member_test1.name%type)
is
v_passwd_length number(2);
v_ch varchar2(1);
v_flag_alphabet number(1) := 0;
v_flag_number number(1) := 0;
v_flag_special number(1) := 0;
error_insert exception;
error_dayTime exception;
begin
-- 입력(insert)이 불가한 요일명과 시간대를 알아봅니다. --
if( to_char(sysdate, 'd') in('1','7') OR
to_number(to_char(sysdate, 'hh24')) < 14 OR
to_number(to_char(sysdate, 'hh24')) > 16 ) then
raise error_dayTime;
else -- 입력(insert)이 가능한 요일명과 시간대 이라면 암호를 검사하겠다.
v_passwd_length := length(p_passwd);
if( v_passwd_length < 5 or v_passwd_length > 20 ) then
raise error_insert; -- 사용자가 정의하는 예외절(exception)을 구동시켜라.
else
for i in 1..v_passwd_length loop
v_ch := substr(p_passwd, i, 1);
if(v_ch between 'A' and 'Z') OR (v_ch between 'a' and 'z') then -- 영문자 이라면
v_flag_alphabet := 1;
elsif(v_ch between '0' and '9') then -- 숫자 이라면
v_flag_number := 1;
else -- 특수문자이라면
v_flag_special := 1;
end if;
end loop; -- end of for loop
if(v_flag_alphabet * v_flag_number * v_flag_special = 1) then
insert into tbl_member_test1(userid, passwd, name) values(p_userid, p_passwd, p_name);
else
raise error_insert; -- 사용자가 정의하는 예외절(exception)을 구동시켜라.
end if;
end if;
end if;
exception
when error_dayTime then
raise_application_error(-20003, '>> 영업시간(월~금 14:00 ~ 16:59:59 까지)이 아니므로 입력불가함!! <<');
when error_insert then
raise_application_error(-20002, '>> 암호는 최소 5글자 이상이면서 영문자 및 숫자 및 특수기호가 혼합되어져야 합니다. <<');
end pcd_tbl_member_test1_insert;
-- Procedure PCD_TBL_MEMBER_TEST1_INSERT이(가) 컴파일되었습니다.
exec pcd_tbl_member_test1_insert('eomjh','qwer1234$','엄정화');
/*
오류 보고 -
ORA-20003: >> 영업시간(월~금 14:00 ~ 16:59:59 까지)이 아니므로 입력불가함!! <<
*/
/*
-- 현재시각은 월요일 오후 2시 1분이다 (이거 시간대가 맞아야되므로 테스트할때는
if( to_char(sysdate, 'd') in('1','7') OR
to_number(to_char(sysdate, 'hh24')) < 14 OR
to_number(to_char(sysdate, 'hh24')) > 16 ) then
raise error_dayTime;
이 부분의 시간대 수정
*/
exec pcd_tbl_member_test1_insert('eomjh','qwer1234$','엄정화');
-- PL/SQL 프로시저가 성공적으로 완료되었습니다.
commit;
select *
from tbl_member_test1;
/*
hongkd qwer1234$ 홍길동
eomjh qwer1234$ 엄정화
*/
FOR LOOP CURSOR 문을 사용하면
커서의 OPEN, 커서의 FETCH, 커서의 CLOSE 가 자동적으로 발생되어지기 때문에
우리는 커서의 OPEN, 커서의 FETCH, 커서의 CLOSE 문장을 기술할 필요가 없다.
그래서 3단계, 4단계를 할 필요가 없음.
-- ※ 형식
-- FOR 변수명(select 되어진 행의 정보가 담기는 변수) IN 커서명 LOOP
-- 실행문장;
-- END LOOP;
create or replace procedure pcd_employees_deptid_forcursor
(p_department_id IN employees.department_id%type)
is
-- 1단계 == CURSOR의 선언(정의)
CURSOR cur_empinfo
IS
with E as
(
select department_id
, employee_id
, first_name || ' ' || last_name AS ENAME
, to_char(hire_date, 'yyyy-mm-dd') AS HIREDATE
, func_gender(jubun) AS GENDER
, func_age(jubun) AS AGE
from employees
where department_id = p_department_id -- pk/unique가 아니기 때문에 복수 행수가 나온다. (유일한 값이 아니기 때문)
)
select E.department_id, D.department_name, E.employee_id, E.ename, E.hiredate, E.gender, E.age
from departments D right join E
on D.department_id = E.department_id;
v_fetch_count number := 0;
begin
/*
-- 이것이 2단계
FOR 변수명(select 되어진 행의 정보가 담기는 변수) IN 커서명 LOOP
실행문장;
END LOOP;
*/
FOR v_rcd IN cur_empinfo LOOP -- select 되어진 결과물이 v_rcd 변수에 한행 들어온다.
v_fetch_count := cur_empinfo%ROWCOUNT; --커서명 rowcount가 fetch되어진 갯수(1,2,3,4)를 말한다.
if ( v_fetch_count = 1) then
dbms_output.put_line( lpad('-',60,'-') );
dbms_output.put_line( '부서번호 부서명 사원번호 사원명 입사일자 성별 나이' );
dbms_output.put_line( lpad('-',60,'-') );
end if;
dbms_output.put_line( v_rcd.department_id || ' ' ||
v_rcd.department_name || ' ' ||
v_rcd.employee_id || ' ' ||
v_rcd.ename || ' ' ||
v_rcd.hiredate || ' ' ||
v_rcd.gender || ' ' ||
v_rcd.age );
END LOOP;
if(v_fetch_count = 0) then
dbms_output.put_line('>>> 부서번호' || p_department_id || '은 존재하지 않습니다.<<' );
ELSE
dbms_output.put_line(' ');
dbms_output.put_line(' >> 조회된 행의 개수 : ' || v_fetch_count || '개 <<');
end if;
end pcd_employees_deptid_forcursor;
exec pcd_employees_deptid_forcursor(30);
/*
------------------------------------------------------------
부서번호 부서명 사원번호 사원명 입사일자 성별 나이
------------------------------------------------------------
30 Purchasing 114 Den Raphaely 2002-12-07 여 56
30 Purchasing 115 Alexander Khoo 2003-05-18 남 62
30 Purchasing 116 Shelli Baida 2005-12-24 남 61
30 Purchasing 117 Sigal Tobias 2005-07-24 여 62
30 Purchasing 118 Guy Himuro 2006-11-15 남 45
30 Purchasing 119 Karen Colmenares 2007-08-10 남 44
>> 조회된 행의 개수 : 6개 <<
*/
exec pcd_employees_deptid_forcursor(8888);
-- >>> 부서번호8888은 존재하지 않습니다.<<
--------- 커서 for loop---------------------------------
--커서 선언/열고/petch/닫음
-- 근데 여기서 열고,가져오고, 닫는 과정 생략
DECLARE
CURSOR cur_RebelionRider IS
SELECT employee_id, first_name, last_name FROM employees
WHERE employee_id > 200; -- 커서 선언
BEGIN
FOR L_IDX IN cur_RebelionRider
LOOP
DBMS_OUTPUT.put_line(L_IDX.employee_id || ' ' || L_IDX.first_name || ' ' || L_IDX.last_name);
-- 커서가 반환한 데이터를 화면에 인쇄
END LOOP;
END;
-- 커서 for loop는 숫자 for loop와 달리 실행 횟수를 결정하는 최소 또는 최대 범위가 없다
-- 그럼 몇 번이나 실행될까?
-- 이 루프는 지정된 커서에서 반환된 '각 행'에 대해 실행된다.
-- 반환할 행이 없거나, 예외가 발생하면 종료된다.
-- salary가 ~이상인 사람을 반환하는 프로시저
--
create or replace procedure pcd_salInfo
(p_salary IN employees.salary%type)
is
CURSOR cur_empinfo
is
select employee_id, first_name || ' ' || last_name as ename, salary
from employees
where salary > p_salary;
v_fetch_count number := 0;
begin
for v_rcd IN cur_empinfo LOOP
v_fetch_count := cur_empinfo%ROWCOUNT;
if ( v_fetch_count = 1) then
dbms_output.put_line( lpad('-', 40, '-') );
dbms_output.put_line( '사원번호 사원명 월급' );
dbms_output.put_line( lpad('-', 40, '-') );
end if;
dbms_output.put_line( v_rcd.employee_id || ' ' ||
v_rcd.ename || ' ' ||
v_rcd.salary
);
END LOOP;
IF(v_fetch_count = 0) then
dbms_output.put_line('>>> 월급이 ' || p_salary || '보다 큰 사람은 존재하지 않습니다.');
ELSE
dbms_output.put_line(' ');
dbms_output.put_line(' >> 조회된 행의 개수 : ' || v_fetch_count || '개 <<');
END IF;
end pcd_salInfo;
exec pcd_salInfo(7000);
보기>DBMS 출력> 더하기 버튼> 데이터베이스 선택> exec 프로시저명(매개변수) 실행
----------------------------------------
사원번호 사원명 월급
----------------------------------------
101 Neena Kochhar 17000
102 Lex De Haan 17000
103 Alexander Hunold 9000
108 Nancy Greenberg 12008
109 Daniel Faviet 9000
110 John Chen 8200
111 Ismael Sciarra 7700
112 Jose Manuel Urman 7800
114 Den Raphaely 11000
120 Matthew Weiss 8000
121 Adam Fripp 8200
122 Payam Kaufling 7900
145 John Russell 14000
146 Karen Partners 13500
147 Alberto Errazuriz 12000
148 Gerald Cambrault 11000
149 Eleni Zlotkey 10500
150 Peter Tucker 10000
151 David Bernstein 9500
152 Peter Hall 9000
153 Christopher Olsen 8000
154 Nanette Cambrault 7500
156 Janette King 10000
157 Patrick Sully 9500
158 Allan McEwen 9000
159 Lindsey Smith 8000
160 Louise Doran 7500
163 Danielle Greene 9500
164 Mattea Marvins 7200
169 Harrison Bloom 10000
170 Tayler Fox 9600
171 William Smith 7400
172 Elizabeth Bates 7300
174 Ellen Abel 11000
175 Alyssa Hutton 8800
176 Jonathon Taylor 8600
177 Jack Livingston 8400
201 Michael Hartstein 13000
204 Hermann Baer 10000
205 Shelley Higgins 12008
206 William Gietz 8300
162 Clara Vishney 10500
168 Lisa Ozer 11500
>> 조회된 행의 개수 : 43개 <<