炭炭背单词免费
125.76MB · 2025-10-10
PL/pgSQL 是 PostgreSQL 自带的过程化SQL语言(Procedural Language/PostgreSQL Structured Query Language),它将 SQL 的声明式语法(比如 SELECT、INSERT)与过程式控制结构(比如条件判断、循环、错误处理)结合,让你能编写更复杂的数据库逻辑——比如计算、批量操作、事务控制等。
简单来说,SQL 是“做什么”,PL/pgSQL 是“怎么一步步做”。比如:用 SQL 可以查“用户表有多少人”,用 PL/pgSQL 可以写一个函数,先查数量,再根据数量发送通知,最后返回结果。
在 PostgreSQL 中,存储过程通常以函数(FUNCTION)或过程(PROCEDURE)的形式存在。我们先从最基础的函数讲起,它的核心结构是:
CREATE OR REPLACE FUNCTION 函数名(参数列表)
RETURNS 返回类型 AS $$ -- $$ 是字符串分隔符(代替单引号)
DECLARE
-- 变量声明(可选)
BEGIN
-- 逻辑代码(必须)
EXCEPTION
-- 错误处理(可选)
END;
$$ LANGUAGE plpgsql; -- 指定语言为PL/pgSQL
我们写一个计算两个整数之和的函数:
-- 创建函数:计算两数之和
CREATE OR REPLACE FUNCTION add_two_numbers(
a INT, -- 输入参数:整数a
b INT -- 输入参数:整数b
)
RETURNS INT AS $$
BEGIN
RETURN a + b; -- 返回结果
END;
$$ LANGUAGE plpgsql;
-- 调用函数:用SELECT获取返回值
SELECT add_two_numbers(3, 5); -- 结果:8
关键点:
CREATE OR REPLACE
:如果函数已存在,替换它(避免重复删除重建);RETURNS INT
:指定函数返回整数;$$ ... $$
:是美元引号,用于包裹PL/pgSQL代码(避免与SQL中的单引号冲突);SELECT
(因为函数有返回值)。PL/pgSQL 允许你声明变量(存储中间结果)和参数(接收外部输入),常见类型包括:
参数支持 IN
(默认,输入)、OUT
(输出)、INOUT
(既输入又输出)三种模式:
-- 示例:计算两数之和与乘积(INOUT参数)
CREATE OR REPLACE FUNCTION calc_sum_product(
IN a INT,
IN b INT,
OUT sum INT,
OUT product INT
) AS $$
BEGIN
sum := a + b; -- 赋值给OUT参数sum
product := a * b; -- 赋值给OUT参数product
END;
$$ LANGUAGE plpgsql;
-- 调用:获取多个返回值
SELECT * FROM calc_sum_product(3, 5); -- 结果:sum=8, product=15
用 DECLARE
块声明变量,支持:
INT
、TEXT
、NUMERIC
);%ROWTYPE
,对应表的一行数据);RECORD
,动态存储一行数据,无固定结构);%TYPE
,继承列或变量的类型)。示例:
CREATE OR REPLACE FUNCTION get_user_info(
user_id INT
) RETURNS TEXT AS $$
DECLARE
v_user users%ROWTYPE; -- 行类型变量:对应users表的一行
v_age INT; -- 基本类型变量
BEGIN
-- 查询用户信息存入v_user
SELECT * INTO v_user FROM users WHERE id = user_id;
-- 计算年龄(假设users表有birth_year列)
v_age := EXTRACT(YEAR FROM CURRENT_DATE) - v_user.birth_year;
-- 返回用户信息
RETURN 'User: ' || v_user.name || ', Age: ' || v_age;
END;
$$ LANGUAGE plpgsql;
关键点:
%ROWTYPE
:确保变量结构与表一致(表结构变化时,变量自动同步);:=
:是PL/pgSQL的赋值运算符(代替SQL的=
);||
:字符串拼接运算符(类似Java的+
)。PL/pgSQL 的核心是执行SQL语句和处理结果,常见操作包括:
如果SQL语句是固定的,可以直接写在BEGIN...END
中:
-- 示例:插入用户并返回ID
CREATE OR REPLACE FUNCTION insert_user(
user_name TEXT,
user_age INT
) RETURNS INT AS $$
DECLARE
new_user_id INT;
BEGIN
INSERT INTO users (name, age)
VALUES (user_name, user_age)
RETURNING id INTO new_user_id; -- 将插入的ID存入变量
RETURN new_user_id;
END;
$$ LANGUAGE plpgsql;
关键点:RETURNING ... INTO
:将INSERT/UPDATE/DELETE的结果存入变量。
如果SQL语句是动态的(比如表名或条件由参数决定),需要用 EXECUTE
语句,必须结合USING
绑定参数(避免SQL注入):
-- 示例:动态查询表的行数
CREATE OR REPLACE FUNCTION get_table_row_count(
table_name TEXT
) RETURNS BIGINT AS $$
DECLARE
row_count BIGINT;
BEGIN
-- 动态执行SQL:用$1占位符,USING传递参数
EXECUTE 'SELECT COUNT(*) FROM ' || quote_ident(table_name)
INTO row_count;
RETURN row_count;
END;
$$ LANGUAGE plpgsql;
-- 调用:查询users表的行数
SELECT get_table_row_count('users'); -- 结果:100(假设)
关键点:
quote_ident(table_name)
:将表名转义(避免表名包含特殊字符,比如user
是关键字);EXECUTE 'SQL' USING 参数
:绑定参数($1
对应第一个参数,$2
对应第二个,依此类推);EXECUTE
,否则PL/pgSQL会提前解析SQL(导致表名未找到错误)。PL/pgSQL 支持过程式控制结构,让你能根据条件分支或重复执行代码。
-- 示例:判断用户年龄阶段
CREATE OR REPLACE FUNCTION get_age_group(
user_age INT
) RETURNS TEXT AS $$
BEGIN
IF user_age < 18 THEN
RETURN 'Minor';
ELSIF user_age BETWEEN 18 AND 60 THEN
RETURN 'Adult';
ELSE
RETURN 'Senior';
END IF;
END;
$$ LANGUAGE plpgsql;
-- 调用:
SELECT get_age_group(25); -- 结果:Adult
(1) 简单循环(LOOP):用EXIT
退出循环:
-- 示例:计算1到n的和
CREATE OR REPLACE FUNCTION sum_from_1_to_n(
n INT
) RETURNS INT AS $$
DECLARE
total INT := 0;
i INT := 1;
BEGIN
LOOP
total := total + i;
i := i + 1;
EXIT WHEN i > n; -- 当i超过n时退出循环
END LOOP;
RETURN total;
END;
$$ LANGUAGE plpgsql;
(2) 遍历查询结果(FOR循环):最常用的循环方式,直接遍历SQL查询的结果集:
-- 示例:批量更新用户年龄(增加1岁)
CREATE OR REPLACE FUNCTION increment_all_ages() RETURNS VOID AS $$
DECLARE
user_rec users%ROWTYPE; -- 行类型变量,存储每个用户的信息
BEGIN
-- 遍历users表的所有记录
FOR user_rec IN SELECT * FROM users LOOP
UPDATE users
SET age = user_rec.age + 1
WHERE id = user_rec.id;
END LOOP;
END;
$$ LANGUAGE plpgsql;
-- 调用(无返回值,用SELECT):
SELECT increment_all_ages();
用 EXCEPTION
块捕获并处理错误(比如除以零、主键冲突):
-- 示例:处理除以零的错误
CREATE OR REPLACE FUNCTION safe_divide(
numerator NUMERIC,
denominator NUMERIC
) RETURNS NUMERIC AS $$
BEGIN
RETURN numerator / denominator;
EXCEPTION
WHEN division_by_zero THEN -- 捕获除以零的错误
RAISE NOTICE 'Cannot divide by zero! Returning 0.'; -- 发送通知
RETURN 0; -- 返回默认值
WHEN OTHERS THEN -- 捕获其他所有错误
RAISE EXCEPTION 'Unexpected error: %', SQLERRM; -- 重新抛出错误
END;
$$ LANGUAGE plpgsql;
-- 调用:
SELECT safe_divide(10, 0); -- 结果:0,控制台输出NOTICE
关键点:
RAISE NOTICE
:输出调试信息(不会中断执行);RAISE EXCEPTION
:抛出错误(中断执行);SQLERRM
:返回错误信息(比如“division by zero”)。PostgreSQL 11 引入了过程(PROCEDURE),它与函数的核心区别是:
COMMIT
/ROLLBACK
);CALL
命令调用(不是SELECT
);示例:批量插入用户并处理事务
-- 创建过程:批量插入用户,失败则回滚
CREATE OR REPLACE PROCEDURE batch_insert_users(
user_list JSONB[] -- 输入参数:JSON数组,每个元素是用户信息
) LANGUAGE plpgsql AS $$
BEGIN
-- 开始事务(可选,PostgreSQL自动开启)
FOR i IN 1..array_length(user_list, 1) LOOP
INSERT INTO users (name, age)
VALUES (
user_list[i]->>'name', -- 取JSON中的name字段
(user_list[i]->>'age')::INT -- 转换为整数
);
END LOOP;
COMMIT; -- 提交事务
EXCEPTION
WHEN OTHERS THEN
ROLLBACK; -- 回滚事务
RAISE EXCEPTION 'Batch insert failed: %', SQLERRM; -- 抛出错误
END;
$$;
-- 调用过程:
CALL batch_insert_users(
ARRAY[
'{"name":"Alice","age":30}'::JSONB,
'{"name":"Bob","age":25}'::JSONB
]
);
关键点:
CREATE PROCEDURE
创建;JSONB[]
:JSON数组(适合批量数据);array_length(user_list, 1)
:获取JSON数组的长度;EXCEPTION
块中ROLLBACK
:如果插入失败,回滚所有操作。答案:使用EXECUTE
语句结合USING
子句绑定参数,而非直接拼接字符串。例如:
EXECUTE 'SELECT * FROM users WHERE name = $1' USING user_name;
$1
是占位符,USING
传递的参数会被PostgreSQL自动转义,彻底避免SQL注入。
答案:
COMMIT
/ROLLBACK
,函数不能(默认运行在事务块中);CALL
调用,函数用SELECT
调用;RETURNS VOID
),过程可以没有;ERROR: syntax error at or near "END"
原因:BEGIN...END
块中的语句缺少分号,或END
后面没有分号。
解决:检查每个语句末尾的分号,确保END;
后面有分号。例如:
-- 错误写法:
BEGIN
RETURN 1 -- 缺少分号
END -- 缺少分号
-- 正确写法:
BEGIN
RETURN 1;
END;
ERROR: column "v_name" does not exist
原因:变量名与表的列名冲突(PL/pgSQL优先解析为列名)。
解决:
v_name
代替name
);%ROWTYPE
或%TYPE
声明变量时,确保表结构正确;SELECT name INTO v_name FROM users WHERE id = 1;
。ERROR: query has no destination for result data
原因:函数中执行了返回结果集的SELECT
,但没有将结果存入变量或返回。
解决:
SELECT COUNT(*) INTO v_count FROM users;
;RETURNS TABLE(...)
或RETURNS SETOF ...
,并用RETURN QUERY
返回结果:
CREATE OR REPLACE FUNCTION get_all_users()
RETURNS TABLE(name TEXT, age INT) AS $$
BEGIN
RETURN QUERY SELECT name, age FROM users; -- 返回结果集
END;
$$ LANGUAGE plpgsql;
余下文章内容请点击跳转至 个人博客页面 或者 扫码关注或者微信搜一搜:编程智域 前端至全栈交流与成长
,阅读完整的文章:PostgreSQL里的PL/pgSQL到底是啥?能让SQL从“说目标”变“讲步骤”?