掘金 后端 ( ) • 2024-04-09 13:38

简介

PostgreSQL是一款功能强大、灵活且安全的开源关系型数据库管理系统。在本文中,我们将介绍如何在PostgreSQL中来编写函数。

什么是函数?

函数是一个预定义的 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 $$: 函数包装器。任何被$$符号包围的内容都将是函数体的一部分。
  • $$;: 函数包装器的闭合符号。

优势

  1. 灵活性:自定义函数允许用户根据具体需求编写复杂的计算、数据处理或业务逻辑,以满足特定的应用场景。
  2. 封装性:通过将一系列相关的 SQL 语句和操作封装在一个函数中,可以提高代码的可读性和可维护性。
  3. 安全性:通过限制对底层数据的直接访问,自定义函数可以提供一定的安全性。此外,还可以利用 PostgreSQL 的权限管理机制来控制对函数的访问权限。

限制

  1. 性能开销:与内置函数相比,自定义函数可能会引入一些额外的性能开销。
  2. 调试和排错:由于自定义函数涉及复杂的逻辑和可能的依赖关系,因此在调试和排错方面可能会比内置函数更加困难。开发者需要仔细检查函数的代码,确保逻辑正确并处理所有可能的异常情况。

准备工作

登录MemFire Cloud平台,创建一个新应用,如下图所示:

img

应用创建成功后,即可获得一个云端的Postgres数据库。

下面是一个具体的例子,这个函数接受两个整数参数,并返回它们的和:

CREATE OR REPLACE FUNCTION add_two_numbers(a integer, b integer)  
RETURNS integer AS 
$$
  
BEGIN  
    RETURN a + b;  
END;  
​
$$
 LANGUAGE plpgsql;

你可以在数据库-函数页面,手动添加该函数,如下图所示:

img

在创建了这个函数之后,你就可以像调用内置函数一样调用它:

SELECT add_two_numbers(5, 3);  -- 返回 8

在SQL编辑器中执行上述命令,获得返回结果如下:

img

接下来,我们创建一个包含星球大战数据的数据库:

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编辑器中执行上述命令,在表编辑器可以查看结果如下:

img

接下来,我们可以创建一个返回所有星球的函数:

create or replace function get_planets()
returns setof planets
language sql
as $$
select * from planets;
$$;

执行上述SQL命令,可以在数据库函数列表中查看该函数信息。

img

因为此函数返回一个表集,我们还可以应用过滤器和选择器。例如,如果我们只想要第一个星球:

select *
from get_planets()
where id = 1;

执行上述SQL命令,结果如下:

img

让我们创建一个函数,将新星球插入到星球表中并返回新ID。注意这次我们使用的是plpgsql语言。

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;
$$;

同样,您可以通过选择查询在数据库内部执行此函数,或使用客户端库:

select * from add_planet('Jakku');

安全定义者与调用者

Postgres允许您指定函数是作为调用函数的用户(调用者)执行,还是作为函数创建者(定义者)执行。例如:

create function hello_world()
returns text
language plpgsql
security definer set search_path = public
as $$
begin
    select 'hello world';
end;
$$;

最好的做法是使用安全调用者(这也是默认设置)。如果您使用安全定义者,您必须设置search_path。这限制了如果允许访问执行函数的用户不应该拥有的模式的潜在损害。

函数权限

默认情况下,数据库函数可以由任何角色执行。您可以通过更改默认权限,然后选择可以执行函数的角色来限制这一点。

ALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC;
  • 选择哪些角色可以执行函数
GRANT EXECUTE ON FUNCTION hello_world TO authenticated;
GRANT EXECUTE ON FUNCTION hello_world TO service_role;

调试函数

您可以添加日志来帮助您调试函数。这对于复杂的函数特别推荐。

记录的良好目标包括:

  • 非敏感变量的值
  • 查询返回的结果
  • 一般日志记录

要在仪表板的Postgres日志中创建自定义日志,您可以使用raise关键字。默认情况下,有三个观察到的严重级别:

  • log
  • warning
  • exception (错误级别)
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关键字创建自定义错误。

一个常见的模式是在变量不符合条件时抛出错误:

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关键字。它使用以下格式:

-- 当条件为false时抛出错误
assert <some condition>, 'message';

下面是一个例子:

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关键字:

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;
$$;