database.sarang.net
UserID
Passwd
Database
DBMS
MySQL
ㆍPostgreSQL
Firebird
Oracle
Informix
Sybase
MS-SQL
DB2
Cache
CUBRID
LDAP
ALTIBASE
Tibero
DB 문서들
스터디
Community
공지사항
자유게시판
구인|구직
DSN 갤러리
도움주신분들
Admin
운영게시판
최근게시물
PostgreSQL Tutorials 3846 게시물 읽기
 News | Q&A | Columns | Tutorials | Devel | Files | Links
No. 3846
PL/pgSQL - SQL procedural language (3)
작성자
정재익(advance)
작성일
2002-01-07 03:55
조회수
15,061

24.3. Trigger Procedures

 

PL/pgSQL는 trigger procedure를 정의 할 수 있다. 이를 생성하는 방법은 일반적인 것과 같이 CREATE FUNCTION를 사용하고 그 함수에 매개변수없이 리턴타입을 OPAQUE로 준다.

 

trigger procedure로 함수를 사용하는데는 Postgres에서만의 몇가지 세부사항이 있다.

 

첫째는 최상위 선언부(declare section)에 자동으로 생성되는 몇가지 변수들이 있다.

 

NEW

 

자료형이 RECORD인 변수로 ROW level trigger에서 INSERT/UPDATE 작업 할 때 데이터베이스의 새로운 행의 값을 갖는다.

 

OLD

 

자료형이 RECORD인 변수로 ROW level trigger에서 INSERT/UPDATE 작업 할 때 데이터베이스의 과거 행의 값을 갖는다.

 

TG_NAME

 

자료형이 name인 변수로 실제 작동할 trigger의 이름을 갖고 있다.

 

TG_WHEN

 

자료형이 test인 문자열로 trigger의 정의에 따라 "BEFORE" 또는 "AFTER"를 갖는다.

 

TG_LEVEL

 

자료형이 test인 문자열로 trigger의 정의에 따라 "ROW" 또는 "STATEMENT"를 갖는다.

 

TG_OP

 

자료형이 test인 문자열로 trigger가 실제 수행하는 작업을 나타내는데 "INSERT", "UPDATE" 또는 "DELETE"를 갖는다.

 

TG_RELID

 

자료형이 oid인 변수로 trigger를 촉발시킨 테이블의 Object ID를 갖는다.

 

TG_RELNAME

 

자료형이 name으로 trigger를 촉발시킨 테이블의 이름을 갖는다.

 

TG_NARGS

 

자료형이 integer으로 CREATE TRIGGER 문으로 생성된 trigger procedure로 넘겨진 매개변수의 숫자이다.

 

TG_ARGV[]

 

자료형이 text인 배열로 CREATE TRIGGER 문의 실제 매개변수 값이다. 배열의 첫째 요소는 0번재 배열에 있고, 만일 배열의 인덱스 값이 0보다 작거나 tg_nargs보다 크면 해당 배열은 NULL을 반환한다.

 

두번째는 trigger procedure는 NULL 또는 trigger를 동작시킨 테이블과 동인한 구조의 레코드/행을 반납해야한다. "AFTER"로 동작된 trigger는 항상 NULL값을 반납한다. "BEFORE"로 동작한 trigger는 NULL을 반환하면 이 실제의 행에 대한 동작을 건너 뛰도록 신호를 보낸다. 그렇지 않으면 반환된 레코드/행의 값이 삽입되거나 갱신된 행의 값을 덮어쒸울 것이다. signal값을 직접적으로 "NEW"에 기록해서 반환하거나 완전히 새로운 레코드/행을 만들어서 반환하는 것이 가능하다.

 

Example 24-1. PL/pgSQL Trigger Procedure 예

 

이 trigger는 테이블에 행 하나가 삽입되거나 갱신될 때마다 현재 사용자 이름과 행에 있는 시간을 확인한다. 그리고 주어진 종업원의 이름과 월급이 양수값인지 확인한다.

CREATE TABLE emp (
    empname text,
    salary integer,
    last_date timestamp,
    last_user text
);

CREATE FUNCTION emp_stamp () RETURNS OPAQUE AS '
    BEGIN
        -- 주어진 empname과 salary를 확인
        IF NEW.empname ISNULL THEN
            RAISE EXCEPTION ''empname은 NULL값이 될 수 없다.'';
        END IF;
        IF NEW.salary ISNULL THEN
            RAISE EXCEPTION ''% 는 NULL로 된 salary를 가질 수 없다'', NEW.empname;
        END IF;

        -- 월급을 받고 있는 사람 중에 종업원인가?
        IF NEW.salary < 0 THEN
            RAISE EXCEPTION ''% 는 음수의 salary를 가질 수 없다.'', NEW.empname;
        END IF;

        -- 언제 누가 월급을 조정했나?
        NEW.last_date := ''now'';
        NEW.last_user := current_user;
        RETURN NEW;
    END;
' LANGUAGE 'plpgsql';

CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
    FOR EACH ROW EXECUTE PROCEDURE emp_stamp();

24.4. 예제

 

여기에 PL/pgSQL로 함수를 만드는 것이 얼마나 쉬운지를 보여주는 예가 있다.

PL/pgSQL를 사용하여 함수를 만들 때 한가지 귀찮은 것은 작은 따옴표를 사용하는 것이다. CREATE FUNCTION에 사용하는 함수의 소스는 문자열형이어야 한다. 그렇기 때문에 문자열형 내부의 작은 따옴표는 두번을 사용하던지 ('') 아니면 역슬레쉬에 이은 작은 따옴표 (')이어야 한다. PL/pgSQL개발자는 여전히 좀더 우아한 방법을 찾고 있고 그동안은 (괴롭겠지만) 아래의 예와 같이 두개의 작은 따옴표를 써야한다. 미래에 어떤 해결점이 나오던지 간에 현재의 사용하는 형태는 미래에도 호환이 될것이다.

 

다른 상황에서 작은 따옴표에서 벗어나는 방법에 대한 자세한 설명은 24.5.1.1에 있으니 참고 바란다.

 

Example 24-2. 정수를 증가시키기 위한 간단한 PL/pgSQL 함수

 

이 함수는 정수를 받아서 1을 증가시키고, 그 증가된 값을 반환한다.

CREATE FUNCTION add_one (integer) RETURNS integer AS '
    BEGIN
        RETURN  + 1;
    END;
' LANGUAGE 'plpgsql';

[b]Example 24-3. 문자열을 합치는 간단한 PL/pgSQL 함수[/b]

이 함수는 두 문자열을 받아들여서 하나로 합친다음 그 것을 반환한다.

CREATE FUNCTION concat_text (text, text) RETURNS text AS '
    BEGIN
        RETURN  || ;
    END;
' LANGUAGE 'plpgsql';

Example 24-4. 복합형의 PL/pgSQL 함수

 

이 예에서 EMP라는 테이블과 매개변수로 정수를 받아서 참 또는 거짓을 반환한다. 만약에 EMP테이블의 salary 필드가 NULL이면 "f"를 반한하고 그렇지 않으면 그 필드의 값과 매개변수로 받은 정수값을 비교하여 그 결과값을 반환한다. ( t 또는 f )

CREATE FUNCTION c_overpaid (EMP, integer) RETURNS boolean AS '
    DECLARE
        emprec ALIAS FOR ;
        sallim ALIAS FOR ;
    BEGIN
        IF emprec.salary ISNULL THEN
            RETURN ''f'';
        END IF;
        RETURN emprec.salary > sallim;
    END;
' LANGUAGE 'plpgsql';

24.5. Oracle PL/SQL에서 PG/pgSQL로 변환

 

글쓴이: Roberto Mello (<rmello@fslc.usu.edu>)

 

이 장은 Oracle의 PL/SQL과 PostgreSQL의 PL/pgSQL 사이에 다른 점을 설명하고자 한다. Oracle PL/SQL에서 PG/pgSQL로 변환하고자 하는 개발자들에게 도움이 되었으면한다. 여기있는 대부분의 코드는 Roberto Mello가 2000년 여름에 OpenForce Inc.와 internship을 가졌을 때 PostgreSQL로 변환한 ArsDigita Clickstream에서 거의 가져왔다.

 

PL/pgSQL는 PL/SQL과 외양면에서 상당히 비슷하다. 블록으로 구조화 되어있고 엄격한 언어이다. (모든 변수는 반드시 선언되어져야한다.) PL/SQL은 PostgreSQL보다 더 많은 기능들을 가지고 있긴 하지만, PL/pgSQL는 우수한 기능을 가지고 있고 꾸준하게 향상되어왔다.

 

24.5.1. 주요 차이점

 

변환을 하고자 할 때 유념해야 할 것은...

 

PostgreSQL에는 기본 parameter가 없다.

PostgreSQL에서는 함수를 loverload할 수 있다. 이 것은 종종 기본 parameter가 없는 것에 대한 대안으로 사용된다.

할당(대입), 반복문 그리고 조건문은 비슷하다.

PostgreSQL에서는 커서를 필요로 하지 않는다. 그저 쿼리를 FOR 문에 넣으면 된다.

(아래의 예제 참조)

PostgreSQL에서는 작은 따옴표 사용에 주의 해야한다.

(24.5.1.1 참조)

 

24.5.1.1. 작은 따옴표를 잘 사용하는 법

PostgreSQL에서 함수를 선언할 때 작은 따옴표를 잘 사용해야한다. 때때로 이러한 작은 따옴표는 웃기는(?) 코드를 만들기도 하는 데 특히 다른 함수를 만드는 함수를 만들 때 더욱 그렇다. (Example 24-6을 참조바람) 작은 따옴표를 쓸 때 한가지 명심할 것은 오로지 시작하고 끝맺을 때 쓴 작은 따옴표를 빼고는 다른 모든 것들이 짝수로 온다는 것이다.

 

이런 맥락에서 Table 24-1는 편리함을 제공할 것이다. (당신은 이 작은 도표를 사랑할 껄...)

 

[TABLE]

 

24.5.2. 함수 전환하기

 

Example 24-5. 간단한 함수

 

아래의 예는 Oracle의 함수입니다.

CREATE OR REPLACE FUNCTION cs_fmt_browser_version(v_name IN varchar, v_version IN varchar)
RETURN varchar IS
BEGIN
    IF v_version IS NULL THEN
        RETURN v_name;
    END IF;
    RETURN v_name || '/' || v_version;
END;
/
SHOW ERRORS; 

위의 함수를 한번 살펴보고 PL/pgSQL과의 차이점을 알아보자

 

OR REPLACE 부분은 PL/pgSQL에서 사용하지 않는다. PL/pgSQL에서는 확실하게 이미 생성된 함수를 삭제하고 생성해야 비슷한 결과는 낼 수 있다.

 

PostgreSQL는 매개변수가 이름을 갖지 않는다. 전환할 때 함수 내에서 명확하게 alias를 이용하여 변수를 선언해야한다.

 

Oracle은 함수에 값을 넘길 때 IN, OUT 그리고 INOUT 매개변수를 갖는다. 여기서 INOUT은 값을 받고 그 변수를 통해 값을 반환하는 역할을 한다. 그러나 PostgreSQL은 오직 "IN" 매개변수만을 갖고 오직 1개의 값만을 반환한다.

 

Oracle에서 함수를 정의할 때 사용하는 RETURN 키워드는 PostgreSQL에서는 RETURNS로 사용한다.

 

PostgreSQL에서 함수는 작은 따옴표를 이용하여 생성되기 때문에 함수 내부의 따옴표에 대해서 조심해서 잘 사용해야한다. (Section 24.5.1.1 참조 바람)

 

"/show"는 PostgreSQL에는 없기 때문에 오류를 야기한다.

 

자.. 이제 PostgreSQL에서 변환되고 나서 어떻게 보이는지 한번 보자.

DROP FUNCTION cs_fmt_browser_version(varchar, varchar);
CREATE FUNCTION cs_fmt_browser_version(varchar, varchar)
RETRUNS varchar AS '
DECLARE
    v_name ALIAS FOR ;
    v_version ALIAS FOR ;
BEGIN
    IF v_version IS NULL THEN
        return v_name;
    END IF;
    RETURN v_name || ''/'' || v_version;
END;
' LANGUAGE 'plpgsql';

Example 24-6. 다른 함수를 생성하는 함수

 

다음의 procedure는 SELECT 문에서 결과를 받은 다음 IF문 안에서 결과를 가지고 효율성을 위해 커다란 함수를 만든다. 다음의 예로부터 커서, FOR 반복문, PostgreSQL의 작은 따옴표 문제 등의 차이점을 주목해서 보기 바란다.

create or replace procedure cs_update_referrer_type_proc is 
    cursor referrer_keys is 
        select * from cs_referrer_keys 
        order by try_order;

    a_output varchar(4000); 
begin 
    a_output := 'create or replace function cs_find_referrer_type(v_host IN varchar, 
                v_domain IN varchar, v_url IN varchar) return varchar is begin'; 

    for referrer_key in referrer_keys loop 
        a_output := a_output || ' if v_' || referrer_key.kind || ' like ''' || 
referrer_key.key_string || ''' then return ''' || referrer_key.referrer_type || 
'''; end if;'; 
    end loop; 

    a_output := a_output || ' return null; end;'; 
    execute immediate a_output; 
end; 
/ 
show errors

변환된 PostgreSQL: 

CREATE FUNCTION cs_update_referrer_type_proc() RETURNS integer AS '
DECLARE
    referrer_keys RECORD;  -- FOR에서 사용하기 위한 일반 RECORD 선언
    a_output varchar(4000);
BEGIN 
    a_output := ''CREATE FUNCTION cs_find_referrer_type(varchar,varchar,varchar) 
                  RETURNS varchar AS '''' 
                     DECLARE 
                         v_host ALIAS FOR ; 
                         v_domain ALIAS FOR ; 
                         v_url ALIAS FOR ; ''; 

    -- 
    -- 어떻게 FOR 반복문에서 쿼리의 결과를 가져오는지 주목하라
    -- 여기서는 FOR  를 사용하였다..
    --

    FOR referrer_keys IN select * from cs_referrer_keys order by try_order LOOP
        a_output := a_output || '' if v_'' || referrer_keys.kind || '' like '''''''''' 
                 || referrer_keys.key_string || '''''''''' then return '''''' 
                 || referrer_keys.referrer_type || ''''''; end if;''; 
    END LOOP; 
  
    a_output := a_output || '' return null; end; '''' language ''''plpgsql'''';''; 
 
    -- 여기서는 변후를 치환하지 않았기 때문에 적법하다.
    -- 그러지 않으면 실패를 할 것이다. 함수를 작동시기기 위한 다른 방법인 PERFORM 을 볼것.
    
    EXECUTE a_output; 
end; 
' LANGUAGE 'plpgsql';

Example 24-7. 많은 문자열을 처리하는 함수와 OUT 매개변수

 

다음에 나오는 Oracle PL/SQL procedure는 URL을 받아 이를 몇몇개의 부분 (host, path, query)로 쪼게 반환하는 함수이다. 이것은 procedure 이어야 하는 까닭은 PL/pgSQL 함수는 오직 하나의 값만을 반환할 수 잇기 때문이다. (24.5.3 절 참조)

PostgreSQL에서 이를 해결하는 방법의 3개의 다른 함수로 procedure로 쪼개야한다. 하나는 host를 반환, 다른 하나는 path, 그리고 또다른 하나는 query 식으로....

create or replace procedure cs_parse_url(
    v_url IN varchar,
    v_host OUT varchar,  -- 반환값
    v_path OUT varchar,  -- 이것도 반환값
    v_query OUT varchar) -- 그리고 이것도...
is
    a_pos1 integer;
    a_pos2 integer;
begin
    v_host := NULL;
    v_path := NULL;
    v_query := NULL;
    a_pos1 := instr(v_url, '//'); -- PostgreSQL는 이 함수가 없다.

    if a_pos1 = 0 then
        return;
    end if;
    a_pos2 := instr(v_url, '/', a_pos1 + 2);
    if a_pos2 = 0 then
        v_host := substr(v_url, a_pos1 + 2);
        v_path := '/';
        return;
    end if;

    v_host := substr(v_url, a_pos1 + 2, a_pos2 - a_pos1 - 2);
    a_pos1 := instr(v_url, '?', a_pos2 + 1);

    if a_pos1 = 0 then
        v_path := substr(v_url, a_pos2);
        return;
    end if;

    v_path := substr(v_url, a_pos2, a_pos1 - a_pos2);
    v_query := substr(v_url, a_pos1 + 1);
end;
/
show errors;

PostgreSQL로 변환한 뒤: 

drop function cs_parse_url_host(varchar); 
create function cs_parse_url_host(varchar) returns varchar as ' 
declare 
    v_url ALIAS FOR ; 
    v_host varchar; 
    v_path varchar; 
    a_pos1 integer; 
    a_pos2 integer; 
    a_pos3 integer; 
begin 
    v_host := NULL; 
    a_pos1 := instr(v_url,''//''); 

    if a_pos1 = 0 then 
        return '''';  -- Return a blank
    end if; 

    a_pos2 := instr(v_url,''/'',a_pos1 + 2); 
    if a_pos2 = 0 then 
        v_host := substr(v_url, a_pos1 + 2); 
        v_path := ''/''; 
        return v_host; 
    end if; 

    v_host := substr(v_url, a_pos1 + 2, a_pos2 - a_pos1 - 2 ); 
    return v_host; 
end; 
' language 'plpgsql';

주목: PostgreSQL는 instr함수가 없기 때문에 다른 함수를 조합하여 구현하도록 한다. 나는 그렇게 하기가 귀찮아서 나만의 오라클의 기능과 같은 instr함수를 만들었다. ( 24.5.6 참조)

 

24.5.3. Procedure

 

Oracle의 procedure는 좀더 많은 유연성을 제공한다. 왜냐하면 반환값을 명시해줄 필요가 없기 때문이긴 하지만 INOUT 또는 OUT을 사용하여 값을 반환할 수는 있다.

 

예:

create or replace procedure cs_create_job(v_job_id in integer)
is
    a_running_job_count integer;
    pragma autonomous_transaction;(1)
begin
    lock table cs_jobs in exclusive mode;(2)

    select count(*) into a_running_job_count from cs_jobs
    where end_stamp is null;

    if a_running_job_count > 0 then
        commit; -- free lock(3)
        raise_application_error(-20000, 'Unable to create a new job: a job is currently running.');
    end if;

    delete from cs_active_job;
    insert into cs_active_job(job_id) values(v_job_id);

    begin
        insert into cs_jobs(job_id, start_stamp) values(v_job_id, sysdate);
        exception when dup_val_on_index then null; -- 이미 있어도 걱정말것(4)
    end;
    commit;
end;
/
show errors 

위의 예와같은 Procedure는 쉽게 INTEGER를 반환하는 PostgreSQL 함수로 변환할 수 있다. 위의 예는 상당히 재미있게도 몇가지 가르쳐 주는 바가 있다.

 

PostgreSQL에는 'pragma' 문이 없다.

PL/pgSQL에서 LOCK TABLE을 사용하면 잠금상태는 트랜젝션이 종료될 때 까지 유지될 것이다. PL/pgSQL procedure 내에는 또 다른 트렌젝션을 가질 수 없다. 함수는 (그리고 여기서 호출된 다른 함수) 하나의 트랜젝션 속에서 실행이 된다. 그래서 어떤 문제가 발생하였을 때 PL/pgSQL는 결과를 roll back한다. 그래서 오직 하나의 BEGIN문 만을 사용할 수 있다.

IF문을 쓸 수 있다면 예외처리보다 나을 듯...

 

아래의 코드는 PL/pgSQL procedure로 전환한 것이다.

drop function cs_create_job(integer);
create function cs_create_job(integer) returns integer as ' declare
    v_job_id alias for ;
    a_running_job_count integer;
    a_num integer;
    -- pragma autonomous_transaction;
begin
    lock table cs_jobs in exclusive mode;
    select count(*) into a_running_job_count from cs_jobs where end_stamp is null;

    if a_running_job_count > 0 then
        -- commit; -- free lock
        raise exception ''Unable to create a new job: a job is currently running.'';
    end if;

    delete from cs_active_job;
    insert into cs_active_job(job_id) values(v_job_id);

    SELECT count(*) into a_num FROM cs_jobs WHERE job_id=v_job_id;
    IF NOT FOUND THEN  -- If nothing was returned in the last query
        -- This job is not in the table so lets insert it.
        insert into cs_jobs(job_id, start_stamp) values(v_job_id, sysdate());
        return 1;
    ELSE
        raise NOTICE ''Job already running.'';(1)
    END IF;

    return 0;
end;
' language 'plpgsql';

(1) PL/pgSQL에서 어떻게 하면 Notice나 error을 사용할 수 있는지 볼 것...

 

24.5.4. Package

 

주목: 나는 이 패키지로 그리 많은 일을 하지 않았다. 그러니 아래의 코드에 오류가 있을 수 있다.

 

Package는 Oracle이 제공하는 PL/SQL문과 함수들을 하나로 묶어 은패(encapsulate)하는 방법을 제공한다. (자바에서 클래스처럼 함수와 오브젝트를 정의 하는 식의....)

그리고 이러한 오브젝트와 함수들은 점 '.'을 이용하여 접근할 수 있다.

 

아래에 ACS 4(the ArsDigita Community System)에서 사용하는 Oracle Package가 있다.

create or replace package body acs
as
  function add_user (
    user_id     in users.user_id%TYPE default null,
    object_type     in acs_objects.object_type%TYPE
               default 'user',
    creation_date   in acs_objects.creation_date%TYPE
               default sysdate,
    creation_user   in acs_objects.creation_user%TYPE
               default null,
    creation_ip     in acs_objects.creation_ip%TYPE default null,
  ...
  ) return users.user_id%TYPE
  is
    v_user_id       users.user_id%TYPE;
    v_rel_id        membership_rels.rel_id%TYPE;
  begin
    v_user_id := acs_user.new (user_id, object_type, creation_date,
                creation_user, creation_ip, email,
    ...
    return v_user_id;
  end;
end acs;
/
show errors

여기서는 다른 오프젝트를 생성하여 변환을 하는데 일반적인 이름을 갖는 함수를 정의하여 Oracle Package를 대신하였다. 여기서 몇가지 주의해야하는데, PostgreSQL 함수의 기본 매개변수의 부족 같은 것이다. 위의 package는 아래와 같은 방식으로 바뀔수 있다.

CREATE FUNCTION acs__add_user(integer,integer,varchar,datetime,integer,integer,...)
RETURNS integer AS '
DECLARE
    user_id ALIAS FOR ;
    object_type ALIAS FOR ;
    creation_date ALIAS FOR ;
    creation_user ALIAS FOR ;
    creation_ip ALIAS FOR ;
    ...
    v_user_id users.user_id%TYPE;
    v_rel_id membership_rels.rel_id%TYPE;
BEGIN
    v_user_id := acs_user__new(user_id,object_type,creation_date,creation_user,creation_ip, ...);
    ...

    return v_user_id;
END;
' LANGUAGE 'plpgsql';

 

24.5.5. 눈여겨 봐야할 기타사항

 

24.5.5.1. EXECUTE

PostgreSQL의 EXECUTE는 잘 작동하지만 Section 24.2.5.3에서 설명하였지만 quote_literal(TEXT) 와 quote_string(TEXT)를 사용할 때는 명심해야하는 것이 있다. EXECUTE ''SELECT * from '';를 사용하는 것은 앞에서 말한 함수를 사용하지 않으면 제대로 작동하지 않을 것이다.

 

24.5.5.2. PL/pgSQL 함수의 최적화

PostgreSQL에는 두가지의 함수 최적화 옵션이 있다.

 

iscachable (매개변수 값이 같으면 함수는 항상 같은 값을 반환한다.) 와 isstrict (매개변수 중에 NULL이 있으면 함수는 항상 NULL을 반환한다.). 좀더 자세한 것은 CREATE FUNCTION 참고서를 보기바란다.

 

이 최적화를 위한 옵션을 사용하기 위해서, WITH 문을 CREATE FUNCTION문과 함께 다음과 같이 사용한다.

 

CREATE FUNCTION foo(...) RETURNS integer AS '

...

' LANGUAGE 'plpgsql'

WITH (isstrict, iscachable);

 

 

24.5.6. 부록 I

 

24.5.6.1. instr 함수 코드

--
-- Oracle의 instr 함수와 같은 역할을 하는 함수.
-- 사용법: instr(string1,string2,[n],[m]) [] 부분은 옵션.
-- 
-- string2에서 n번째의 문자부터 탐색하여 string1을 찾는다. n 이 음수이면 
-- 뒤로부터 찾는다. 만일 m이 넘겨지지 않았으면 1로 가정한다.
-- (처음부터 찾는다)
-- 
--
-- by Roberto Mello (rmello@fslc.usu.edu)
-- modified by Robert Gaszewski (graszew@poland.com)
-- Licensed under the GPL v2 or later.
--

DROP FUNCTION instr(varchar,varchar);
CREATE FUNCTION instr(varchar,varchar) RETURNS integer AS '
DECLARE
    pos integer;
BEGIN
    pos:= instr(,,1);
    RETURN pos;
END;
' language 'plpgsql';


DROP FUNCTION instr(varchar,varchar,integer);
CREATE FUNCTION instr(varchar,varchar,integer) RETURNS integer AS '
DECLARE
    string ALIAS FOR ;
    string_to_search ALIAS FOR ;
    beg_index ALIAS FOR ;
    pos integer NOT NULL DEFAULT 0;
    temp_str varchar;
    beg integer;
    length integer;
    ss_length integer;
BEGIN
    IF beg_index > 0 THEN

       temp_str := substring(string FROM beg_index);
       pos := position(string_to_search IN temp_str);

       IF pos = 0 THEN
             RETURN 0;
         ELSE
             RETURN pos + beg_index - 1;
         END IF;
    ELSE
       ss_length := char_length(string_to_search);
       length := char_length(string);
       beg := length + beg_index - ss_length + 2;

       WHILE beg > 0 LOOP

           temp_str := substring(string FROM beg FOR ss_length);
             pos := position(string_to_search IN temp_str);

             IF pos > 0 THEN
                   RETURN beg;
             END IF;

             beg := beg - 1;
       END LOOP;
       RETURN 0;
    END IF;
END;
' language 'plpgsql';

--
-- Written by Robert Gaszewski (graszew@poland.com)
-- Licensed under the GPL v2 or later.
--
DROP FUNCTION instr(varchar,varchar,integer,integer);
CREATE FUNCTION instr(varchar,varchar,integer,integer) RETURNS integer AS '
DECLARE
    string ALIAS FOR ;
    string_to_search ALIAS FOR ;
    beg_index ALIAS FOR ;
    occur_index ALIAS FOR ;
    pos integer NOT NULL DEFAULT 0;
    occur_number integer NOT NULL DEFAULT 0;
    temp_str varchar;
    beg integer;
    i integer;
    length integer;
    ss_length integer;
BEGIN
    IF beg_index > 0 THEN
        beg := beg_index;
        temp_str := substring(string FROM beg_index);

        FOR i IN 1..occur_index LOOP
            pos := position(string_to_search IN temp_str);

            IF i = 1 THEN
                beg := beg + pos - 1;
            ELSE
                beg := beg + pos;
            END IF;

            temp_str := substring(string FROM beg + 1);
        END LOOP;

        IF pos = 0 THEN
            RETURN 0;
        ELSE
            RETURN beg;
        END IF;
    ELSE
        ss_length := char_length(string_to_search);
        length := char_length(string);
        beg := length + beg_index - ss_length + 2;

        WHILE beg > 0 LOOP
            temp_str := substring(string FROM beg FOR ss_length);
            pos := position(string_to_search IN temp_str);

            IF pos > 0 THEN
                occur_number := occur_number + 1;

                IF occur_number = occur_index THEN
                    RETURN beg;
                END IF;
            END IF;

            beg := beg - 1;
        END LOOP;

        RETURN 0;
    END IF;
END;
' language 'plpgsql'; 

24.5.7. 부록 II - PL/pgSQL을 이용한 자바코드

 

안녕하세요, 김일형입니다.

 

PL/pgSQL 매뉴얼을 번역하다 말고 잠시 옆길로 새어봤습니다.

제가 주로 하는 것이 JAVA이다 보니.... 한번 이 두 개를 붙여보고 싶더군요.

 

다음은 제가 실행해본 테스트 순서입니다.

 

24.5.7.1. PL/pgSQL을 사용가능하도록 database 만들기..

이것은 메뉴얼에 나와있습니다만 기본적으로 database에는 PL/pgSQL을 사용할 수 없습니다. 그래서 사용자가 직접 해당 db에 PL/pgSQL을 사용할 수 있도록 해주어야 합니다.

하지만 한줄만 쳐놓으시면 다시는 신경안써도 되죠.

 

$ createlang plpgsql bbs

 

라고 합니다. 위의 명령에서 bbs는 PL/pgSQL을 사용할 db명으로 제가 앞으로 테스트하면서 사용할 database이름입니다. 만일 어떤분이 PL/pgSQL을 ABC라는 db에서 사용하기를 원하신다면...

 

$ createlang plpgsql ABC

 

라고 입력하시면 됩니다. 아무런 메세지도 안떨어지고 끝납니다.

그러나 이 작업을 하시기 전에 반드시 postmaster 가 떠있어야 합니다. 안그럼 ...

psql: connectDBStart() -- connect() failed: No such file or directory
        Is the postmaster running locally
        and accepting connections on Unix socket '/tmp/.s.PGSQL.5432'?
createlang: external error

이런 에러가 떨어집니다.

 

24.5.7.2. 작업용 테이블 작성하기

CREATE TABLE test (
   id   int,
   name varchar
);

이 테이블은 사용자 식별 id하고 이름을 갖는 간단한 테이블입니다.

 

24.5.7.3. PL/pgSQL 함수 작성하기

 

예전에 올렸던 메뉴얼을 기준으로 함수를 다음과 같이 만들었습니다.

DROP FUNCTION myInsertRow(VARCHAR);
CREATE FUNCTION myInsertRow(VARCHAR) RETURNS int AS '
DECLARE
    v_name ALIAS FOR ;
    maxNo  int;
BEGIN
    SELECT INTO maxNo max(id) FROM test;
    IF NOT FOUND OR maxNo IS NULL THEN
        RAISE NOTICE ''MAX Value is not found or null'';
        maxNo := 0;
    END IF;

    maxNo := maxNo + 1;

    INSERT INTO test VALUES( maxNo, v_name );
    RETURN maxNo;
END;
' LANGUAGE 'plpgsql';

이 함수는 일단 첨가할 이름을 입력받고 현재 테이블에 입력된 id값 중에서 최대값을 뽑아낸 다음 거기(maxNo)에 1을 증가시켜 새로운 사용자 id를 만들어 낸 다음 이 새 번호와 입력받은 이름으로 새로운 Row를 테이블에 추가합니다.

간단한 것이죠?

한번 코드를 보시면 그리 어렵지 않게 읽으실 수 있을 것입니다.

 

위의 코드를 myInsert.sql이라는 화일로 저장을 합니다.

 

24.5.7.4. PL/pgSQL 함수를 db에 넣기

 

$ psql -f myInsert.sql bbs

 

이렇게 하면 myInsert.sql에서 bbs라는 db로 위의 코드를 하나하나 집어넣습니다.

이것을 처음 입력시키면 ...

 

ERROR: RemoveFunction: function 'myinsertrow(varchar)' does not exist

psql:03.sql:1: ERROR: RemoveFunction: function 'myinsertrow(varchar)' does not

exist

CREATE

 

라고 에러가 발생하는데 당연한 것이 위의 코드 맨 윗줄에 일단 기존의 함수를 drop하는 부분이 있습니다. 당연히 처음 위의 코드를 넣을 때는 기존에 함수가 없으니 에러가 나죠. 이렇게 하는 이유는 일단 코드를 수정해서 입력을 하게되면 기존의 함수를 없애야하니까 그렇습니다.

 

24.5.7.5. 함수 테스트하기

위에처럼 함수를 일단 생성하시고 나면.... Java하고 붙이기 전에 시험을 해봐야 겠습니다.

 

$ psql bbs

 

이렇게 해서 bbs라는 db로 들어갑니다.

그리고

 

=# \df myinsertrow

 

라고 입력하면

             List of functions
 Result  |  Function   |     Arguments
---------+-------------+-------------------
 integer | myinsertrow | character varying
(1 row) 

이렇게 나옵니다. 제대로 함수가 생성이 되었죠?

 

그럼 이 함수를 psql상에서 시험가동을 시켜보겠습니다.

 

# SELECT myInsertRow( 'My Name' );

NOTICE: MAX Value not found

myinsertrow

-------------

1

(1 row)

 

=# SELECT * FROM test;

id | name

----+---------

1 | My Name

(1 row)

 

어떻습니까 잘 되죠? 여기서 주목하실 것은 함수를 작동시키는 명령어가 'SELECT'입니다.

 

24.5.7.6. JAVA의 JDBC와 연결...

 

아래는 자바 코드입니다.

import java.sql.*;

public class pltest {

    public pltest() {
        String url = "jdbc:postgresql://127.0.0.1/bbs";

        try {
            Class.forName("org.postgresql.Driver");
            Connection con = DriverManager.getConnection(url, "id_user", "" );

            PreparedStatement ps = con.prepareStatement("SELECT myInsertRow( ? )");
            ps.setString(1, "This is Name"  );
            ResultSet rs = ps.executeQuery();
            int    result = 0;

            while(rs.next()) {
                result = rs.getInt(1);
                System.out.println( "NEW ID: " + result );
            }

            rs.close();
            ps.close();
            con.close();
        }
        catch( Exception ex ) {
            System.out.println( ex.getMessage() );
        }
    }
    public static void main(String[] args) {
        pltest pltest1 = new pltest();
    }
} 

이렇게 그리 길지 않은 코드입니다.

 

중요한 부분만 간단하게 설명하겠습니다.

 

Class.forName("org.postgresql.Driver");

 

PostgreSQL의 JDBC Driver를 가져오는 부분입니다. 이것은 각 DBMS마다 다르게 되어있는 것입니다.

 

String url = "jdbc:postgresql://127.0.0.1/bbs";

Connection con = DriverManager.getConnection(url, "id_user", "" );

 

이것은 가져온 드라이버를 바탕으로 해당 address로 실제 DBMS로부터 connection을 가져오는 부분입니다. ip부분을 127.0.0.1로 되어있는 것은 DBMS하고 자바로 실행시키는 application하고 같은 기계안에 있다는 뜻이겠죠... 그리고 url뒷부분의 bbs는 db이름이구요.

 

PreparedStatement ps = con.prepareStatement("SELECT myInsertRow( ? )");

 

이 것은 다른 DB의 Stored Procedure를 실행하는 코드와 같은 부분입니다.

그런데 그 안에 들어가는 쿼리 문자열이 아까 전에 보셨던거랑 똑같죠... 매개변수 부분만 틀리고... 일단 이렇게 해서 쿼리문을 준비해 둡니다.

 

ps.setString(1, "This is Name" );

 

이렇게 해서 위의 '?'부분을 'This is Name'이라는 문자열로 치환을 시킵니다.

 

ResultSet rs = ps.executeQuery();

 

위의 문장은 이렇게 준비된 쿼리문을 실행시키고 반환값을 ResultSet으로 가져옵니다.

while(rs.next()) {
    result = rs.getInt(1);
    System.out.println( "NEW ID: " + result );
}

그래서 반환값이 있으면 첫번째 반환값을 result 변수로 가져오고 이 값을 표준 출력장치로 출력한다..... 아시지만 작성한 PL/pgSQL함수를 보면 거기에 int값을 반환하게 되있습니다. 그건 새로운 ID값이죠.

 

그래서 컴팔하고 돌려보면

 

NEW ID: 2

 

요렇게 나옵니다. 당연히 아까 전 psql에서 하나 생성했으니까 지금은 새 ID가 2이겠죠.

 

그리고 psql에 가서 쿼리를 해보면....

 

=# SELECT * FROM test;

id | name

----+--------------

1 | My Name

2 | This is Name

(2 rows)

 

 

이렇게 나옵니다. 아까 전에 입력한 거랑, 지금 자바를 통해 입력한 거랑 입력이 되어있죠.

 

이렇게 함으로써 일반코드에서 2개의 쿼리 및 계산식을 코드로 구현을 해서 불러야 하는데 여기서는 그 것을 단순한 하나의 함수 호출하는 것으로 끝이 났습니다. 당연히 2개의 statement를 만들어 네트워크 넘어로 요청을 해야하는데 여기서는 1개의 요청으로 동일한 작업을 끝냈습니다.

 

이게 PL/pgSQL을 작성하는 또 하나의 이유가 되겠죠....

[Top]
No.
제목
작성자
작성일
조회
3990PostgreSQL 7.2 설치하기
정재익
2002-02-11
7713
3910DB data directory 를 여러군데 이용하기.
정재익
2002-01-21
5949
3871pgbash 사용법
정재익
2002-01-11
8355
3846PL/pgSQL - SQL procedural language (3)
정재익
2002-01-07
15061
3845PL/pgSQL - SQL procedural language (2)
정재익
2002-01-07
9675
3844PL/pgSQL - SQL procedural language (1)
정재익
2002-01-07
9205
3831부분 인덱스 사용법
정재익
2002-01-01
6255
Valid XHTML 1.0!
All about the DATABASE... Copyleft 1999-2023 DSN, All rights reserved.
작업시간: 0.053초, 이곳 서비스는
	PostgreSQL v16.1로 자료를 관리합니다