简介
PostgreSQL是一款功能强大、灵活且安全的开源关系型数据库管理系统。在本文中,我们将介绍如何在PostgreSQL中来编写函数。
什么是函数?
函数是一个预定义的 SQL 代码块,可以接受参数、执行复杂的逻辑处理,并返回结果。它们可以在 SQL 查询中像使用内置函数一样被调用。通过自定义函数,你可以简化数据库操作,提高代码重用性,扩展数据库的功能和满足特定的业务需求,并使得数据库操作更加模块化和易于管理。
函数定义
sql
CREATE OR REPLACE FUNCTION function_name(parameters)
RETURNS return_datatype AS
$$
DECLARE
-- 声明变量
variable_name datatype;
BEGIN
-- 函数体
-- 执行 SQL 语句或其他操作
RETURN result;
END;
$$
在一个函数的最基本构成中,有以下几个部分:
CREATE OR REPLACE FUNCTION
:您可以使用create
来创建一个新函数,或使用replace
来替换一个现有函数。或者您可以同时使用create or replace
来处理任一情况。function_name
:这是你自定义的函数名。parameters
:这是你的函数参数列表,格式为param_name datatype
。如果有多个参数,使用逗号分隔。如果没有参数,则留空。RETURNS return_datatype
:这指定了函数返回的数据类型。如果它不返回任何内容,您可以使用returns void
。DECLARE
:这部分用于声明函数内部使用的变量。BEGIN ... END;
:这是函数体,包含函数实际执行的 SQL 语句或其他操作。RETURN result;
:这指定了函数的返回值。AS $$
: 函数包装器。任何被$$符号包围的内容都将是函数体的一部分。$$;
: 函数包装器的闭合符号。
优势
- 灵活性:自定义函数允许用户根据具体需求编写复杂的计算、数据处理或业务逻辑,以满足特定的应用场景。
- 封装性:通过将一系列相关的 SQL 语句和操作封装在一个函数中,可以提高代码的可读性和可维护性。
- 安全性:通过限制对底层数据的直接访问,自定义函数可以提供一定的安全性。此外,还可以利用 PostgreSQL 的权限管理机制来控制对函数的访问权限。
限制
- 性能开销:与内置函数相比,自定义函数可能会引入一些额外的性能开销。
- 调试和排错:由于自定义函数涉及复杂的逻辑和可能的依赖关系,因此在调试和排错方面可能会比内置函数更加困难。开发者需要仔细检查函数的代码,确保逻辑正确并处理所有可能的异常情况。
准备工作
登录MemFire Cloud平台,创建一个新应用,如下图所示:
应用创建成功后,即可获得一个云端的Postgres数据库。
下面是一个具体的例子,这个函数接受两个整数参数,并返回它们的和:
sql
CREATE OR REPLACE FUNCTION add_two_numbers(a integer, b integer)
RETURNS integer AS
$$
BEGIN
RETURN a + b;
END;
$$
LANGUAGE plpgsql;
你可以在数据库-函数页面,手动添加该函数,如下图所示:
在创建了这个函数之后,你就可以像调用内置函数一样调用它:
scss
SELECT add_two_numbers(5, 3); -- 返回 8
在SQL编辑器中执行上述命令,获得返回结果如下:
接下来,我们创建一个包含星球大战数据的数据库:
sql
create table planets (
id serial primary key,
name text
);
insert into planets
(id, name)
values
(1, 'Tattoine'),
(2, 'Alderaan'),
(3, 'Kashyyyk');
create table people (
id serial primary key,
name text,
planet_id bigint references planets
);
insert into people
(id, name, planet_id)
values
(1, 'Anakin Skywalker', 1),
(2, 'Luke Skywalker', 1),
(3, 'Princess Leia', 2),
(4, 'Chewbacca', 3);
在SQL编辑器中执行上述命令,在表编辑器可以查看结果如下:
接下来,我们可以创建一个返回所有星球的函数:
sql
create or replace function get_planets()
returns setof planets
language sql
as $$
select * from planets;
$$;
执行上述SQL命令,可以在数据库函数列表中查看该函数信息。
因为此函数返回一个表集,我们还可以应用过滤器和选择器。例如,如果我们只想要第一个星球:
csharp
select *
from get_planets()
where id = 1;
执行上述SQL命令,结果如下:
让我们创建一个函数,将新星球插入到星球表中并返回新ID。注意这次我们使用的是plpgsql
语言。
sql
create or replace function add_planet(name text)
returns bigint
language plpgsql
as $$
declare
new_row bigint;
begin
insert into planets(name)
values (add_planet.name)
returning id into new_row;
return new_row;
end;
$$;
同样,您可以通过选择查询在数据库内部执行此函数,或使用客户端库:
csharp
select * from add_planet('Jakku');
安全定义者与调用者
Postgres允许您指定函数是作为调用函数的用户(调用者)执行,还是作为函数创建者(定义者)执行。例如:
sql
create function hello_world()
returns text
language plpgsql
security definer set search_path = public
as $$
begin
select 'hello world';
end;
$$;
最好的做法是使用安全调用者(这也是默认设置)。如果您使用安全定义者,您必须设置search_path
。这限制了如果允许访问执行函数的用户不应该拥有的模式的潜在损害。
函数权限
默认情况下,数据库函数可以由任何角色执行。您可以通过更改默认权限,然后选择可以执行函数的角色来限制这一点。
sql
ALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC;
- 选择哪些角色可以执行函数
vbscript
GRANT EXECUTE ON FUNCTION hello_world TO authenticated;
GRANT EXECUTE ON FUNCTION hello_world TO service_role;
调试函数
您可以添加日志来帮助您调试函数。这对于复杂的函数特别推荐。
记录的良好目标包括:
- 非敏感变量的值
- 查询返回的结果
- 一般日志记录
要在仪表板的Postgres日志中创建自定义日志,您可以使用raise
关键字。默认情况下,有三个观察到的严重级别:
- log
- warning
- exception (错误级别)
sql
create function logging_example(
log_message text,
warning_message text,
error_message text
)
returns void
language plpgsql
as $$
begin
raise log 'logging message: %', log_message;
raise warning 'logging warning: %', warning_message;
-- 立即结束函数并回滚事务
raise exception 'logging error: %', error_message;
end;
$$;
select logging_example('LOGGED MESSAGE', 'WARNING MESSAGE', 'ERROR MESSAGE');
错误处理
您可以使用raise exception
关键字创建自定义错误。
一个常见的模式是在变量不符合条件时抛出错误:
sql
create or replace function error_if_null(some_val text)
returns text
language plpgsql
as $$
begin
-- 如果some_val为null则出错
if some_val is null then
raise exception 'some_val should not be NULL';
end if;
-- 如果它不为null,则返回some_val
return some_val;
end;
$$;
值检查是很常见的,所以Postgres提供了一个简写:assert
关键字。它使用以下格式:
sql
-- 当条件为false时抛出错误
assert <some condition>, 'message';
下面是一个例子:
sql
create function assert_example(name text)
returns uuid
language plpgsql
as $$
declare
student_id uuid;
begin
-- 将用户ID保存到user_id变量中
select
id into student_id
from attendance_table
where student = name;
-- 如果student_id为null,则抛出错误
assert student_id is not null, 'assert_example() ERROR: student not found';
-- 否则,返回用户ID
return student_id;
end;
$$;
select assert_example('Harry Potter');
错误消息也可以被捕获和修改,使用exception
关键字:
sql
create function error_example()
returns void
language plpgsql
as $$
begin
-- 失败:无法从不存在的表中读取
select * from table_that_does_not_exist;
exception
when others then
raise exception 'An error occurred in function <function name>: %', sqlerrm;
end;
$$;