Postgresql - FUNCTION 작성 및 실행방법

PostgreSQL에서 서버 프로그래밍의 일환으로 PL/pgSQL, PL/Tcl, PL/Perl, PL/Python 등 여러 프로그래밍 언어를 사용하여 만들 수 있다. 함수(FUNCION)는 다양한 작업을 자동화하거나 간단한 데이터 처리를 수행할 수 있으며 그중 기본 설치되어 있는 PL/pgSQL 으로 간단한 예시를 정리해본다.

간단한 FUNCTION 만들기

이 함수는 단순히 “Hello, World!” 문자열을 반환한다.

CREATE OR REPLACE FUNCTION hello_world()
RETURNS text AS $$
BEGIN
    RETURN 'Hello, World!';
END;
$$ LANGUAGE plpgsql;

함수(FUNCION)을 사용하는 방법은 아래처럼 select 문으로 바로 호출하면 된다.

SELECT hello_world();

/* 출력결과 */
hello_world  
-------------
Hello, World!

두 개의 숫자를 받아서 합을 반환하는 함수이다.

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

실행결과이다.

select add_numbers(10,50);

/* 출력결과 */
add_numbers
-----------
         60

그냥 select 로 할 수도 있지만, 테이블 명칭과 컬럼명칭을 동시에 줄 수도 있다.

select tab_name.col_name 
  from add_numbers(10,50) tab_name(col_name);

/* 출력결과 */
col_name
--------
      60

IN 및 OUT 파라미터를 사용하는 함수는 입력 값을 받아서 처리한 후, 결과 값을 반환할 수 있다. OUT 파라미터는 반환 값으로 사용되며, 함수에서 명시적으로 RETURN을 호출하지 않아도 자동으로 결과가 반환된다. 아래는 IN과 OUT 파라미터를 사용하여 두 숫자의 합과 차를 계산하는 간단한 함수 예제이다.

CREATE OR REPLACE FUNCTION calculate_sum_and_diff(
    IN num1 INTEGER,
    IN num2 INTEGER,
    OUT sum_result INTEGER,
    OUT diff_result INTEGER
)
AS $$
BEGIN
    sum_result := num1 + num2;
    diff_result := num1 - num2;
END;
$$ LANGUAGE plpgsql;

이 함수를 호출하면, 두 숫자의 합(sum_result)과 차(diff_result)가 반환된다.

SELECT * FROM calculate_sum_and_diff(10, 5);

/* 출력결과 */
sum_result|diff_result
----------+-----------
        15|          5

RETURNS TABLE

RETURNS TABLE 로 쿼리의 결과(레코드)를 반환할수 있다. 특징은 테이블의 구조를 지정하여, 결과를 반환할 수 있는 것인데, 기존과 동일한 구조로 리턴할 거라면, 조금은 귀찮은 리턴방식이고, 테이블간 JOIN 하여 조회하는 경우에 좋을거 같다. 우선 테스트를 위해 테이블을 하나 만들어본다.

-- employees 테이블 예시
 CREATE TABLE employees (
     id SERIAL PRIMARY KEY,
     name TEXT,
     position TEXT,
     salary NUMERIC
 );

INSERT INTO employees (name, position, salary) VALUES
('Alice', 'Manager', 70000),
('Bob', 'Manager', 75000),
('Charlie', 'Engineer', 60000);

RETURNS TABLE 을 이용하여 직원 정보를 리턴하는 함수를 만들었다.

CREATE OR REPLACE FUNCTION get_employee_info(employee_id INTEGER)
RETURNS TABLE (id INTEGER, name TEXT, position TEXT, salary NUMERIC) AS $$
BEGIN
    RETURN QUERY
    SELECT id, name, position, salary
    FROM employees
    WHERE id = employee_id;
END;
$$ LANGUAGE plpgsql;

오류가 발생했다. position이라는 컬럼 이름이 PostgreSQL에서 예약어이기 때문에 발생하는 문제이다.

select * from get_employee_info(1)

SQL Error [42601]: ERROR: syntax error at or near "position"
  Position: 105

이 문제를 해결하기 위해서는 해당 컬럼 이름을 쿼리에서 사용할 때 따옴표로 감싸야 한다. PostgreSQL에서는 예약어로 인한 충돌을 피하기 위해 컬럼 이름을 쿼리에서 “로 감싸서 처리할 수 있다.

CREATE OR REPLACE FUNCTION get_employee_info(employee_id INTEGER)
RETURNS TABLE (id INTEGER, name TEXT, "position" TEXT, salary NUMERIC) AS $$
BEGIN
    RETURN QUERY
    SELECT id, name, position, salary
    FROM employees
    WHERE id = employee_id;
END;
$$ LANGUAGE plpgsql;

또 오류가 발생했다. 이건 PostgreSQL에서 PL/pgSQL 함수 내에서 id와 같은 변수나 컬럼 이름이 혼동될 때 발생하는 오류이다.

select * from get_employee_info(1)

SQL Error [42702]: ERROR: column reference "id" is ambiguous
  Detail: It could refer to either a PL/pgSQL variable or a table column.
  Where: PL/pgSQL function get_employee_info(integer) line 4 at RETURN QUERY

id는 테이블의 컬럼 이름일 수 있고, 동시에 함수의 지역 변수로 사용될 수도 있기 때문에 발생하는 오류라, 이 문제를 해결하려면 테이블의 컬럼과 변수의 이름이 충돌하지 않도록 해야 한다. 쿼리에 테이블 이름을 명시적으로 지정하여 충돌나지 않도록 수정했다.

CREATE OR REPLACE FUNCTION get_employee_info(employee_id INTEGER)
RETURNS TABLE (id INTEGER, name TEXT, "position" TEXT, salary NUMERIC) AS $$
BEGIN
    -- 직원 정보를 반환
    RETURN QUERY
    SELECT e.id, e.name, e."position", e.salary
    FROM employees e
    WHERE e.id = employee_id;
END;
$$ LANGUAGE plpgsql;

드디어 실행이 되었다.

select * from get_employee_info(1)

id|name |position|salary
--+-----+--------+------
 1|Alice|Manager | 70000

RETURNS SETOF

쿼리결과를 리턴할때, SETOF 와 RETURN QUERY 을 사용하서도 해결할 수 있다. join 하지 않고, 단일 테이블을 조회하는 거라면, 이 방법이 더 좋은거 같다.

CREATE OR REPLACE FUNCTION get_employees_by_position(job_position TEXT)
RETURNS SETOF employees AS $$
BEGIN
    -- 직원 정보를 여러 행 반환
    RETURN QUERY
    SELECT *
    FROM employees
    WHERE position = job_position;
END;
$$ LANGUAGE plpgsql;

위에서 여러 문제를 겪어왔는데, 여기서는 바로 테이블의 정보를 이용해 받다보니, 테이블에 별도의 alias 를 지정할 필요도 없다.

SELECT * FROM get_employees_by_position('Manager');

id|name |position|salary
--+-----+--------+------
 1|Alice|Manager | 70000
 2|Bob  |Manager | 75000

더 보면 좋을 글들