Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

PL/SQL

이번에 다룰 내용은 Pro*C/C++ 에서 PL/SQL을 할 수 있는가? 만약 할 수 있다면 어떻게 하는가? 라는 문제다. 일단 결론부터 말하자면 Pro*C/C++에서도 PL/SQL을 할 수 있다. 특별히 어려운 것 없이 간단히 Anonymous PL/SQL Block, Procedure, Function, Package를 수행할 수 있다. 만약 PL/SQL에 대해서 알지 못하는 독자가 있다면 오라클 매뉴얼이나 PL/SQL관련 서적을 참고하여 먼저 습득하기 바란다. 요즘 서점에 가보면 PL/SQL 서적의 경우 몇권 나와있는것으로 안다. 고급서는 없지만 기초서로는 훌륭한 책들이다.

우리가 지금까지 배워온 내용들로 보건데 지금까지 배운 내용들이면 모든 프로그래밍에서 막힘없이 처리할수 있을 정도의 수준이다. 사실 C/C++과 Oracle과의 연동이라 그래봐야 별거 없지 않는가? 모든 로직은 C/C++에서 처리하고 데이터를 단지 Oracle에서 얻어오고 다시 Oracle에 저장하는 것이다. 그럼 PL/SQL이란 것을 왜 해야 하는가? 라는 의문이 생긴다. 몇가지 이유가 있긴 하지만 반드시 필요한 것은 아니니 독자들이 알아서 선택하면 되는 것이다. 아래의 몇가지 이유를 보자.

Performance

일반적인 성능의 향상으로 받아들일 수는 없는 문제이다. PL/SQL을 쓴다고 해서 반드시 성능의 향상이 일어나는 것이 아니라 어떤 경우에는 PL/SQL을 사용함으로써 성능의 향상을 볼 수도 있다는 의미이다. 예를 들어보자. C/C++에도 for문이 있고 PL/SQL에도 for문이 있다. 두경우에서 for문만을 놓고 본다면 절대 PL/SQL이 빠르다고 할 수 없다. 이 경우의 성능의 향상이란 오라클로부터 대량의 데이터를 패치해오고 C/C++에서 for문을 통해서 데이터를 조작하는 것 보다는 PL/SQL로 이미 조작된 소량의 데이터를 가져오는 것이 더 빠르다는 것이다. 네트웍의 부하라는 문제는 그리 간단히 생각할 문제가 아니기 때문이다. 분명 PL/SQL이 일반 언어와 또다른 재미를 주기는 하지만 단순히 성능의 향상이란 말을 그대로 받아들여 PL/SQL 신봉자가 되기 보다는 한번 더 생각하여 어떤 것이 시스템의 성능에 나을지 고민해 보는 것이 좋겠다.

Integration with Oracle

이 부분은 상당히 유용한 점이라 할 수 있다. 프로그래밍을 하면서 수많은 테이블의 각각의 칼럼 데이터 타입을 기억하지 못하여 할때마다 테이블 정의를 보고 하는 경우가 있다. 하지만 PL/SQL에서는 %TYPE을 사용하여 변수의 데이터 타입을 정의할 수 가 있는 것이다. 이렇게 하면 변수의 데이터 타입을 직접 지정하는 것이 아니라 ‘어느 테이블의 어느 칼럼과 같은 데이터 타입으로 하겠다’ 라고 지정해 주는 것이다. 이부분은 상당히 편리한 기능인 것이다.

Cursor FOR Loops

그 편리하다는 ‘Cursor For Loop’이다. Pro*C/C++뿐만 아니라 PL/SQL에서도 커서를 선언하고 Loop를 수행하는 것은 여간 귀찮은 일이 아니다. 하지만 PL/SQL에서는 특히 편한 방법이 존재하는데 그것이 바로 Cursor For Loop이다. 커서를 선언하고 열고 패치하고 닫는 모든 과정을 자동으로 해주고 커서에서 데이터를 패치할 레코드형의 변수마저 선언할 필요없이 자동으로 만들어 준다. 한번이라도 사용해보면 알겠지만 매우 편리한 기능임에는 틀림없다.

Procedures and Functions and Packages

프로시저, 함수, 패키지는 PL/SQL의 사용에 있어서 없어서는 안될 중요한 요소이다. 간단한 문제인 경우 익명블럭(Anonymous Block)을 사용하면 되지만 자주사용하고 중요한 기능에 대하여는 프로시저나 함수로 만들어 놓고 사용하는 것이 보통이고 PL/SQL에서도 역시 이러한 기능을 제공하고 있다. 특히 C++의 클래스와 비슷한 패키지도 제공한다.

PL/SQL Tables and User-Defined Records

그 밖에 많이 사용하지는 않지만 경우에 따라서 유용한 기능이 있다. C의 배열과 비슷한 테이블 타입과 구조체와 비슷한 레코드 타입이 있다.

그럼 예제를 들어보겠다. 잘 살펴보고 Pro*C/C++에서 PL/SQL을 사용하는 법을 익히도록 하라. 이 예제는 Pro*C/C++에서 하나의 프로시저를 생성하고 그 프로시저를 호출하는 것이다. 생성하는 프로시저의 내용은 부서의 번호를 입력받아서 그 부서의 평균급여, 최고급여, 최저급여를 구해준다. 만약 해당 부서가 없는 경우나 프로시저 수행중 오류가 발생했을 경우에는 각각의 값들은 0으로 셋팅된다.

다음 프로그램을 컴파일 하기 위해서는 Pro*C/C++를 수행할 때 옵션을 주어야 한다.

$ proc mode=ansi parse=none sqlcheck=full userid=scott/tiger plsql.pc 

주의할 것은 sqlcheck=full와 userid=scott/tiger를 반드시 지정해 주어야 한다.

/* --------------------------------------------------------------------------------
파일 이름 : plsql.pc
개발 일자 : 2002-10-28
작성자 : 류명환
-------------------------------------------------------------------------------- */

#include <stdio.h>
#include <stdlib.h>
#include <stdlib.h>

/*
 * $ORACLE_HOME/precomp/public/sqlca.h 를 포함해 주기 위해서
 */
exec sql include sqlca;

/*
 * 호스트 변수 선언
 */
exec sql begin declare section;
    /*
     * 사용자 ID와 패스워드
     */
    char *username;
    char *password;

/*
 * DB와 연동할 호스트 변수
 */
char deptno [128];
int avg_sal;
int max_sal;
int min_sal;

/*
 * Indicator 변수
 */
short ind_deptno;
short ind_avg_sal;
short ind_max_sal;
short ind_min_sal;	
exec sql end declare section;


/*
 * SQL 에러 발생시 실행할 함수
 * 에러코드를 프린트 하고 롤백 후에 접속을 종료한다.
 */
void sql_error (void)
{
    printf ("SQL Error Code : %d\n", sqlca.sqlcode);
    printf ("SQL Error Message : %s\n", sqlca.sqlerrm.sqlerrmc);

    exec sql whenever sqlerror continue;
    exec sql rollback work release;

    exit (EXIT_FAILURE);
}


/*
 * 메인 함수
 */
int main (int argc, char **argv)
{
    username = (char *)strdup ("scott");
    password = (char *)strdup ("tiger");

    /*
     * scott 사용자 계정으로 접속 시도
     * 접속 시도 후에 성공 여부를 위해 에러를 검사한다.
     */
    exec sql
        connect :username identified by :password;
    if (sqlca.sqlcode != 0)
    {
        sql_error ();
    }
    printf ("Connected to Oracle Database\n");

/*
 * DB에 Procedure를 생성한다.
 * Procedure의 내용은 부서번호를 입력받아서
 * 그 부서의 평균급여, 최고급여, 최저급여를 얻어온다.
 */
exec sql
	create or replace
	    procedure dept_salary_info
	    (
		p_deptno in dept.deptno%type,   /* 부서번호 */
		p_salary_avg out number,        /* 평균급여 */
		p_salary_max out number,        /* 최고급여 */
		p_salary_min out number         /* 최저급여 */
	    )
	is
	begin
	    /*
	     * 해당 부서의 평균급여, 최고급여, 최저급여를
	     * 구하여 Out Parameter 변수에 넣어준다.
	     */
	    select	round (avg (sal + nvl (comm, 0)), 0) average,
			round (max (sal + nvl (comm, 0)), 0) maximum,
			round (min (sal + nvl (comm, 0)), 0) minimum
	    into	p_salary_avg,
			p_salary_max,
			p_salary_min
	    from	emp
	    where   deptno = p_deptno;

	    exception
	    /*
	     * 해당 부서가 없는 경우
	     */
	    when no_data_found then
		p_salary_avg := 0;
		p_salary_max := 0;
		p_salary_min := 0;

	    /*
	     * 알 수 없는 에러가 발생한 경우
	     */
	    when others then
		p_salary_avg := 0;
		p_salary_max := 0;
		p_salary_min := 0;

end dept_salary_info;
end-exec;

if (sqlca.sqlcode != 0)
{
	sql_error ();
}

printf ("Procedure created\n\n");

/*
 * 부서번호를 얻어온다.
 */
printf ("Deptno : ");
fgets (deptno, sizeof (deptno) - 1, stdin);
deptno [strlen (deptno) - 1] = '\0';
ind_deptno = 0;

/*
 * 위에서 생성한 프로시저를 호출하여 입력받은
 * 부서의 평균급여, 최고급여, 최저급여를 얻어온다.
 */
exec sql
	execute
	    begin
		dept_salary_info
		(
		    :deptno:ind_deptno,
		    :avg_sal:ind_avg_sal,
		    :max_sal:ind_max_sal,
		    :min_sal:ind_min_sal
		);
	    end;
	end-exec;

if (sqlca.sqlcode != 0)
{
	sql_error ();
}
	
/*
 * 각 값들이 NULL 이라면 0으로 셋팅한다.
 */
if (ind_avg_sal == -1) avg_sal = 0;
if (ind_max_sal == -1) max_sal = 0;
if (ind_min_sal == -1) min_sal = 0;

printf ("Average Salary : %d\n", avg_sal);
printf ("Maximum Salary : %d\n", max_sal);
printf ("Minimum Salary : %d\n", min_sal);

    /*
     * 데이터베이스와 접속을 종료한다.
     */
    exec sql
        rollback work release;
    if (sqlca.sqlcode != 0)
    {
        sql_error ();
    }

    exit (EXIT_SUCCESS);
}