PostgreSQL 存储过程 #
存储过程概述 #
存储过程是存储在数据库中的程序,可以执行复杂的业务逻辑。
text
┌─────────────────────────────────────────────────────────────┐
│ 函数 vs 存储过程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 函数(FUNCTION) │
│ ├── 必须返回值 │
│ ├── 可以在 SQL 语句中调用 │
│ ├── 不能执行事务控制 │
│ └── 适合计算、转换 │
│ │
│ 存储过程(PROCEDURE) │
│ ├── 可以不返回值 │
│ ├── 使用 CALL 调用 │
│ ├── 可以执行事务控制 │
│ └── 适合业务逻辑、批量操作 │
│ │
└─────────────────────────────────────────────────────────────┘
创建函数 #
基本函数 #
sql
-- 创建简单函数
CREATE FUNCTION add_numbers(a INTEGER, b INTEGER)
RETURNS INTEGER AS $$
BEGIN
RETURN a + b;
END;
$$ LANGUAGE plpgsql;
-- 调用函数
SELECT add_numbers(10, 20);
-- 输出:30
-- 在查询中使用
SELECT add_numbers(id, 100) FROM employees;
函数参数 #
sql
-- 带默认值的参数
CREATE FUNCTION greet(name VARCHAR, greeting VARCHAR DEFAULT 'Hello')
RETURNS VARCHAR AS $$
BEGIN
RETURN greeting || ', ' || name || '!';
END;
$$ LANGUAGE plpgsql;
SELECT greet('Alice'); -- Hello, Alice!
SELECT greet('Alice', 'Hi'); -- Hi, Alice!
-- 使用命名参数
SELECT greet(greeting := 'Welcome', name := 'Bob');
-- OUT 参数
CREATE FUNCTION get_employee_info(
emp_id INTEGER,
OUT emp_name VARCHAR,
OUT emp_salary DECIMAL
) AS $$
BEGIN
SELECT name, salary INTO emp_name, emp_salary
FROM employees WHERE id = emp_id;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM get_employee_info(1);
-- INOUT 参数
CREATE FUNCTION double_value(INOUT value INTEGER) AS $$
BEGIN
value := value * 2;
END;
$$ LANGUAGE plpgsql;
SELECT double_value(5); -- 10
-- 返回多行(RETURNS TABLE)
CREATE FUNCTION get_employees_by_dept(dept_name VARCHAR)
RETURNS TABLE (
id INTEGER,
name VARCHAR,
salary DECIMAL
) AS $$
BEGIN
RETURN QUERY
SELECT e.id, e.name, e.salary
FROM employees e
JOIN departments d ON e.department_id = d.id
WHERE d.name = dept_name;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM get_employees_by_dept('Engineering');
返回类型 #
sql
-- 返回标量
CREATE FUNCTION get_employee_count()
RETURNS INTEGER AS $$
DECLARE
count INTEGER;
BEGIN
SELECT COUNT(*) INTO count FROM employees;
RETURN count;
END;
$$ LANGUAGE plpgsql;
-- 返回记录
CREATE FUNCTION get_employee(emp_id INTEGER)
RETURNS RECORD AS $$
DECLARE
result RECORD;
BEGIN
SELECT * INTO result FROM employees WHERE id = emp_id;
RETURN result;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM get_employee(1) AS (id INTEGER, name VARCHAR, department_id INTEGER, salary DECIMAL, hire_date DATE, is_active BOOLEAN);
-- 返回 SETOF
CREATE FUNCTION get_all_employees()
RETURNS SETOF employees AS $$
BEGIN
RETURN QUERY SELECT * FROM employees;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM get_all_employees();
PL/pgSQL 语法 #
变量声明 #
sql
CREATE FUNCTION variable_example()
RETURNS VOID AS $$
DECLARE
-- 基本类型
v_integer INTEGER := 10;
v_varchar VARCHAR(100) := 'Hello';
v_boolean BOOLEAN := TRUE;
v_decimal DECIMAL(10, 2) := 99.99;
v_date DATE := CURRENT_DATE;
-- 行类型
v_employee employees%ROWTYPE;
-- 列类型
v_name employees.name%TYPE;
-- 记录类型
v_record RECORD;
-- 数组
v_array INTEGER[] := ARRAY[1, 2, 3];
-- 常量
v_constant CONSTANT INTEGER := 100;
BEGIN
-- 使用变量
v_name := 'Alice';
-- 从查询赋值
SELECT * INTO v_employee FROM employees WHERE id = 1;
-- 使用行类型字段
RAISE NOTICE 'Employee name: %', v_employee.name;
END;
$$ LANGUAGE plpgsql;
控制结构 #
sql
-- IF 语句
CREATE FUNCTION check_salary(salary DECIMAL)
RETURNS VARCHAR AS $$
BEGIN
IF salary >= 80000 THEN
RETURN 'High';
ELSIF salary >= 60000 THEN
RETURN 'Medium';
ELSE
RETURN 'Low';
END IF;
END;
$$ LANGUAGE plpgsql;
-- CASE 语句
CREATE FUNCTION get_department_name(dept_id INTEGER)
RETURNS VARCHAR AS $$
BEGIN
CASE dept_id
WHEN 1 THEN RETURN 'Engineering';
WHEN 2 THEN RETURN 'Sales';
WHEN 3 THEN RETURN 'Marketing';
ELSE RETURN 'Unknown';
END CASE;
END;
$$ LANGUAGE plpgsql;
-- 循环
CREATE FUNCTION loop_example()
RETURNS VOID AS $$
DECLARE
i INTEGER := 0;
BEGIN
-- 基本循环
LOOP
i := i + 1;
EXIT WHEN i > 5;
RAISE NOTICE 'i = %', i;
END LOOP;
-- WHILE 循环
i := 0;
WHILE i < 5 LOOP
i := i + 1;
RAISE NOTICE 'while i = %', i;
END LOOP;
-- FOR 循环(整数)
FOR i IN 1..5 LOOP
RAISE NOTICE 'for i = %', i;
END LOOP;
-- FOR 循环(反向)
FOR i IN REVERSE 5..1 LOOP
RAISE NOTICE 'reverse i = %', i;
END LOOP;
-- FOR 循环(查询结果)
DECLARE
emp RECORD;
BEGIN
FOR emp IN SELECT * FROM employees LOOP
RAISE NOTICE 'Employee: %, Salary: %', emp.name, emp.salary;
END LOOP;
END;
END;
$$ LANGUAGE plpgsql;
-- CONTINUE
CREATE FUNCTION continue_example()
RETURNS VOID AS $$
BEGIN
FOR i IN 1..10 LOOP
CONTINUE WHEN i % 2 = 0; -- 跳过偶数
RAISE NOTICE 'Odd number: %', i;
END LOOP;
END;
$$ LANGUAGE plpgsql;
异常处理 #
sql
CREATE FUNCTION safe_divide(a DECIMAL, b DECIMAL)
RETURNS DECIMAL AS $$
BEGIN
RETURN a / b;
EXCEPTION
WHEN division_by_zero THEN
RAISE NOTICE 'Division by zero attempted';
RETURN NULL;
WHEN OTHERS THEN
RAISE NOTICE 'Error: %', SQLERRM;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
-- 自定义异常
CREATE FUNCTION check_employee_age(age INTEGER)
RETURNS VOID AS $$
BEGIN
IF age < 18 THEN
RAISE EXCEPTION 'Employee must be at least 18 years old';
ELSIF age > 65 THEN
RAISE EXCEPTION 'Employee age cannot exceed 65';
END IF;
RAISE NOTICE 'Age is valid: %', age;
END;
$$ LANGUAGE plpgsql;
游标 #
基本游标 #
sql
CREATE FUNCTION process_employees()
RETURNS VOID AS $$
DECLARE
emp_cursor CURSOR FOR SELECT * FROM employees;
emp RECORD;
BEGIN
OPEN emp_cursor;
LOOP
FETCH emp_cursor INTO emp;
EXIT WHEN NOT FOUND;
RAISE NOTICE 'Processing: %, Salary: %', emp.name, emp.salary;
-- 可以执行复杂逻辑
IF emp.salary < 50000 THEN
UPDATE employees SET salary = salary * 1.1 WHERE id = emp.id;
END IF;
END LOOP;
CLOSE emp_cursor;
END;
$$ LANGUAGE plpgsql;
参数化游标 #
sql
CREATE FUNCTION process_employees_by_dept(dept_id INTEGER)
RETURNS VOID AS $$
DECLARE
emp_cursor CURSOR (d_id INTEGER) FOR
SELECT * FROM employees WHERE department_id = d_id;
emp RECORD;
BEGIN
OPEN emp_cursor(dept_id);
LOOP
FETCH emp_cursor INTO emp;
EXIT WHEN NOT FOUND;
RAISE NOTICE 'Employee: %', emp.name;
END LOOP;
CLOSE emp_cursor;
END;
$$ LANGUAGE plpgsql;
FOR 游标 #
sql
CREATE FUNCTION process_all_employees()
RETURNS VOID AS $$
DECLARE
emp RECORD;
BEGIN
FOR emp IN SELECT * FROM employees ORDER BY salary DESC LOOP
RAISE NOTICE 'Employee: %, Salary: %', emp.name, emp.salary;
END LOOP;
END;
$$ LANGUAGE plpgsql;
创建存储过程 #
基本存储过程 #
sql
-- 创建存储过程
CREATE PROCEDURE transfer_salary(
from_emp_id INTEGER,
to_emp_id INTEGER,
amount DECIMAL
) AS $$
DECLARE
from_salary DECIMAL;
BEGIN
-- 获取源员工薪资
SELECT salary INTO from_salary FROM employees WHERE id = from_emp_id;
IF from_salary < amount THEN
RAISE EXCEPTION 'Insufficient salary';
END IF;
-- 执行转账
UPDATE employees SET salary = salary - amount WHERE id = from_emp_id;
UPDATE employees SET salary = salary + amount WHERE id = to_emp_id;
RAISE NOTICE 'Transferred % from employee % to employee %', amount, from_emp_id, to_emp_id;
END;
$$ LANGUAGE plpgsql;
-- 调用存储过程
CALL transfer_salary(1, 2, 5000.00);
带事务控制的存储过程 #
sql
CREATE PROCEDURE batch_update_employees()
AS $$
BEGIN
-- 开始事务
-- 存储过程内可以控制事务
UPDATE employees SET salary = salary * 1.1 WHERE department_id = 1;
COMMIT;
UPDATE employees SET salary = salary * 1.05 WHERE department_id = 2;
COMMIT;
-- 可以回滚部分操作
-- ROLLBACK;
END;
$$ LANGUAGE plpgsql;
动态 SQL #
sql
CREATE FUNCTION dynamic_query(table_name VARCHAR, column_name VARCHAR, value VARCHAR)
RETURNS SETOF RECORD AS $$
DECLARE
query TEXT;
result RECORD;
BEGIN
query := format('SELECT * FROM %I WHERE %I = %L', table_name, column_name, value);
FOR result IN EXECUTE query LOOP
RETURN NEXT result;
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
-- 使用动态 SQL 更新
CREATE FUNCTION update_column_value(
table_name VARCHAR,
column_name VARCHAR,
old_value VARCHAR,
new_value VARCHAR
) RETURNS INTEGER AS $$
DECLARE
query TEXT;
affected_rows INTEGER;
BEGIN
query := format(
'UPDATE %I SET %I = %L WHERE %I = %L',
table_name, column_name, new_value, column_name, old_value
);
EXECUTE query;
GET DIAGNOSTICS affected_rows = ROW_COUNT;
RETURN affected_rows;
END;
$$ LANGUAGE plpgsql;
触发器函数 #
sql
-- 创建触发器函数
CREATE OR REPLACE FUNCTION update_modified_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at := CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 创建表
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
price DECIMAL(10, 2),
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 创建触发器
CREATE TRIGGER trg_products_updated_at
BEFORE UPDATE ON products
FOR EACH ROW
EXECUTE FUNCTION update_modified_at();
函数管理 #
查看函数 #
sql
-- 查看所有函数
SELECT
proname AS function_name,
pg_get_functiondef(oid) AS definition
FROM pg_proc
WHERE pronamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public');
-- 使用 psql 命令
\df
\df+ function_name
修改和删除 #
sql
-- 替换函数
CREATE OR REPLACE FUNCTION add_numbers(a INTEGER, b INTEGER)
RETURNS INTEGER AS $$
BEGIN
RETURN a + b + 100;
END;
$$ LANGUAGE plpgsql;
-- 删除函数
DROP FUNCTION add_numbers(INTEGER, INTEGER);
-- 如果存在则删除
DROP FUNCTION IF EXISTS add_numbers(INTEGER, INTEGER);
学习路径 #
text
高级阶段
├── 索引
├── 视图
├── 存储过程(本文)
├── 触发器
└── 事务与锁
下一步 #
掌握了存储过程后,接下来学习 触发器,了解如何自动执行操作!
最后更新:2026-03-29