参考文献: Regina Obe Leo Hsu 著, 丁奇鹏 译《PostgreSQL即学即用》
将若干 SQL 语句组合在一起然后作为一个单元来处理, 这种机制在一些数据库中被称为
函数不是仅仅将一堆 SQL 语句编排在一起即可, 其中还需要使用过程化语言(Procedural Language, PL)来对 SQL 语句的执行过程进行控制.
在 PostgreSQL 中, 你可以选择使用不同的语言来编写函数, 可选择的语言有很多, 其中
从 PostgreSQL 9.2 版开始新增了对
执行
postgres@localhost:5436 tutorial# select lanname from pg_language; lanname ---------- internal c sql plpgsql (4 行记录)
函数的基本结构如下:
CREATE OR REPLACE FUNCTION func_name(arg1 arg1_datatype DEFAULT arg1_default) RETURNS some_type | set_of_some_types | TABLE(...) AS $$ body_of_function $$ LANGUAGE language_of_function
定义函数时可以添加一些标记符来优化执行效率或提升安全性, 支持的标记符列表如下.
借助触发器机制可以实现自动捕捉数据变化事件并进行相应处理. PostgreSQL 对表和视图均支持建触发器.
大多数其他数据库仅允许使用 ANSI SQL 标准中定义的那些聚合函数, 比如
在 PostgreSQL 中, 一个聚合函数同时也可以作为窗口函数来使用.
你可以使用 PostgreSQL 所支持的包括 SQL 语言在内的几乎任何语言来编写聚合函数.
一个聚合函数一般是基于一个或者多个子函数实现的.
不管使用何种编程语言, 最终将
CREATE AGGREGATE my_agg(input data_type)( SFUNC = state function name, STYPE = state_type, FINALFUNC = final_function_name, INITCOND = initial_state_value, SORTOP = sort_operator );
有的情况下,
最后的
SELECT agg(col) FROM sometable; SELECT col FROM sometable ORDER BY col USING sortop LIMIT 1;
SQL 不是一种过程式语言, 因此你就无法用上比如条件分支判断这种过程控制语句. 此外还有一个更严重的限制, 那就是无法进行根据传入到函数中的实参即时组合成动态的SQL语句.
SQL 函数也有其优点. 查询规划器可以深入到 SQL 函数内部并对其中每一条 SQL 语句进行分析和优化, 该过程被称为
示例. 创建一个SQL函数, 其返回值为新插入的记录的唯一 ID.
CREATE OR REPLACE FUNCTION write_to_log(param_user_name varchar, param_description text) RETURNS integer AS $$ INSERT INTO logs(user_name, description) VALUES($1, $2) RETURNING log_id; $$ LANGUAGE 'sql' VOLATILE;
在创建此函数前, 检查以下
postgres@localhost:5436 tutorial# select * from logs;
log_id | user_name | description | log_ts
--------+-----------+-------------+------------------------
4 | hfxu | logged in | 2025-11-28 12:59:00+08
1 | robe | logged in | 2025-11-20 23:15:00+08
2 | lhsu | logged out | 2025-11-21 23:20:00+08
3 | mike | logged in | 2025-11-22 22:25:00+08
(4 行记录)
postgres@localhost:5436 tutorial# CREATE OR REPLACE FUNCTION write_to_log(param_user_name varchar, param_description text) tutorial-# RETURNS integer AS tutorial-# $$ tutorial$# INSERT INTO logs(user_name, description) VALUES($1, $2) tutorial$# RETURNING log_id; tutorial$# $$ tutorial-# LANGUAGE 'sql' VOLATILE; CREATE FUNCTION 时间:28.797 ms
使用
postgres@localhost:5436 tutorial# \df write_to_log
函数列表
架构模式 | 名称 | 结果资料型别 | 参数资料型别 | 型别
----------+--------------+--------------+-----------------------------------------------------------+------
public | write_to_log | integer | param_user_name character varying, param_description text | 常规
(1 行记录)
函数的所有信息都存储在
postgres@localhost:5436 tutorial# select proname, prosrc from pg_proc where proname='write_to_log';
proname | prosrc
--------------+---------------------------------------------------------
write_to_log | +
| INSERT INTO logs(user_name, description) VALUES($1, $2)+
| RETURNING log_id; +
|
(1 行记录)
测试,
SELECT write_to_log('alejandro', 'Woke up at noon.') As new_id;
postgres@localhost:5436 tutorial# SELECT write_to_log('alejandro', 'Woke up at noon.') As new_id;
new_id
--------
5
(1 行记录)
时间:16.856 ms
postgres@localhost:5436 tutorial# select * from logs;
log_id | user_name | description | log_ts
--------+-----------+------------------+----------------------------
4 | hfxu | logged in | 2025-11-28 12:59:00+08
5 | alejandro | Woke up at noon. | 2025-12-04 19:33:47.583+08
1 | robe | logged in | 2025-11-20 23:15:00+08
2 | lhsu | logged out | 2025-11-21 23:20:00+08
3 | mike | logged in | 2025-11-22 22:25:00+08
(5 行记录)
类似地, 也可以在 SQL 函数中更新数据并返回一个标量或者不返回. 示例如下.
CREATE OR REPLACE FUNCTION update_logs(log_id int, param_user_name varchar, param_description text) RETURNS void AS $$ UPDATE logs SET user_name=$2, description=$3, log_ts=CURRENT_TIMESTAMP WHERE log_id=$1; $$ LANGUAGE 'sql' VOLATILE;
postgres@localhost:5436 tutorial# CREATE OR REPLACE FUNCTION tutorial-# update_logs(log_id int, param_user_name varchar, param_description text) tutorial-# RETURNS void AS tutorial-# $$ tutorial$# UPDATE logs SET user_name=$2, description=$3, tutorial$# log_ts=CURRENT_TIMESTAMP WHERE log_id=$1; tutorial$# $$ tutorial-# LANGUAGE 'sql' VOLATILE; CREATE FUNCTION 时间:15.949 ms
使用以下语句来调用此函数.
SELECT update_logs(5, 'alejandro', 'Fell back asleep.');
postgres@localhost:5436 tutorial# SELECT update_logs(5, 'alejandro', 'Fell back asleep.');
update_logs
-------------
NULL
(1 行记录)
时间:11.790 ms
postgres@localhost:5436 tutorial# select * from logs;
log_id | user_name | description | log_ts
--------+-----------+-------------------+----------------------------
4 | hfxu | logged in | 2025-11-28 12:59:00+08
5 | alejandro | Fell back asleep. | 2025-12-04 19:39:07.292+08
1 | robe | logged in | 2025-11-20 23:15:00+08
2 | lhsu | logged out | 2025-11-21 23:20:00+08
3 | mike | logged in | 2025-11-22 22:25:00+08
(5 行记录)
基本上所有编程语言编写的函数都支持返回结果集, SQL 函数也不例外. SQL 函数有三种返回结果集的办法:
第一种的
下面的示例展现了三种返回结果集的方法.
CREATE OR REPLACE FUNCTION select_logs_rt(param_user_name varchar) RETURNS TABLE (log_id int, user_name varchar(50), description text, log_ts timestamptz) AS $$ SELECT log_id, user_name, description, log_ts FROM logs WHERE user_name = $1; $$ LANGUAGE 'sql' STABLE;
CREATE OR REPLACE FUNCTION select_logs_out(param_user_name varchar, OUT log_id int, OUT user_name varchar, OUT description text, OUT log_ts timestamptz) RETURNS SETOF record AS $$ SELECT * FROM logs WHERE user_name = $1; $$ LANGUAGE 'sql' STABLE;
CREATE OR REPLACE FUNCTION select_logs_so(param_user_name varchar) RETURNS SETOF logs AS $$ SELECT * FROM logs WHERE user_name = $1; $$ LANGUAGE 'sql' STABLE;
以上三种方式实现的函数的调用方法都是一致的.
SELECT * FROM select_logs_xxx('alejandro');
$n$ 个正数 $a_1, a_2, \ldots, a_n$ 的
几何平均值可以使用更高效的公式来计算:
该公式使用了对数来将连乘运算转换为加法运算, 因此计算机执行的效率更高.
为了实现几何平均值运算, 我们使用了两个子函数: 一个状态转换函数, 用于把对数运算结果相加; 一个最终处理函数, 用于对对数之和进行取幂运算. 此外我们还需要指定状态初始值为 0.
CREATE OR REPLACE FUNCTION geom_mean_state(prev numeric[2], next numeric)
RETURNS numeric[2] AS
$$
SELECT
CASE
WHEN $2 IS NULL OR $2 = 0 THEN $1
ELSE ARRAY[COALESCE($1[1],0)+ln($2), $1[2]+1]
END;
$$
LANGUAGE sql IMMUTABLE;
此状态切换函数有两个输入项:
如果第二个实参的值为
此外我们还需要一个最终处理函数, 该函数中需要将状态转换函数计算得到的两个值相除.
CREATE OR REPLACE FUNCTION geom_mena_final(numeric[2]) RETURNS numeric AS $$ SELECT CASE WHEN $1[2]>0 THEN exp($1[1]/$1[2]) ELSE 0 END; $$ LANGUAGE sql IMMUTABLE;
最后, 我们需要将前面定义的这些子函数整合到一起组成一个完整的聚合函数, 语法如下.
CREATE AGGREGATE geom_mean(numeric)(
SFUNC = geom_mean_state,
STYPE = numeric[],
FINALFUNC = geom_mean_final,
INITCOND = '{0,0}'
);
注意这里需要一个初始值 $(0,0)$. 该初始值的类型与 SFUNC 的实参类型一定是一致的.
下面使用
CREATE FUNCTION select_logs_rt(param_user_name varchar)
RETURNS TABLE(log_id int, user_name varchar(50), description text, log_ts timestamptz) AS
$$
BEGIN RETURN QUERY
SELECT log_id, user_name, description, log_ts FROM logs
WHERE user_name = param_user_name;
END;
$$
LANGUAGE 'plpgsql' STABLE;
由于 PostgreSQL 不支持使用 SQL 编写触发器函数, 因此 PL/pgSQL 就成了编写触发器函数的首选.
总共需要两个步骤:
这里第二步将触发器的函数与触发器本身分离开, 这是 PostgreSQL 的一个强大功能. 你可以将同一个触发器函数附加到多个触发器上, 从而实现触发器函数逻辑的重用. 该模式是 PostgreSQL 的独创功能.
由于触发器函数之间是完全独立的, 因此你可以为每个触发器函数选择不同的编程语言, 这些不同语言编写的触发器完全可以协同工作.
PostgreSQL 支持通过一个触发事件(
示例: 通过触发器对新插入的记录或者修改的记录打时间戳.
定义触发器函数
CREATE OR REPLACE FUNCTION trig_time_stamper()
RETURNS trigger AS
$$
BEGIN
NEW.upd_ts := CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$
LANGUAGE plpgsql VOLATILE;
注意这种修改记录新值的动作应该放在
CREATE TRIGGER trig_1 BEFORE INSERT ON UPDATE OF session_state, session_id ON web_sessions FOR EACH ROW EXECUTE PROCEDURE trig_time_stamper();
这里的触发器是“字段级触发”, 这是9.0版开始支持的一个特性, 通过该特性可以将触发器的触发时机精确到字段级别. 在9.0版之前, 只要发生了 UPDATE 或者 INSERT 动作, 上面示例中的触发器都会被触发; 因此如果要实现字段级触发控制就必须拿
请注意:
从 9.0 版开始, PostgreSQL 支持 Python2 和 Python3 两种语言.
你可以在同一个数据库中同时安装
这意味着你不能在同一个语句中同时调用分别由
首先搭建 Python 运行环境, 然后为 PostgreSQL 安装 Python 语言扩展包.
CREATE EXTENSION plpython2U; CREATE EXTENSION plpython3U;