PostgreSQL의 PL/pgSQL 튜토리얼 – 4 : IF 조건문

안녕하세요, GIS Developer 김형준입니다. 이번 장에서는 PL/pgSQL에서 제공하는 조건문에 대해 살펴보겠습니다. 조건문은 어떤 조건에 대해 실행할 코드를 분리하기 위한 목적으로 사용됩니다.

PL/pgSQL에서 제공하는 조건문은 IF 문과 CASE 문으로 크게 구분할 수 있습니다. 먼저 IF 문에 대해 살펴보겠습니다.

IF 문의 문법은 아래와 같습니다.

IF <조건> THEN
    <참의 실행 코드>
ELSE
    <거짓의 실행 코드>
END IF;

만약 <조건>이 부합되는 거짓(false)이 아닌 참(true)일 경우 <참의 실행 코드>를 실행하고, <조건>이 거짓일 경우 <거짓의 실행 코드>를 실행합니다. 조건에 대한 실행 코드는 1줄 이상으로 구성할 수 있습니다.

일단 간단한 코드를 통해 ELSE 구문이 없는 IF문의 예를 살펴보겠습니다.

DO $$
DECLARE
    a integer := 20;
    b integer := 40;
    c integer := 20;
BEGIN 
    IF a > b THEN
        RAISE NOTICE 'a가 b보다 더 큽니다다.';
    END IF;
 
    IF a < b THEN
        RAISE NOTICE 'a가 b보다 더 작습니다.';
    END IF;
 
    IF a = b THEN
        RAISE NOTICE 'a와 b가 동일합니다.';
    END IF;
    
    IF a >= c THEN
        RAISE NOTICE 'a가 c보다 크거나 같습니다.';
    END IF;
    
    IF a != b THEN
        RAISE NOTICE 'a가 b가 같지 않습니다.';
    END IF;
    
    IF a != b AND a = c THEN
        RAISE NOTICE 'a와 b가 같지 않고 a와 c가 같습니다.';
    END IF;
    
    IF a = b OR a = c THEN
        RAISE NOTICE 'a와 b가 같거나 a와 c가 같습니다.';
    END IF;
    
    IF NOT (a = b OR a = c) THEN
        RAISE NOTICE 'a와 b가 같지지 않고 a와 c도 같지 않습니다.';
    END IF;
END $$;

변수 a, b, c를 각각 20, 40, 20으로 초기화하고, 각 값에 대해 IF 문을 통해 비교하고 있습니다. 먼저 7번 IF 문은 a가 b 보다 크다라는 조건에 부합하지 않으므로 8번 코드를 실행하지 않습니다. 비교 연산은 크다, 작다, 같거나 크다, 같거나 작다, 같지 않다, 같다에 대해 각각 >, <, >=, <=, !=, =입니다. 조건에 대해 AND와 OR 그리고 NOT으로 묶어 조합할 수 있습니다. 27번의 IF 문은 a와 b가 같지 않고(AND) a와 c가 같은가라는 조건으로 참이므로 28번 코드가 실행됩니다. 30번의 IF문은 a와 b가 같거나(OR) a와 c가 같은가라는 조건으로 거짓이므로 31번은 실행되지 않습니다. 그리고 33번의 IF 문은 a와 b가 같거나 a와 c가 같은가에 대한 조건을 NOT으로 부정하고 있는데, a와 b가 같거나 a와 c가 같은가는 참(true)이고 이를 부정하므로 결국 거짓(false)되어 34번 코드는 실행되지 않습니다. 최종 결과는 아래의 화면과 같습니다.

ELSE 구문까지 포함한 IF 문의 예를 보면 아래와 같습니다.

DO $$
DECLARE
    a integer := 20;
    b integer := 40;
BEGIN 
    IF a > b THEN
        RAISE NOTICE 'a가 b보다 더 큽니다.';
    ELSE
        RAISE NOTICE 'a가 b보다 더 크지 않습니다.';
    END IF;
END $$;

IF 문에서 a가 b보다 큰가에 대한 조건이 거짓(false)이므로 ELSE 문 다음의 실행 코드가 실행되므로 그 결과는 다음과 같습니다.

IF 문에 대한 마지막 예로 다음 코드를 살펴보겠습니다.

DO $$
DECLARE
    a integer := 20;
    b integer := 40;
BEGIN 
    IF a > b THEN
        RAISE NOTICE 'a가 b보다 더 큽니다.';
    ELSEIF a = b THEN
        RAISE NOTICE 'a와 b가 같습니다.';
    ELSE
        RAISE NOTICE 'a와 b가 같지 않고, a가 b보다 더 크지도 않습니다.';
    END IF;
END $$;

ELSEIF 문을 사용하고 있는데요. 만약 첫번째 IF 문에 대한 조건이 거짓이면 다음의 ELSEIF 문의 조건을 검사하게 됩니다. 그런데 이 ELSEIF 문 역시 거짓이라면 다음 ELSEIF 문을 검사하게되고, 최종적으로 어떠한 조건도 만족하지 않는다면 ELSE 문의 코드가 실행되게 됩니다. 위의 코드의 실행 결과는 아래와 같습니다.

PostgreSQL의 PL/pgSQL 튜토리얼 – 3 : 변수와 상수

안녕하세요, GIS Developer 김형준입니다. 이번 포스트는 PL/pgSQL에서 변수와 상수를 정의하고 사용하는 내용에 대해 정리하여 설명하겠습니다.

변수란 어떤 값을 가지고 있으면서, 필요할 경우 그 값을 변경할 수 있습니다. 또한 변수는 그 이름과 변수가 가지는 값의 타입을 지정해야 합니다.

변수의 이름은 개발자에게 의미있는 이름을 부여하는 것이 좋고, 값의 타입은 integer, numeric, char, varchar 등과 같이 각각 정수, 실수, 문자, 가변길이 문자 등으로 지정할 수 있습니다.

변수는 선언 시에 기본값을 지정할 수 있습니다. 만약 지정하지 않는다면 그 변수의 값은 NULL 값을 갖게 됩니다.

다음은 여러 개의 변수를 선언하는 PL/pgSQL 코드입니다. 특히 아래의 코드는 함수를 정의하지 않고 바로 PL/pgSQL을 실행합니다.

DO $$ 
DECLARE
   age integer := 40;
   korean_name varchar(10) := '김형준';
   alias_name varchar(50) := 'Dip2K';
   weight numeric(3,1) := 65.5;
BEGIN 
   RAISE NOTICE '%의 별명은 % 이고 나이는 %이며 몸무게는 %입니다.', 
       korean_name, alias_name, age, weight;
END $$;

위의 코드를 보시면 DECLARE 구문 바로 뒤에 변수를 선언하고 있습니다. 4개의 변수를 선언하고 있고, 초기값 40인 정수형 변수 age와 ‘김형준’ 값을 가지는 가변길이 10인 문자형 변수 korean_name 그리고 65.5 값을 가지는 실수형 변수 weight를 갖습니다. 실수형 타입을 지정하기 위해 numeric(n,m)으로 선언하는데, n은 전체 자리수이고, m은 소수점 아래에 대한 자리수입니다. 8번 줄의 RAISE NOTICE는 해당 변수들을 원하는 문자열로 포맷으로 구성한 것을 PL/pgSQL의 실행환경에서 표시하는 코드입니다. 실행하면 아래와 같은 결과를 볼 수 있습니다.

일반적인 숫자형이나 문자형 이외에도 훨씬 다양한 데이터 타입이 존재합니다. 그 중에 아래의 코드에서 볼 수 있는 시간(time) 타입입니다.

DO $$ 
DECLARE
   current_time time := now();
BEGIN 
   RAISE NOTICE '%', current_time;
END $$;

위 코드의 실행 결과는 아래와 같습니다.

변수의 데이터 타입을 결정할 때, 기존에 존재하는 테이블의 특정 필드의 데이터 타입을 가져와 그 데이터 타입으로 변수의 타입을 지정해야 할 필요가 있습니다. 예를 들어 employee라는 데이블의 salary라는 필드의 데이터 타입을 가져와 my_salary라는 변수를 선언하는 코드는 아래와 같습니다.

my_salary employee.salary#TYPE := 100000.12;

끝으로 변수 선언에 대해 하나더 설명하면, 동일한 대상의 값을 담고 있는 변수에 대해 또 다른 이름(별칭)을 붙일 수 있습니다. 아래의 코드는 current_time이라는 변수를 old_time이라는 별칭을 붙이는 예입니다. 이는 어떤 변수에서, 개발하고자 하는 기능에 좀더 의미에 부합하는 이름을 붙이기 위한 목적입니다.

DO $$ 
DECLARE
   current_time time := now();
   old_time ALIAS FOR current_time;
BEGIN 
   RAISE NOTICE '%', old_time;
END $$;

이제 상수(constant)에 대해 설명하겠습니다. 상수는 변수와 다르게 한번 값이 정해지면 변경할 수 없다는 차이가 있습니다. 상수를 선언하는 방식은 변수의 선언처럼 DECLARE 구문 뒤에서 이루어지며, 상수 이름과 타입 사이에 CONSTANT라고 지정하면 됩니다. 아래의 예를 보면 쉽게 알 수 있습니다.

DO $$ 
DECLARE
   vat CONSTANT numeric := 0.1;
   cost numeric := 100000;
   real_cost numeric;
BEGIN 
   real_cost = cost + cost * vat;
   RAISE NOTICE '부가세를 적용한 실제 가격은 % 입니다', real_cost;
END $$;

위의 3번 코드가 vat라는 실수형 상수를 선언하는 코드입니다.

이상으로 PL/pgSQL에서 변수와 상수를 선언하는 내용에 대해 살펴 보았습니다.

PostgreSQL의 PL/pgSQL 튜토리얼 – 2 : 함수 인자

안녕하세요, GIS Developer 김형준입니다. 2번째 PostgreSQL의 Stored Procedure의 튜토리얼입니다. 이번에는 사용자 정의 함수를 생성하는 CREATE FUNCTION 구문의 한가지 예로 아래의 코드를 살펴보겠습니다.

CREATE FUNCTION add(a INTEGER, b INTEGER)
RETURNS INTEGER AS
$$ BEGIN
    RETURN a+b;
END; $$
LANGUAGE PLPGSQL;

위의 코드 중 CREATE FUNCTION 코드 대신 CREATE OR REPLACE FUNCTION 코드로 대체하면 아래와 같은데요.

CREATE OR REPLACE FUNCTION add(a INTEGER, b INTEGER)
RETURNS INTEGER AS
$$ BEGIN
    RETURN a+b;
END; $$
LANGUAGE PLPGSQL;

위의 CREATE OR REPLACE FUNCTION 코드는 CREATE FUNCTION와 다르게 이미 동일한 함수가 존재할 경우 함수를 새롭게 교체하라는 의미를 갖습니다. 이 add 함수는 정수형 타입인 a와 b 인자를 갖습니다. 기본적으로 인자는 IN 형식입니다. IN 형식 이외에 OUT과 INOUT 형식의 인자도 지정이 가능합니다. IN 형식으로 지정된 인자는 단지 함수에 값만을 전달해 주는 목적이고, OUT은 함수 종료시 이 인자에 값을 전달해 외부에 반환해주는 목적도 갖습니다. INOUT은 IN과 OUT에 대한 목적을 모두 갖습니다.

함수의 인자를 OUT으로 지정하는 예를 살펴 보겠습니다. 아래의 코드를 예로 들어 OUT 형식의 함수 인자를 살펴 보겠습니다.

CREATE OR REPLACE FUNCTION getmaxmin(
    v1 NUMERIC, 
    v2 NUMERIC,
    OUT min_value NUMERIC,
    OUT max_value NUMERIC)
AS 
$$ BEGIN
    min_value := GREATEST(v1, v2);
    max_value := LEAST(v1, v2);
END; $$
LANGUAGE PLPGSQL;

위의 getmaxmin이라는 함수는 4개의 인자를 받습니다. 첫번째와 두번째 인자는 별도의 지정이 없으므로 IN 형식이고 min_value와 max_value는 OUT 형식으로 지정하였습니다. 이 함수는 첫번째와 두번째로 지정된 인자값중 최대값을 max_value의 인자로 넘기고 최소값은 min_value의 인자로 넘깁니다. 이 함수를 보면 별도의 반환값을 지정하기 위한 RETURNS 구문을 사용하지 않고, 이처럼 OUT 형식으로 지정된 인자에 값을 반환하고 있습니다.

이 getmaxmin 함수는 아래의 화면처럼 실행할 수 있습니다.

다음으로 INOUT 형식은 IN과 OUT에 대한 특성 모드를 갖습니다. 즉, 값은 함수로 전달할 수도 있고 함수 내부에서 값을 변경해 외부로 값을 전달도 할 수 있습니다. 아래의 함수를 예로 살펴보겠습니다.

CREATE OR REPLACE FUNCTION getmaxmin2(
    INOUT v1 NUMERIC, 
    INOUT v2 NUMERIC)
AS 
$$ DECLARE
    v3 NUMERIC := v1;
BEGIN
    v1 := GREATEST(v2, v3);
    v2 := LEAST(v2, v3);
END; $$
LANGUAGE PLPGSQL;

이 getmaxmin2 함수는 INOUT 형식의 인자 2개를 받습니다. 이 두개의 인자값에서 최대, 최소값을 다시 이 인자의 첫번째와 두번째에 각각 기록해 반환합니다. 이를 위해 5~6번에서 v3 변수를 하나 정의해 사용하고 있습니다. 이 변수에 대한 자세한 내용은 추후 다른 튜토리얼에서 설명하도록 하겠습니다. 이 getmaxmin2 함수를 실행하면 다음과 같은 결과를 얻을 수 있습니다.

IN, OUT, INOUT 형식 이외에도 성격은 다르지만 VARIADIC 형식이 있습니다. 이 VARIADIC 형식은 가변인자로써 동일한 데이터 타입의 여러개의 인자들을 한번에 지정할 수 있습니다. 다음의 예를 살펴보겠습니다.

CREATE OR REPLACE FUNCTION sum(VARIADIC params numeric[]) 
RETURNS numeric AS 
$$ DECLARE
    res numeric := 0;
BEGIN
    FOR i IN 1 .. array_length(params, 1) LOOP
        res := res + params[i];
    END LOOP;

    RETURN res;
END; $$ 
LANGUAGE plpgsql;

위의 코드에서 params 인자는 VARIADIC 형식이고 타입은 NUMERIC[]입니다. 지정한 인자의 개수는 array_length 내장 함수를 사용해 얻을 수 있습니다. FOR 반복만을 사용하고 있는데, 1부터 인자의 개수만큼 반복하고 있습니다. 반복문에 대해서는 추후 자세히 설명하겠습니다. 이 함수는 여러개의 숫자값들로 인자를 지정하여 그 합한 결과를 반환하는데요. 이 함수의 실행 결과의 예는 아래와 같습니다.

PostgreSQL의 PL/pgSQL 튜토리얼 – 1 : 소개 및 첫번째 사용자 정의 함수

안녕하세요, GIS Developer 김형준입니다. GIS 프로젝트를 진행하면서 PostgreSQL을 많이 사용하게 되는데요. 프로젝트에서 PostgreSQL를 사용하면서 점진적으로 더 나은 개발 방법을 찾게되었고, 더 나은 개발 생산성과 개발하고자 하는 시스템의 안정성과 퍼포먼스를 위해 Trigger와 Stored Procedure를 도입하게 되었습니다.

이 글은 Stored Procedure에 대한 강좌 형식으로 정리하는 글입니다. PostgreSQL는 프로그래밍 언어를 이용해 함수를 직접 만들어 실행하기 위해 PL/pgSQL이라는 언어를 제공합니다. 이 PL/pgSQL를 활용한다는 것이 바로 Stored Procedure를 사용한다는 것이고, 이를 이용해 Trigger를 사용할 수 있는 발판이 됩니다.

PL/pgSQL은 PostgreSQL을 보다 편리하고 빠른 응답성을 제공받기 위해 사용할 수 있을 뿐만 아니라 데이터베이스 자체의 기능을 확장할 수 있는 기반을 제공합니다. PostgreSQL의 Stored Procedure 개발을 위한 언어가 비단 PL/pgSQL만 가능한 것은 아닌데요, 특히 C언어를 통해서도 개발이 가능하지만, 이 글에서는 PL/pgSQL에만 집중하도록 하겠습니다.

먼저 PostgreSQL의 Stored Procedure를 사용하면 얻을 수 있는 장점을 정리하면 다음과 같습니다.

  • 여러개의 SELECT나 UPDATE, INSERT 등과 같은 Query 문을 Stored Procedure 문을 통해 하나로 묶어 실행함으로써 서버와 클라이언트 간의 Round Trip의 개수를 줄여 줍니다. 이러한 Round Trip을 줄이면 불필요한 네트워크 통신 횟수를 줄여 더 빠르게 DBMS 연산 결과를 얻을 수 있습니다.
  • SELECT나 UPDATE, INSERT, DELETE 문 등을 Java의 JDBC 등을 이용해 실행하게 되면, Query 문을 해석하고 실행하기에 앞서 많은 준비가 필요한데요. Stored Procedure는 이미 컴파일되어 DBMS 단에서 바로 실행할 수 있도록 준비되어 있어 그 실행 속도가 매우 빠르며, 함수 호출 시 인자만을 변경하여 빠르고 쉽게 재활용이 가능합니다.

PostgreSQL의 장점이 있다면, 단점도 있겠지요. 단점을 언급해 본다면 다음과 같습니다.

  • Stored Procedure를 C나 PL/pgSQL과 같은 프로그래밍 언어로 개발해야 하므로 특별환 스킬을 요구하고, 문제 발생시 디버깅이 어렵습니다.
  • Stored Procedure는 표준인 SQL과는 다르게 DBMS 마다 다르므로 DBMS가 변경되면 해당 DBMS에 맞게 Stored Procedure를 다시 개발해야 합니다.

자, 이제 PL/pgSQL을 이용해 간단한 Stored Procedure를 만들어 보도록 하겠습니다. 사용자 정의 함수를 만들기 위해서는 CREATE FUNCTION 문을 사용하는데요. 그 문법은 아래와 같습니다.

CREATE FUNCTION function_name(param1 type, param2 type)
 RETURNS return_type AS
BEGIN
 -- code
END;
LANGUAGE language_name;

위의 코드를 좀더 상세히 설명하면 function_name은 함수의 이름이고 param1 type과 param2 type는 함수의 인자를 의미합니다. 여기서는 2개이고 인자의 이름은 param1, param2이며, 인자명 바로 뒤에는 인자의 타입이 옵니다. return_type에는 이 함수의 반환값의 타입을 의미합니다. BEGIN과 END 사이의 — code에 이 함수가 실행할 코드를 입력하면 됩니다. 그리고 마지막으로 language_name에는 이 함수에 대한 프로그래밍 언어가 무엇인지를 나타내는데, 여기서 우리는 PL/pgSQL 언어를 사용하므로 항상 plpgsql이 됩니다.

이제 Stored Procedure를 만들기 위한 CREATE FUNCTION 구문에 대한 문법을 모두 살펴보았으므로, 실제 간단한 함수를 만들어 보겠습니다.

만들고자 하는 함수는 2개의 정수를 입력받아 합한 결과를 반환해 주는 함수입니다.

CREATE FUNCTION add(a INTEGER, b INTEGER)
RETURNS INTEGER AS
$$ BEGIN
    RETURN a+b;
END; $$
LANGUAGE PLPGSQL;

위의 코드를 pgAdmin 툴에서 입력하고 실행하면 functions에 add라는 함수가 새롭게 추가된 것을 볼 수 있습니다.

위의 그림은 실제 위의 add 함수를 pgAdmin에서 입력하고 실행하여 새로운 Function을 등록한 모든 내용을 담고 있습니다. 위의 그림에서 (1)은 함수를 생성하기 위한 CREATE FUNCTION을 입력하고 (2)는 함수를 생성하도록 입력된 CREATE FUNCTION 문을 실행하고 (3)은 그 실행 결과이며 (4)는 등록에 성공했을때 추가되는 add 함수입니다.

이렇게 추가된 add 함수는 다음처럼 실행해 그 결과를 볼 수 있습니다.

이상으로 PostgreSQL에서 Stored Procedure의 소개와 장단점에 대해 알아봤고, PG/pgSQL을 이용해 간단한 사용자 정의 함수를 만들어 보았습니다.

FingerEyes-Xr for HTML5와 GeoService-Xr를 활용한 설비 공간정보시스템

특화된 GIS 시스템 개발을 견고하고 빠르게 개발할 수 있는 GIS 엔진으로써, FingerEyes-Xr과 GeoService-Xr을 활용해 설비를 관리하는 GIS 시스템을 개발하고 있습니다.

다양한 기능 중 웹에서 공간 데이터를 SHP 파일로 바로 내보내기하여 저장할 수 있는 기능에 대해 소개합니다.

공간서버인 GeoService-Xr이 접근(Access)하는 공간 데이터가 저장된 DBMS로부터 SHP 파일을 다운로드할 수 있는 방법은 단지 URL의 호출만으로 이루어집니다. 즉, 공간 데이터에 대한 레이어의 고유 식별자가 ecl_alts_main이라고 하고, SHP 파일로 저장할 영역의 범위를 MBR로써, 각각 MinX, MinY, MaxX, Max라고 한다면 아래의 URL을 호출하면 SHP 파일을 압축 파일로 바로 다운로드 받을 수 있습니다.

http://localhost/Xr?gdownload|ecl_alts_main|MinX|MinY|MaxX|MaxY

아래의 화면은 SHP 파일을 저장하는 기능에 대한 실제 화면입니다. 레이어를 관리하는 UI에서 바로 원하는 레이어를 SHP 파일이나 KML로 저장할 수 있도록 하였습니다.

위의 UI 중 SHP 파일로 저장하기 원하는 레이어 명 옆의 SHP 버튼을 클릭하면 아래와 같이 웹브라우저에서 흔히 볼 수 있는 다운로드가 시작됩니다.

다운로드를 위한 URL 호출을 통해 클라이언트 측에 다운로드를 받기 위해 JavaScript 코드를 간단히 설명하겠습니다. 먼저 주요 줄기를 언급하면 iframe의 src 속성에 url을 지정하면 되는데요. 이를 위해서 해당 웹 페이지에 다음과 같이 숨겨진 iframe을 추가합니다.

var iframe = $('');
iframe.appendTo(this._containerDiv);

그리고 SHP 파일을 저장하는 버튼에 대한 클릭 이벤트에 다음과 같은 코드가 필요합니다.

function onDownloadSHPClick(event) {
    var layerId = $(this).attr('layerId');
    var cm = g_map.coordMapper();
    var mbr = cm.viewportMBR();
    var iframe = $("#downloadFrame");
    var url = mg_MapLayers.GIS_HTTP_SERVER + '/Xr?gdownload|' + layerId + '|' + mbr.minX + '|' + mbr.minY + '|' + mbr.maxX + '|' + mbr.maxY;
    
    iframe.attr("src", url);

위의 코드 중 실제 의미 있는 부분은 5~8번 코드입니다.

이상으로 FingerEyes-Xr과 GeoService-Xr을 이용하여 GIS 시스템에서 공간 데이터를 바로 SHP 파일로 저장할 수 있는 기능과 JavaScript 코드에 대해 살펴 보았습니다.