**저장 프로시저** 또는 **스토어드 프로시저**(stored procedure)는 일련의 쿼리를 마치 하나의 함수처럼 실행하기 위한 쿼리의 집합이다. 데이터베이스에 대한 일련의 작업을 정리한 절차를 관계형 데이터베이스 관리 시스템에 저장한(지속성) 것으로, **영구저장모듈**(Persistent Storage Module)이라고도 불린다.

개요
--

데이터베이스 언어 표준 SQL에서는 SQL / PSM 기준으로 책정되어 있다. 벤더(제조사) 각사 모두 정적, 동적 SQL에 커서 처리 및 제어 구문, 예외 처리 등을 포함한 사양의 확장 언어로 절차를 설명할 수 있는 DBMS를 제공하는 경우가 많다. 또한 C 언어로 작성된 컴파일한 외부 모듈(공유 라이브러리) 및 Java 클래스 라이브러리에서 함수나 클래스 메소드를 호출하는 것으로 실현하는 ‘외부 프로시저’ 기능을 구현하는 것도 있다.
저장프로시저를 사용하여 다음과 같은 장점이 있다.

1. 하나의 요청으로 여러 SQL문을 실행할 수 있다. (네트워크에 대한 부하를 줄일 수 있다.)
2. 미리 구문 분석 및 내부 중간 코드로 변환을 끝내야 하므로 처리 시간이 줄어든다.
3. 데이터베이스 트리거와 결합하여 복잡한 규칙에 의한 데이터의 참조무결성 유지가 가능하게 된다. 간단히 말하면 응용 프로그램 측 로직을 가지지 않고도 데이터베이스의 데이터 앞뒤가 맞게 될 수 있다.
4. JAVA 등의 호스트 언어와 SQL 문장이 확실하게 분리된 소스 코드의 전망이 좋아지는 것, 또한 웹사이트 등 운용 중에도 저장프로시저의 교체에 의한 수정이 가능하기 때문에 보수성이 뛰어나다.

저장프로시저를 많이 사용하면 다음과 같은 단점이 있다.

1. 데이터베이스 제품에 대해 설명하는 구문 규칙이 SQL / PSM 표준과의 호환성이 낮기 때문에 코드 자산으로의 재사용성이 나쁘다.
2. 비즈니스 로직의 일부로 사용하는 경우 업무의 사양 변경 시 외부 응용 프로그램과 함께 저장프로시저의 정의를 변경할 필요가 있다. 이때 불필요한 수고와 변경 실수에 의한 장애를 발생시킬 가능성이 있다.

DBMS별 예제
--------

### 오라클

다음은 PL/SQL로 구현한 오라클 저장 프로시저의 예이다.

```
CREATE OR REPLACE PROCEDURE helloworld (str IN VARCHAR2)
  AS
     hw VARCHAR2 (100) : = 'Hello World!';
  BEGIN
     DBMS_OUTPUT. PUT_LINE ( 'Hello World!');
     DBMS_OUTPUT. PUT_LINE ( 'VARIABLE hw ='| | hw);
     DBMS_OUTPUT. PUT_LINE ( 'Parameter str ='| | str);
  END;
  /
```

### MySQL

MySQL은 버전 5.0 이후 표준 SQL 규격 저장프로시저를 지원하고 있다. 다음은 함수와 프로시저에서 동등한 처리를 하는 예를 보여준다.

#### 함수 예1 (DB 개입 없음)

(1) 정의

```
drop function if exists DecToNshin; -- 존재한다면 삭제
delimiter // -- 마침 기호의 변경
create function DecToNshin
(dec\_num int,
 n\_shin tinyint)
 returns varchar(32)
--
-- 10진수→n진수
--
begin
 declare ltr char(36) default '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
 declare w\_dec\_num int;
 declare amari int;
 declare w\_DecToNshin varchar(32);
 set w\_DecToNshin='';
 set w\_dec\_num=dec\_num;
 while w\_dec\_num>=n\_shin do
 set amari=mod(w\_dec\_num,n\_shin);
 set w\_DecToNshin=concat(substr(ltr,amari+1,1),w\_DecToNshin);
 set w\_dec\_num=w\_dec\_num div n\_shin;
 end while;
 set w\_DecToNshin=concat(substr(ltr,w\_dec\_num+1,1),w\_DecToNshin);
 return w\_DecToNshin;
end;
//
delimiter ; -- 마침기호 취소

```

(2) 실행

```
SELECT DecToNshin(100,16); -- 100을 16진수로 하면?

```

#### 프로시저의 예1 (DB 개입 없음)

(1) 정의

```
drop procedure if exists DecToNshin; -- 존재한다면 삭제
delimiter // -- 마침기호 변경
create procedure DecToNshin
(in dec\_num int,
 in n\_shin tinyint,
 out w\_DecToNshin varchar(32))
--
-- 10진수→n진수
--
begin
 declare ltr char(36) default '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
 declare w\_dec\_num int;
 declare amari int;
 set w\_DecToNshin='';
 set w\_dec\_num=dec\_num;
 while w\_dec\_num>=n\_shin do
 set amari=mod(w\_dec\_num,n\_shin);
 set w\_DecToNshin=concat(substr(ltr,amari+1,1),w\_DecToNshin);
 set w\_dec\_num=w\_dec\_num div n\_shin;
 end while;
 set w\_DecToNshin=concat(substr(ltr,w\_dec\_num+1,1),w\_DecToNshin);
end;
//
delimiter ; -- 마침 기호 취소

```

(2) 실행

```
CALL DecToNshin(100,16,@RSLT); -- 100을 16진수로 하면?
SELECT @RSLT; -- 마침 표시

```

#### 함수 예제2 (DB 작업 있음)

(1) 테이블 정의 및 데이터

```
create table gengou
(bgn\_date date,
 end\_date date,
 gengou\_name varchar(4));
insert into gengou values
('1868-01-01','1912-07-30','메이지'),
('1912-07-30','1926-12-25','다이쇼'),
('1926-12-25','1989-01-07','쇼'),
('1989-01-08','2019-04-30','헤이세이');

```

(2) 정의

```
drop function if exists cng\_gengou; -- 존재한다면 삭제
delimiter // -- 마침기호 변경
create function cng\_gengou
(p\_seireki\_date date)
 returns varchar(30)
--
-- 서기→일본력
--
begin
 declare w\_rcnt int default 0; -- 행 line 확인
 declare w\_gengou\_name varchar(4) default ''; -- 연호명
 declare w\_bgn\_date date; -- 시작일
 declare w\_nensuu tinyint default 0; -- 일본력 년
 declare w\_rslt varchar(30) default ''; -- 결과
-- 
 select count(\*)
 into w\_rcnt
 from gengou
 where bgn\_date<=p\_seireki\_date and end\_date>=p\_seireki\_date;
 if w\_rcnt>1 then
 set w\_rslt='2행 이상 존재하기 때문에 지원하지 않음';
 else
 select gengou\_name,bgn\_date
 into w\_gengou\_name,w\_bgn\_date
 from gengou
 where bgn\_date<=p\_seireki\_date and end\_date>=p\_seireki\_date;
 if length(w\_gengou\_name)>0 then
 set w\_nensuu=year(p\_seireki\_date)-year(w\_bgn\_date)+1;
 set w\_rslt=concat(w\_gengou\_name,cast(w\_nensuu as char(2)));
 else
 set w\_rslt='change unsuccessful';
 end if;
 end if;
 return w\_rslt;
end;
//
delimiter ; -- 종결기호 취소

```

(3) 실행

```
select cng\_gengou('2006-07-19');

```

#### 프로시저 예제2 (DB 작업 있음, 비켜서 작업)

(1) 테이블 정의 및 데이터

"함수 예제2 (DB 작업 있음)"와 같다.
(2) 정의

```
drop procedure if exists cng\_gengou; -- 존재한다면 삭제
delimiter // -- 마침기호 변경
create procedure cng\_gengou
(in p\_seireki\_date date,
 out p\_rslt varchar(30))
--
-- 서기→일본력
--
begin
 declare w\_rcnt int default 0; -- 행 line 확인
 declare w\_gengou\_name varchar(4) default ''; -- 연호이름
 declare w\_bgn\_date date; -- 시작일
 declare w\_nensuu tinyint default 0; -- 일본력 년
-- 
 select count(\*)
 into w\_rcnt
 from gengou
 where bgn\_date<=p\_seireki\_date and end\_date>=p\_seireki\_date;
 if w\_rcnt>1 then
 set p\_rslt='2행 이상 존재하기 때문에 지원하지 않음';
 else
 select gengou\_name,bgn\_date
 into w\_gengou\_name,w\_bgn\_date
 from gengou
 where bgn\_date<=p\_seireki\_date and end\_date>=p\_seireki\_date;
 if length(w\_gengou\_name)>0 then
 set w\_nensuu=year(p\_seireki\_date)-year(w\_bgn\_date)+1;
 set p\_rslt=concat(w\_gengou\_name,cast(w\_nensuu as char(2)));
 else
 set p\_rslt='change unsuccessful';
 end if;
 end if;
end;
//
delimiter ; -- 마침기호 취소

```

(3) 실행

```
call cng\_gengou('2006-07-19',@rslt);
select @rslt;

```

#### 프로시저 예제3 (DB 작업 있음, 커서 작업)

(1) 테이블 정의 및 데이터

"함수 예제2 (DB작업 있음)"와 같다.
(2) 정의

```
drop procedure if exists cng\_gengou; -- 존재한다면 삭제
delimiter // -- 마침기호 변경
create procedure cng\_gengou
(in p\_seireki\_date date,
 out p\_rslt varchar(30))
--
-- 서기→일본력
--
begin
 declare w\_rcnt int default 0; -- 행 수 확인
 declare w\_gengou\_name varchar(4) default ''; -- 연호이름
 declare w\_bgn\_date date; -- 시작일
 declare w\_nensuu tinyint default 0; -- 일본력 년
 declare eod tinyint;
-- 커서 선언
 declare cr1 cursor for
 select gengou\_name,bgn\_date
 from gengou
 where bgn\_date<=p\_seireki\_date and end\_date>=p\_seireki\_date;
-- 예외 선언
 declare continue handler for not found set eod=1;

 set eod=0;
 open cr1;
 fetch cr1 into w\_gengou\_name,w\_bgn\_date;
 while eod=0 do
 set w\_rcnt=w\_rcnt+1;
 if w\_rcnt>1 then
 set p\_rslt='2행 이상 존재하기 때문에 지원하지 않음';
 else
 if length(w\_gengou\_name)>0 then
 set w\_nensuu=year(p\_seireki\_date)-year(w\_bgn\_date)+1;
 set p\_rslt=concat(w\_gengou\_name,cast(w\_nensuu as char(2)));
 else
 set p\_rslt='change unsuccessful';
 end if;
 end if;
 fetch cr1 into w\_gengou\_name,w\_bgn\_date;
 end while;
 close cr1;
end;
//
delimiter ; -- 종결기호 취소

```

(3) 실행

```
call cng\_gengou('2006-08-10',@rslt);
select @rslt;

```