PostgreSQL教程FG151-PG扩展SQL:自定义函数与操作符
本文档风哥主要介绍PostgreSQL数据库扩展SQL的方法,包括自定义函数和操作符的创建、使用和优化,风哥教程参考PostgreSQL官方文档Server Programming和SQL Language内容,适合开发人员在学习和测试中使用,如果要应用于生产环境则需要自行确认。
Part01-基础概念与理论知识
1.1 PostgreSQL数据库自定义函数概念
PostgreSQL数据库自定义函数是用户根据业务需求创建的函数,可以使用多种过程化语言编写,如PL/pgSQL、PL/Tcl、PL/Perl、PL/Python等。更多视频教程www.fgedu.net.cn
- 函数签名:函数名、参数类型和返回类型
- 函数体:函数的实现代码
- 语言:编写函数使用的过程化语言
- 参数:函数接受的输入参数
- 返回值:函数返回的结果
- 副作用:函数对数据库的修改操作
1.2 PostgreSQL数据库自定义操作符概念
PostgreSQL数据库自定义操作符是用户定义的运算符,可以为特定的数据类型实现自定义的操作逻辑。
## 1. 操作符组成
– 操作符符号:如 +, -, *, /, @, # 等
– 操作数类型:操作符左侧和右侧的参数类型
– 结果类型:操作符返回的结果类型
– 实现函数:执行操作的函数
– 优先级:操作符的执行优先级
– 结合性:操作符的结合方向(左结合或右结合)
## 2. 操作符分类
– 一元操作符:只接受一个操作数
– 二元操作符:接受两个操作数
– 三元操作符:接受三个操作数(PostgreSQL不直接支持)
## 3. 操作符重载
– 可以为不同的数据类型定义相同的操作符符号
– PostgreSQL会根据操作数类型选择合适的操作符实现
1.3 PostgreSQL数据库扩展SQL的优势
PostgreSQL数据库扩展SQL的优势包括:
## 1. 业务逻辑封装
– 将复杂的业务逻辑封装为函数
– 提高代码复用性
– 简化SQL语句
– 便于维护和更新
## 2. 性能优化
– 减少网络传输开销
– 利用数据库服务器的计算能力
– 减少应用程序的负担
– 优化查询执行计划
## 3. 类型系统扩展
– 为自定义数据类型定义操作符
– 实现类型之间的转换
– 增强数据库的表达能力
– 提供更自然的查询语法
## 4. 功能扩展
– 实现数据库本身不支持的功能
– 集成外部系统和服务
– 扩展SQL的表达能力
– 满足特定业务需求
## 5. 安全性
– 控制数据访问权限
– 实现数据验证和约束
– 防止SQL注入攻击
– 增强数据安全性
Part02-生产环境规划与建议
2.1 PostgreSQL数据库自定义函数规划
生产环境中,自定义函数规划建议:
## 1. 函数设计
– 函数命名:使用有意义的名称,遵循命名规范
– 参数设计:合理设置参数类型和默认值
– 返回值设计:明确返回类型和语义
– 错误处理:实现完善的错误处理机制
## 2. 性能考虑
– 函数复杂度:避免过于复杂的函数
– 执行计划:考虑函数对查询计划的影响
– 缓存策略:合理使用函数缓存
– 资源消耗:监控函数的CPU和内存使用
## 3. 维护性
– 代码组织:模块化设计,便于维护
– 文档:为函数编写详细的文档
– 版本控制:使用版本控制系统管理函数代码
– 测试:编写单元测试和集成测试
## 4. 安全性
– 权限控制:合理设置函数的执行权限
– 输入验证:验证函数参数的有效性
– 防止SQL注入:使用参数化查询
– 敏感数据处理:保护敏感数据
## 5. 部署策略
– 部署时机:在应用部署前测试函数
– 版本管理:跟踪函数的版本变化
– 回滚机制:准备函数的回滚方案
– 监控:监控函数的执行情况
2.2 PostgreSQL数据库自定义操作符规划
自定义操作符规划建议:
## 1. 操作符设计
– 操作符选择:选择合适的操作符符号
– 操作数类型:明确操作数的类型
– 结果类型:明确返回的结果类型
– 实现函数:选择或创建合适的实现函数
## 2. 优先级和结合性
– 优先级设置:根据操作符的语义设置合适的优先级
– 结合性设置:根据操作符的语义设置合适的结合性
– 冲突处理:避免操作符冲突
– 一致性:保持操作符行为的一致性
## 3. 性能考虑
– 实现效率:优化操作符的实现函数
– 索引支持:考虑为操作符创建索引支持
– 执行计划:考虑操作符对查询计划的影响
– 缓存策略:合理使用操作符缓存
## 4. 维护性
– 文档:为操作符编写详细的文档
– 版本控制:跟踪操作符的版本变化
– 测试:测试操作符的各种使用场景
– 兼容性:考虑操作符的兼容性
## 5. 安全性
– 输入验证:验证操作数的有效性
– 错误处理:实现完善的错误处理机制
– 权限控制:合理设置操作符的使用权限
– 边界情况:处理边界情况和异常输入
2.3 PostgreSQL数据库扩展SQL最佳实践
扩展SQL的最佳实践:
- 优先使用PL/pgSQL编写函数,因为它与PostgreSQL的集成最好
- 避免在函数中使用过多的业务逻辑,保持函数的单一职责
- 为自定义操作符提供清晰的文档和使用示例
- 定期测试和优化扩展SQL的性能
- 使用版本控制系统管理扩展SQL的代码
学习交流加群风哥微信: itpux-com
Part03-生产环境项目实施方案
3.1 PostgreSQL数据库自定义函数实现
## 1. PL/pgSQL函数
### 1.1 创建简单函数
“`sql
CREATE OR REPLACE FUNCTION fgedu_add(a INTEGER, b INTEGER)
RETURNS INTEGER AS $$
BEGIN
RETURN a + b;
END;
$$ LANGUAGE plpgsql;
“`
### 1.2 创建带默认参数的函数
“`sql
CREATE OR REPLACE FUNCTION fgedu_greet(name VARCHAR DEFAULT ‘World’)
RETURNS VARCHAR AS $$
BEGIN
RETURN ‘Hello, ‘ || name || ‘!’;
END;
$$ LANGUAGE plpgsql;
“`
### 1.3 创建带异常处理的函数
“`sql
CREATE OR REPLACE FUNCTION fgedu_divide(a NUMERIC, b NUMERIC)
RETURNS NUMERIC AS $$
BEGIN
IF b = 0 THEN
RAISE EXCEPTION ‘Division by zero’;
END IF;
RETURN a / b;
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION ‘Error: %’, SQLERRM;
END;
$$ LANGUAGE plpgsql;
“`
### 1.4 创建返回表的函数
“`sql
CREATE OR REPLACE FUNCTION fgedu_get_students()
RETURNS TABLE(id INTEGER, name VARCHAR, age INTEGER)
AS $$
BEGIN
RETURN QUERY
SELECT s.id, s.name, s.age
FROM fgedu_student s;
END;
$$ LANGUAGE plpgsql;
“`
## 2. SQL函数
### 2.1 创建简单SQL函数
“`sql
CREATE OR REPLACE FUNCTION fgedu_square(x INTEGER)
RETURNS INTEGER AS $$
SELECT x * x;
$$ LANGUAGE sql;
“`
### 2.2 创建带参数的SQL函数
“`sql
CREATE OR REPLACE FUNCTION fgedu_calculate_discount(price NUMERIC, discount_rate NUMERIC)
RETURNS NUMERIC AS $$
SELECT price * (1 – discount_rate / 100);
$$ LANGUAGE sql;
“`
## 3. 其他语言函数
### 3.1 PL/Python函数
“`sql
CREATE OR REPLACE FUNCTION fgedu_python_upper(text VARCHAR)
RETURNS VARCHAR AS $$
return text.upper()
$$ LANGUAGE plpython3u;
“`
### 3.2 PL/Perl函数
“`sql
CREATE OR REPLACE FUNCTION fgedu_perl_upper(text VARCHAR)
RETURNS VARCHAR AS $$
return uc($_[0]);
$$ LANGUAGE plperl;
“`
3.2 PostgreSQL数据库自定义操作符实现
## 1. 创建操作符
### 1.1 创建简单操作符
“`sql
— 首先创建实现函数
CREATE OR REPLACE FUNCTION fgedu_cube(n INTEGER)
RETURNS INTEGER AS $$
BEGIN
RETURN n * n * n;
END;
$$ LANGUAGE plpgsql;
— 创建操作符
CREATE OPERATOR ^# (
PROCEDURE = fgedu_cube,
LEFTARG = INTEGER,
RIGHTARG = NONE
);
“`
### 1.2 创建二元操作符
“`sql
— 首先创建实现函数
CREATE OR REPLACE FUNCTION fgedu_power(base INTEGER, exponent INTEGER)
RETURNS INTEGER AS $$
DECLARE
result INTEGER := 1;
i INTEGER;
BEGIN
FOR i IN 1..exponent LOOP
result := result * base;
END LOOP;
RETURN result;
END;
$$ LANGUAGE plpgsql;
— 创建操作符
CREATE OPERATOR ** (
PROCEDURE = fgedu_power,
LEFTARG = INTEGER,
RIGHTARG = INTEGER
);
“`
### 1.3 创建带优先级和结合性的操作符
“`sql
— 创建操作符,设置优先级为5,左结合
CREATE OPERATOR @@ (
PROCEDURE = fgedu_custom_operation,
LEFTARG = INTEGER,
RIGHTARG = INTEGER,
PRIORITY = 5,
ASSOCIATION = LEFT
);
“`
## 2. 操作符重载
### 2.1 为不同类型重载操作符
“`sql
— 为INTEGER类型创建操作符
CREATE OR REPLACE FUNCTION fgedu_add(integer, integer)
RETURNS integer AS $$
SELECT $1 + $2;
$$ LANGUAGE sql;
— 为NUMERIC类型创建操作符
CREATE OR REPLACE FUNCTION fgedu_add(numeric, numeric)
RETURNS numeric AS $$
SELECT $1 + $2;
$$ LANGUAGE sql;
— 创建操作符
CREATE OPERATOR +# (
PROCEDURE = fgedu_add,
LEFTARG = INTEGER,
RIGHTARG = INTEGER
);
CREATE OPERATOR +# (
PROCEDURE = fgedu_add,
LEFTARG = NUMERIC,
RIGHTARG = NUMERIC
);
“`
## 3. 操作符索引支持
### 3.1 创建操作符类
“`sql
— 创建操作符类
CREATE OPERATOR CLASS gist_point_ops
DEFAULT FOR TYPE point USING gist AS
OPERATOR 1 <-> (point, point),
OPERATOR 2 <@ (point, box),
OPERATOR 3 @> (point, box),
OPERATOR 4 ~= (point, point),
FUNCTION 1 gist_point_compress(internal),
FUNCTION 2 gist_point_decompress(internal),
FUNCTION 3 gist_point_consistent(internal, point, integer, oid, internal),
FUNCTION 4 gist_point_union(internal, internal),
FUNCTION 5 gist_point_same(point, point, internal);
“`
3.3 PostgreSQL数据库扩展SQL集成
## 1. 函数和操作符的管理
### 1.1 查看函数
“`sql
— 查看所有函数
\df
— 查看特定函数
\df fgedu_add
— 查看函数详细信息
SELECT * FROM pg_proc WHERE proname = ‘fgedu_add’;
“`
### 1.2 查看操作符
“`sql
— 查看所有操作符
\do
— 查看特定操作符
\do ^#
— 查看操作符详细信息
SELECT * FROM pg_operator WHERE oprname = ‘^#’;
“`
### 1.3 修改函数
“`sql
— 修改函数定义
CREATE OR REPLACE FUNCTION fgedu_add(a INTEGER, b INTEGER)
RETURNS INTEGER AS $$
BEGIN
RETURN a + b;
END;
$$ LANGUAGE plpgsql;
“`
### 1.4 删除函数和操作符
“`sql
— 删除函数
DROP FUNCTION IF EXISTS fgedu_add(INTEGER, INTEGER);
— 删除操作符
DROP OPERATOR IF EXISTS ^# (INTEGER);
“`
## 2. 扩展SQL的使用
### 2.1 在SQL语句中使用函数
“`sql
— 在SELECT语句中使用
SELECT fgedu_add(1, 2);
— 在WHERE子句中使用
SELECT * FROM fgedu_student WHERE fgedu_age_check(age);
— 在INSERT语句中使用
INSERT INTO fgedu_student (name, age) VALUES (fgedu_normalize_name(‘John Doe’), 20);
“`
### 2.2 在SQL语句中使用操作符
“`sql
— 使用自定义操作符
SELECT 2 ^#; — 计算2的立方
SELECT 2 ** 3; — 计算2的3次方
SELECT 10 +# 5; — 使用重载的加法操作符
“`
## 3. 扩展SQL的性能优化
### 3.1 函数性能优化
“`sql
— 使用IMMUTABLE标记纯函数
CREATE OR REPLACE FUNCTION fgedu_square(x INTEGER)
RETURNS INTEGER AS $$
SELECT x * x;
$$ LANGUAGE sql IMMUTABLE;
— 使用STABLE标记稳定函数
CREATE OR REPLACE FUNCTION fgedu_get_current_time()
RETURNS TIMESTAMP AS $$
SELECT CURRENT_TIMESTAMP;
$$ LANGUAGE sql STABLE;
— 使用VOLATILE标记易变函数
CREATE OR REPLACE FUNCTION fgedu_generate_random()
RETURNS INTEGER AS $$
SELECT floor(random() * 100);
$$ LANGUAGE sql VOLATILE;
“`
### 3.2 操作符性能优化
“`sql
— 为操作符创建索引支持
CREATE INDEX idx_student_age ON fgedu_student USING btree (age);
— 优化操作符实现函数
CREATE OR REPLACE FUNCTION fgedu_fast_operation(a INTEGER, b INTEGER)
RETURNS INTEGER AS $$
BEGIN
— 优化的实现
RETURN a * b;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
“`
Part04-生产案例与实战讲解
4.1 PostgreSQL数据库自定义函数实战
# custom_functions_practice.py
# from:www.itpux.com.qq113257174.wx:itpux-com
# web: `http://www.fgedu.net.cn`
import psycopg2
from psycopg2 import pool
# 数据库连接参数
DB_PARAMS = {
“fgedu.net.cn”: “localfgedu.net.cn”,
“port”: 5432,
“fgedudb”: “fgedudb”,
“fgedu”: “fgedu”,
“password”: “fgedu_password”
}
# 创建连接池
postgres_pool = None
def init_pool():
global postgres_pool
postgres_pool = pool.ThreadedConnectionPool(
minconn=5,
maxconn=20,
**DB_PARAMS
)
print(“连接池初始化成功”)
def get_connection():
“””从连接池获取连接”””
return postgres_pool.getconn()
def release_connection(conn):
“””将连接归还到连接池”””
postgres_pool.putconn(conn)
def execute_query(query, params=None):
“””执行SQL查询”””
conn = None
try:
conn = get_connection()
with conn.cursor() as cur:
cur.execute(query, params)
if query.strip().upper().startswith(“SELECT”):
result = cur.fetchall()
conn.commit()
return result
else:
conn.commit()
return cur.rowcount
except Exception as e:
if conn:
conn.rollback()
print(f”执行查询失败: {e}”)
raise
finally:
if conn:
release_connection(conn)
def create_functions():
“””创建自定义函数”””
# 创建计算学生平均成绩的函数
avg_grade_function = “””
CREATE OR REPLACE FUNCTION fgedu_calculate_avg_grade(student_id INTEGER)
RETURNS NUMERIC AS $$
DECLARE
avg_grade NUMERIC;
BEGIN
SELECT AVG(grade) INTO avg_grade
FROM fgedu_enrollment
WHERE student_id = $1;
RETURN COALESCE(avg_grade, 0);
END;
$$ LANGUAGE plpgsql;
“””
execute_query(avg_grade_function)
print(“计算学生平均成绩的函数创建成功”)
# 创建统计课程选课人数的函数
course_count_function = “””
CREATE OR REPLACE FUNCTION fgedu_count_course_enrollment(course_id INTEGER)
RETURNS INTEGER AS $$
DECLARE
count INTEGER;
BEGIN
SELECT COUNT(*) INTO count
FROM fgedu_enrollment
WHERE course_id = $1;
RETURN count;
END;
$$ LANGUAGE plpgsql;
“””
execute_query(course_count_function)
print(“统计课程选课人数的函数创建成功”)
# 创建检查学生年龄的函数
age_check_function = “””
CREATE OR REPLACE FUNCTION fgedu_age_check(age INTEGER)
RETURNS BOOLEAN AS $$
BEGIN
RETURN age >= 18 AND age <= 30;
END;
$$ LANGUAGE plpgsql;
"""
execute_query(age_check_function)
print("检查学生年龄的函数创建成功")
# 创建格式化学生信息的函数
format_student_function = """
CREATE OR REPLACE FUNCTION fgedu_format_student_info(id INTEGER)
RETURNS VARCHAR AS $$
DECLARE
student_record RECORD;
BEGIN
SELECT * INTO student_record
FROM fgedu_student
WHERE id = $1;
IF NOT FOUND THEN
RETURN 'Student not found';
END IF;
RETURN student_record.name || ', ' || student_record.age || ' years old, ' || student_record.major;
END;
$$ LANGUAGE plpgsql;
"""
execute_query(format_student_function)
print("格式化学生信息的函数创建成功")
def test_functions():
"""测试自定义函数"""
# 测试计算学生平均成绩的函数
print("\n测试计算学生平均成绩的函数:")
result = execute_query("SELECT fgedu_calculate_avg_grade(%s)", (1,))
print(f"学生1的平均成绩: {result[0][0]}")
# 测试统计课程选课人数的函数
print("\n测试统计课程选课人数的函数:")
result = execute_query("SELECT fgedu_count_course_enrollment(%s)", (1,))
print(f"课程1的选课人数: {result[0][0]}")
# 测试检查学生年龄的函数
print("\n测试检查学生年龄的函数:")
result = execute_query("SELECT fgedu_age_check(%s)", (20,))
print(f"年龄20是否有效: {result[0][0]}")
result = execute_query("SELECT fgedu_age_check(%s)", (17,))
print(f"年龄17是否有效: {result[0][0]}")
# 测试格式化学生信息的函数
print("\n测试格式化学生信息的函数:")
result = execute_query("SELECT fgedu_format_student_info(%s)", (1,))
print(f"学生1的信息: {result[0][0]}")
def main():
"""主函数"""
# 初始化连接池
init_pool()
try:
# 创建自定义函数
create_functions()
# 测试自定义函数
test_functions()
finally:
# 关闭连接池
postgres_pool.closeall()
print("\n连接池关闭成功")
if __name__ == "__main__":
main()
$ python custom_functions_practice.py
连接池初始化成功
计算学生平均成绩的函数创建成功
统计课程选课人数的函数创建成功
检查学生年龄的函数创建成功
格式化学生信息的函数创建成功
测试计算学生平均成绩的函数:
学生1的平均成绩: 90.0
测试统计课程选课人数的函数:
课程1的选课人数: 2
测试检查学生年龄的函数:
年龄20是否有效: True
年龄17是否有效: False
测试格式化学生信息的函数:
学生1的信息: 风哥1号, 21 years old, 人工智能
连接池关闭成功
4.2 PostgreSQL数据库自定义操作符实战
# custom_operators_practice.py
# from:www.itpux.com.qq113257174.wx:itpux-com
# web: `http://www.fgedu.net.cn`
import psycopg2
from psycopg2 import pool
# 数据库连接参数
DB_PARAMS = {
“fgedu.net.cn”: “localfgedu.net.cn”,
“port”: 5432,
“fgedudb”: “fgedudb”,
“fgedu”: “fgedu”,
“password”: “fgedu_password”
}
# 创建连接池
postgres_pool = None
def init_pool():
global postgres_pool
postgres_pool = pool.ThreadedConnectionPool(
minconn=5,
maxconn=20,
**DB_PARAMS
)
print(“连接池初始化成功”)
def get_connection():
“””从连接池获取连接”””
return postgres_pool.getconn()
def release_connection(conn):
“””将连接归还到连接池”””
postgres_pool.putconn(conn)
def execute_query(query, params=None):
“””执行SQL查询”””
conn = None
try:
conn = get_connection()
with conn.cursor() as cur:
cur.execute(query, params)
if query.strip().upper().startswith(“SELECT”):
result = cur.fetchall()
conn.commit()
return result
else:
conn.commit()
return cur.rowcount
except Exception as e:
if conn:
conn.rollback()
print(f”执行查询失败: {e}”)
raise
finally:
if conn:
release_connection(conn)
def create_operators():
“””创建自定义操作符”””
# 创建立方操作符
cube_function = “””
CREATE OR REPLACE FUNCTION fgedu_cube(n INTEGER)
RETURNS INTEGER AS $$
BEGIN
RETURN n * n * n;
END;
$$ LANGUAGE plpgsql;
“””
execute_query(cube_function)
cube_operator = “””
CREATE OPERATOR ^# (
PROCEDURE = fgedu_cube,
LEFTARG = INTEGER,
RIGHTARG = NONE
);
“””
execute_query(cube_operator)
print(“立方操作符创建成功”)
# 创建幂操作符
power_function = “””
CREATE OR REPLACE FUNCTION fgedu_power(base INTEGER, exponent INTEGER)
RETURNS INTEGER AS $$
DECLARE
result INTEGER := 1;
i INTEGER;
BEGIN
FOR i IN 1..exponent LOOP
result := result * base;
END LOOP;
RETURN result;
END;
$$ LANGUAGE plpgsql;
“””
execute_query(power_function)
power_operator = “””
CREATE OPERATOR ** (
PROCEDURE = fgedu_power,
LEFTARG = INTEGER,
RIGHTARG = INTEGER
);
“””
execute_query(power_operator)
print(“幂操作符创建成功”)
# 创建安全除法操作符
safe_divide_function = “””
CREATE OR REPLACE FUNCTION fgedu_safe_divide(a NUMERIC, b NUMERIC)
RETURNS NUMERIC AS $$
BEGIN
IF b = 0 THEN
RETURN NULL;
END IF;
RETURN a / b;
END;
$$ LANGUAGE plpgsql;
“””
execute_query(safe_divide_function)
safe_divide_operator = “””
CREATE OPERATOR /# (
PROCEDURE = fgedu_safe_divide,
LEFTARG = NUMERIC,
RIGHTARG = NUMERIC
);
“””
execute_query(safe_divide_operator)
print(“安全除法操作符创建成功”)
def test_operators():
“””测试自定义操作符”””
# 测试立方操作符
print(“\n测试立方操作符:”)
result = execute_query(“SELECT 2 ^#”)
print(f”2的立方: {result[0][0]}”)
result = execute_query(“SELECT 3 ^#”)
print(f”3的立方: {result[0][0]}”)
# 测试幂操作符
print(“\n测试幂操作符:”)
result = execute_query(“SELECT 2 ** 3″)
print(f”2的3次方: {result[0][0]}”)
result = execute_query(“SELECT 3 ** 4″)
print(f”3的4次方: {result[0][0]}”)
# 测试安全除法操作符
print(“\n测试安全除法操作符:”)
result = execute_query(“SELECT 10 /# 2″)
print(f”10 /# 2: {result[0][0]}”)
result = execute_query(“SELECT 10 /# 0″)
print(f”10 /# 0: {result[0][0]}”)
def main():
“””主函数”””
# 初始化连接池
init_pool()
try:
# 创建自定义操作符
create_operators()
# 测试自定义操作符
test_operators()
finally:
# 关闭连接池
postgres_pool.closeall()
print(“\n连接池关闭成功”)
if __name__ == “__main__”:
main()
$ python custom_operators_practice.py
连接池初始化成功
立方操作符创建成功
幂操作符创建成功
安全除法操作符创建成功
测试立方操作符:
2的立方: 8
3的立方: 27
测试幂操作符:
2的3次方: 8
3的4次方: 81
测试安全除法操作符:
10 /# 2: 5.0
10 /# 0: None
连接池关闭成功
4.3 PostgreSQL数据库扩展SQL案例分析
## 1. 案例一:业务逻辑封装
### 1.1 需求
– 计算学生的GPA(平均学分绩点)
– 根据成绩等级计算绩点
– 考虑课程学分权重
### 1.2 实现
“`sql
— 创建计算绩点的函数
CREATE OR REPLACE FUNCTION fgedu_calculate_gpa(student_id INTEGER)
RETURNS NUMERIC AS $$
DECLARE
total_points NUMERIC := 0;
total_credits NUMERIC := 0;
rec RECORD;
BEGIN
— 计算总绩点和总学分
FOR rec IN
SELECT e.grade, c.credit
FROM fgedu_enrollment e
JOIN fgedu_course c ON e.course_id = c.id
WHERE e.student_id = student_id AND e.grade IS NOT NULL
LOOP
— 根据成绩计算绩点
CASE
WHEN rec.grade >= 90 THEN
total_points := total_points + (4.0 * rec.credit);
WHEN rec.grade >= 80 THEN
total_points := total_points + (3.0 * rec.credit);
WHEN rec.grade >= 70 THEN
total_points := total_points + (2.0 * rec.credit);
WHEN rec.grade >= 60 THEN
total_points := total_points + (1.0 * rec.credit);
ELSE
total_points := total_points + (0.0 * rec.credit);
END CASE;
total_credits := total_credits + rec.credit;
END LOOP;
— 计算GPA
IF total_credits > 0 THEN
RETURN total_points / total_credits;
ELSE
RETURN 0;
END IF;
END;
$$ LANGUAGE plpgsql;
“`
### 1.3 使用
“`sql
— 查询学生的GPA
SELECT fgedu_calculate_gpa(1);
— 按GPA排序学生
SELECT s.id, s.name, fgedu_calculate_gpa(s.id) AS gpa
FROM fgedu_student s
ORDER BY gpa DESC;
“`
## 2. 案例二:数据验证
### 2.1 需求
– 验证学生信息的有效性
– 确保年龄在合理范围内
– 确保专业名称符合规范
### 2.2 实现
“`sql
— 创建验证学生信息的函数
CREATE OR REPLACE FUNCTION fgedu_validate_student(name VARCHAR, age INTEGER, major VARCHAR)
RETURNS VARCHAR AS $$
BEGIN
— 验证姓名
IF name IS NULL OR name = ” THEN
RETURN ‘Name cannot be empty’;
END IF;
— 验证年龄
IF age < 16 OR age > 35 THEN
RETURN ‘Age must be between 16 and 35’;
END IF;
— 验证专业
IF major IS NULL OR major = ” THEN
RETURN ‘Major cannot be empty’;
END IF;
RETURN ‘Valid’;
END;
$$ LANGUAGE plpgsql;
— 创建插入学生的函数
CREATE OR REPLACE FUNCTION fgedu_insert_student(name VARCHAR, age INTEGER, gender VARCHAR, major VARCHAR)
RETURNS INTEGER AS $$
DECLARE
validation_result VARCHAR;
student_id INTEGER;
BEGIN
— 验证学生信息
validation_result := fgedu_validate_student(name, age, major);
IF validation_result <> ‘Valid’ THEN
RAISE EXCEPTION ‘Validation failed: %’, validation_result;
END IF;
— 插入学生
INSERT INTO fgedu_student (name, age, gender, major)
VALUES (name, age, gender, major)
RETURNING id INTO student_id;
RETURN student_id;
END;
$$ LANGUAGE plpgsql;
“`
### 2.3 使用
“`sql
— 插入学生
SELECT fgedu_insert_student(‘赵六’, 22, ‘男’, ‘计算机科学’);
— 尝试插入无效学生
SELECT fgedu_insert_student(‘钱七’, 15, ‘女’, ‘软件工程’); — 年龄无效
“`
## 3. 案例三:数据转换
### 3.1 需求
– 将JSON格式的学生信息转换为关系型数据
– 支持批量导入学生数据
### 3.2 实现
“`sql
— 创建从JSON导入学生的函数
CREATE OR REPLACE FUNCTION fgedu_import_students_from_json(json_data JSONB)
RETURNS INTEGER AS $$
DECLARE
item JSONB;
count INTEGER := 0;
BEGIN
— 遍历JSON数组
FOR item IN SELECT * FROM jsonb_array_elements(json_data) LOOP
— 插入学生
INSERT INTO fgedu_student (
name,
age,
gender,
major
) VALUES (
item->>’name’,
(item->>’age’)::INTEGER,
item->>’gender’,
item->>’major’
);
count := count + 1;
END LOOP;
RETURN count;
END;
$$ LANGUAGE plpgsql;
“`
### 3.3 使用
“`sql
— 导入学生数据
SELECT fgedu_import_students_from_json(‘[
{“name”: “孙八”, “age”: 20, “gender”: “男”, “major”: “数据科学”},
{“name”: “周九”, “age”: 21, “gender”: “女”, “major”: “人工智能”}
]’::jsonb);
“`
Part05-风哥经验总结与分享
5.1 PostgreSQL数据库自定义函数性能优化
PostgreSQL数据库自定义函数性能优化经验:
- 使用合适的语言:对于简单函数,使用SQL语言;对于复杂逻辑,使用PL/pgSQL
- 合理使用函数标记:根据函数的特性使用IMMUTABLE、STABLE或VOLATILE标记
- 避免过度使用函数:对于简单操作,直接使用SQL语句可能更高效
- 优化函数内部逻辑:减少函数内部的循环和复杂计算
- 使用SQL语句代替PL/pgSQL:对于简单的数据操作,SQL函数通常比PL/pgSQL函数更高效
- 避免在函数中使用事务:函数内部的事务会增加开销
- 使用参数化查询:避免在函数中拼接SQL语句
- 监控函数性能:使用pg_stat_fgedu_functions视图监控函数的执行情况
5.2 PostgreSQL数据库自定义操作符性能优化
## 1. 优化实现函数
– 使用高效的实现算法
– 减少函数内部的开销
– 避免不必要的计算
– 使用合适的语言编写实现函数
## 2. 索引支持
– 为操作符创建索引支持
– 使用合适的索引类型
– 优化索引的使用
– 定期维护索引
## 3. 操作符设计
– 选择合适的操作符符号
– 合理设置操作符的优先级和结合性
– 避免操作符冲突
– 保持操作符行为的一致性
## 4. 缓存策略
– 使用IMMUTABLE标记操作符的实现函数
– 利用PostgreSQL的查询缓存
– 避免在操作符中使用易变函数
## 5. 监控和调优
– 监控操作符的执行情况
– 分析操作符对查询计划的影响
– 优化操作符的使用方式
– 定期测试和调优操作符性能
5.3 PostgreSQL数据库扩展SQL问题排查
## 1. 常见问题
### 1.1 函数执行错误
– 症状:函数执行时出现错误
– 原因:函数内部逻辑错误,参数类型不匹配,权限问题
– 解决方案:检查函数代码,验证参数类型,确保用户有执行权限
### 1.2 操作符冲突
– 症状:创建操作符时出现冲突错误
– 原因:操作符符号已被使用,参数类型组合已存在
– 解决方案:选择不同的操作符符号,或为不同的参数类型组合创建操作符
### 1.3 性能问题
– 症状:函数或操作符执行缓慢
– 原因:函数逻辑复杂,操作符实现效率低,缺少索引支持
– 解决方案:优化函数逻辑,改进操作符实现,添加索引支持
### 1.4 权限问题
– 症状:执行函数或操作符时出现权限错误
– 原因:用户没有执行权限,函数访问了无权限的对象
– 解决方案:授予用户执行权限,确保函数使用的对象有适当的权限
### 1.5 兼容性问题
– 症状:函数或操作符在不同版本的PostgreSQL中表现不同
– 原因:PostgreSQL版本差异,依赖的功能在不同版本中变化
– 解决方案:测试不同版本的兼容性,使用兼容的实现方式
## 2. 排查方法
### 2.1 错误日志分析
– 查看PostgreSQL错误日志
– 分析函数执行错误信息
– 定位错误发生的位置
### 2.2 性能分析
– 使用EXPLAIN分析使用函数或操作符的查询
– 监控函数的执行时间和调用次数
– 分析操作符对查询计划的影响
### 2.3 代码审查
– 审查函数和操作符的实现代码
– 检查参数类型和返回类型
– 验证错误处理逻辑
### 2.4 测试
– 编写单元测试测试函数和操作符
– 测试边界情况和异常输入
– 测试不同参数类型和值
### 2.5 权限检查
– 检查用户的执行权限
– 验证函数访问的对象权限
– 确保函数的安全定义
## 3. 解决方案
### 3.1 函数执行错误
– 修复函数内部逻辑
– 确保参数类型匹配
– 授予用户执行权限
### 3.2 操作符冲突
– 选择不同的操作符符号
– 为不同的参数类型组合创建操作符
– 检查现有的操作符定义
### 3.3 性能问题
– 优化函数逻辑
– 改进操作符实现
– 添加索引支持
– 使用更高效的语言编写函数
### 3.4 权限问题
– 授予用户执行权限
– 确保函数使用的对象有适当的权限
– 使用安全的函数定义
### 3.5 兼容性问题
– 测试不同版本的兼容性
– 使用兼容的实现方式
– 避免使用版本特定的功能
本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html
