
https://postgresql.kr/docs/13/plpgsql.html
CREATE [OR REPLACE] FUNCTION 함수이름(파라미터 목록)
RETURNS 반환타입 AS $$
DECLARE
-- 변수 선언
BEGIN
-- 함수 로직
RETURN 결과값;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION one()
RETURNS int4
AS $$
select 1;
$$ LANGUAGE SQL;
--plpgsql
CREATE OR REPLACE FUNCTION one_plpgsql()
RETURNS int4
AS $$
begin
return 1;
end
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION add_num(x integer, y integer)
RETURNS integer
AS $$
begin
return x + y;
end
$$ LANGUAGE plpgsql;

-- sql
CREATE OR REPLACE FUNCTION get_prod_info_sql(serial_no varchar) RETURNS NUMERIC AS
$$
SELECT target_weight FROM product_info WHERE product_info.serial_no = get_prod_info_sql.serial_no;
$$ LANGUAGE SQL;
-- plpgsql
CREATE OR REPLACE FUNCTION get_prod_info(serial_no varchar) RETURNS NUMERIC AS $$
BEGIN
RETURN (SELECT target_weight FROM product_info WHERE product_info.serial_no = get_prod_info.serial_no);
END;
$$ LANGUAGE plpgsql;

CASE문과 같이 기본 SQL 문법만 사용 가능CREATE OR REPLACE FUNCTION weight_pass_sql(weight integer) RETURNS text AS
$$
SELECT case when weight >= 40 then '합격' else '불합격' end;
$$ LANGUAGE SQL;
--
CREATE OR REPLACE FUNCTION weight_pass(weight integer) RETURNS text AS
$$
declare
result text;
BEGIN
if weight >= 40 then
result := '합격';
else
result := '불합격';
end if;
return result;
END
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION print_loop(n integer)
RETURNS void
AS
$$
declare
i integer := 1;
BEGIN
while i <= n loop
raise notice 'Loop: %', i;
i := i + 1;
end loop;
END
$$ LANGUAGE plpgsql;
SELECT print_loop(5);

CREATE OR REPLACE FUNCTION multivalue(in integer)
RETURNS table(f1 int, f2 text)
AS $$
begin
return QUERY
select $1, cast($1 as text) || 'is text';
end;
$$ LANGUAGE plpgsql;
SELECT multivalue(3);

CREATE OR REPLACE FUNCTION view_line_delivery(p_line char)
RETURNS TABLE(
line_id char,
client_name varchar,
product_count bigint
)
AS $$
BEGIN
RETURN QUERY
SELECT
p.line_id,
d.client_name,
COUNT(p.serial_no)
FROM product_info p
JOIN qa_result q ON p.serial_no = q.serial_no
JOIN delivery_log d ON d.serial_no = p.serial_no
WHERE q.qa_status = 'P'
AND p.line_id = p_line
GROUP BY p.line_id, d.client_name;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM view_line_delivery('B');

CREATE OR REPLACE FUNCTION decode_product_info(serial_no varchar)
RETURNS void
AS $$
DECLARE
v_product_id char;
v_year int;
v_factory_nm text;
BEGIN
v_product_id := LEFT(serial_no, 1);
v_year := ('20' || SUBSTRING(serial_no, 2, 2))::int;
SELECT
CASE factory_code
WHEN 'A' THEN '기흥공장'
WHEN 'B' THEN '평택공장'
END
INTO v_factory_nm
FROM product_info
WHERE product_info.serial_no = decode_product_info.serial_no;
RAISE NOTICE '(''%'' , % , ''%'')',
v_product_id,
v_year,
v_factory_nm;
END;
$$ LANGUAGE plpgsql;
SELECT decode_product_info('P2600002');
-- ('P' , 2026 , '기흥공장')


일련의 SQL명령과 로직을 데이터베이스에 저장해두고, 필요할 때마다 호출하여 실행할 수 있는 코드 블록
CREATE [OR REPLACE] PROCEDURE 프로시저_이름(파라미터_목록)
LANGUAGE plpgsql
AS $$
BEGIN
-- SQL 로직
END;
$$;

SELECT * FROM pg_available_extensions WHERE comment like '%procedural language';
CREATE OR REPLACE PROCEDURE model_prod_proc()
AS $$
INSERT INTO model_prod_tbl(prod_date, model_nm, total_sum, save_time)
(
SELECT prod_date, model_nm, total_sum, CURRENT_TIMESTAMP AS save_time
FROM model_prod
WHERE prod_date = '2026-02-04'
);
$$ LANGUAGE SQL;
CALL model_prod_proc();
특정 작업(쿼리, 함수, 프로시저 등)을 예약된 시간이나 주기로 자동 실행하는 기능을 의미

















DO $$
DECLARE
jid integer;
scid integer;
BEGIN
-- Creating a new job
INSERT INTO pgagent.pga_job(
jobjclid, jobname, jobdesc, jobhostagent, jobenabled
) VALUES (
1::integer, 'model_prod_proc_job'::text, '모델별 생산실적 입력 프로시저 스케줄링'::text, ''::text, true
) RETURNING jobid INTO jid;
-- Steps
-- Inserting a step (jobid: NULL)
INSERT INTO pgagent.pga_jobstep (
jstjobid, jstname, jstenabled, jstkind,
jstconnstr, jstdbname, jstonerror,
jstcode, jstdesc
) VALUES (
jid, 'step'::text, true, 's'::character(1),
''::text, 'postgres'::name, 'f'::character(1),
'CALL practice.model_prod_proc();'::text, ''::text
) ;
-- Schedules
-- Inserting a schedule
INSERT INTO pgagent.pga_schedule(
jscjobid, jscname, jscdesc, jscenabled,
jscstart, jscend, jscminutes, jschours, jscweekdays, jscmonthdays, jscmonths
) VALUES (
jid, 'everyminute'::text, ''::text, true,
'2026-01-28 14:50:00+09'::timestamp with time zone, '2026-01-28 14:55:00+09'::timestamp with time zone,
-- Minutes
'{t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t}'::bool[]::boolean[],
-- Hours
'{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}'::bool[]::boolean[],
-- Week days
'{f,f,f,f,f,f,f}'::bool[]::boolean[],
-- Month days
'{f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f}'::bool[]::boolean[],
-- Months
'{f,f,f,f,f,f,f,f,f,f,f,f}'::bool[]::boolean[]
) RETURNING jscid INTO scid;
END
$$;

DO $body$
DECLARE
file_path text;
BEGIN
file_path := 'C:/Users/Public/view_line_delivery_' || to_char(now(),'YYYYMMDD_HH24MISS') || '.csv';
EXECUTE format(
'COPY (SELECT * from practice.view_line_delivery(''A'')) TO %L CSV HEADER', file_path
);
END;
$body$;

(제품번호, 날짜, 중량) 을 입력받아 해당하는 제품-날짜 레코드가 테이블에 있는 경우 중량을 업데이트 하고 별도의 로그 테이블에 기록을 남기며, 번호나 날짜가 일치하지 않을 경우 로그 테이블에 오류로그와 함께 기록하는 프로시저 작성
CREATE TABLE IF NOT EXISTS practice.prod_log (
log_id SERIAL PRIMARY KEY,
serial_no VARCHAR(20) NOT NULL,
prod_date DATE NOT NULL,
old_weight NUMERIC,
new_weight NUMERIC,
memo TEXT,
logged_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE OR REPLACE PROCEDURE practice.update_and_log_current_weight(
s_no varchar,
p_date date,
weight integer
)
LANGUAGE plpgsql
AS $$
DECLARE
v_old_weight integer;
v_prod_start_date date;
BEGIN
-- serial_no 존재 여부 확인
SELECT target_weight, prod_start_date
INTO v_old_weight, v_prod_start_date
FROM practice.product_info
WHERE serial_no = s_no;
-- 대상 없음
IF NOT FOUND THEN
INSERT INTO practice.prod_log (
serial_no,
prod_date,
new_weight,
memo
)
VALUES (
s_no,
p_date,
weight,
format('대상 없음: %s', s_no)
);
-- 날짜 불일치
ELSIF v_prod_start_date != p_date THEN
INSERT INTO practice.prod_log (
serial_no,
prod_date,
new_weight,
memo
)
VALUES (
s_no,
p_date,
weight,
format('날짜 불일치: %s', p_date)
);
-- 정상 업데이트
ELSE
UPDATE practice.product_info
SET
target_weight = weight
WHERE serial_no = s_no;
INSERT INTO practice.prod_log (
serial_no,
prod_date,
old_weight,
new_weight,
memo
)
VALUES (
s_no,
p_date,
v_old_weight,
weight,
'정상 업데이트'
);
END IF;
END;
$$;
-- 실행
CALL practice.update_and_log_current_weight('P2600002', '2026-01-22', 800);
CALL practice.update_and_log_current_weight('P2600002', '2026-11-22', 700);
CALL practice.update_and_log_current_weight('P2222222', '2022-02-22', 500);
