如何在 PL/Python 函数内使用 pandas 处理数据?

来源:网络推广作者:孙志远头衔:网络博主
导读:本期聚焦于孙志远创作的《如何在 PL/Python 函数内使用 pandas 处理数据?》,敬请观看详情。PL/Python 是 PostgreSQL 提供的不可信过程语言,它把 CPython 解释器嵌入数据库后端进程,让存储函数可以 import pandas、numpy 等 Python 科学计算库。pandas 的 DataFrame 与数据库结果集在概念上都表示二维表格,但其布尔筛选、分组聚合、缺失值填充和时间序列处理能力更灵活。当业务规则复杂到单条 SQL 难以维护时,在数据库内部使用 PL/Python 配合 pandas 可以减少应用层与数据库之间的数据搬运。本文从环境准备、函数基本结构、查询结果转 DataFrame、返回 TABLE 或 jsonb 等角度进行说明,同时分析 plpy 与 pandas 类型映射的常见坑,以及在大规模数据下应优先下推 SQL、控制 Python 端内存的实践原则。

PL/Python 是 PostgreSQL 提供的一种不可信过程语言,它将 CPython 解释器直接嵌入数据库后端进程。这种设计让存储函数可以 import pandas、numpy、scipy 等 Python 科学计算库,而不必把数据发送到外部脚本。pandas 的 DataFrame 与数据库结果集在概念上很接近,都表示二维表格,但 pandas 提供了更灵活的布尔筛选、分组聚合、缺失值填充和时间序列处理能力。当业务规则复杂到 SQL 难以维护时,使用 PL/Python 配合 pandas 可以在数据库内部完成数据处理,减少网络传输。

如何在 PL/Python 函数内使用 pandas 处理数据?

环境准备与函数基本结构

要使用 PL/Python,首先需要在 PostgreSQL 中安装扩展。通常需要超级用户执行 CREATE EXTENSION plpython3u;。不同操作系统对应的 PostgreSQL 软件包名称略有差异,有些发行版需要单独安装 postgresql-plpython3 或 postgresql-contrib 包。扩展创建完成后,还要确认数据库所在主机的 Python 环境中已经安装 pandas,因为函数运行在数据库服务进程内,而不是客户端环境中。

下面是一个最简单的函数示例,用来验证 pandas 是否可以在 PL/Python 中正常加载:

CREATE EXTENSION IF NOT EXISTS plpython3u;

CREATE OR REPLACE FUNCTION py_pandas_version()
RETURNS text
LANGUAGE plpython3u
AS $$
import pandas as pd
return pd.__version__
$$;

调用 SELECT py_pandas_version(); 如果返回 pandas 的版本号,说明运行环境已经就绪。创建函数时 LANGUAGE plpython3u 表示使用 Python 3 解释器。函数体用 $$ 包围,内部可以像普通 Python 脚本一样导入模块、定义变量和处理逻辑。PL/Python 中还有 GDSD 两个全局字典,分别用于函数级缓存和静态数据缓存,合理使用可以避免重复加载开销较大的数据。

需要特别注意的是,PL/Python 属于不可信语言,函数可以访问文件系统和网络。因此数据库默认只允许超级用户或具备 USAGE 权限的角色创建这类函数。在多租户环境下,应该遵循最小权限原则,避免普通用户随意创建能够读取服务器文件的存储过程。

将查询结果转换为 DataFrame

PL/Python 通过 plpy.execute 执行 SQL 语句,并返回一个结果对象。该结果对象可以迭代为字典列表,每一行是一个字典,键是列名,值是对应的 Python 对象。这种结构与 pandas 的 DataFrame 构造方式很契合。通常情况下,可以先用 list() 将结果转换为字典列表,再交给 pd.DataFrame 创建二维表。

例如,要读取订单表中金额大于某个阈值的记录,可以这样写:

plan = plpy.prepare(
    "SELECT id, name, amount, created_at FROM orders WHERE amount > $1",
    ["numeric"]
)
rows = plpy.execute(plan, [100])
df = pd.DataFrame(list(rows))

转换完成后,就可以使用 pandas 提供的各种方法进行数据处理。例如按客户名称分组统计总金额和订单数量:

summary = df.groupby('name').agg(
    total_amount=('amount', 'sum'),
    order_count=('amount', 'count')
).reset_index()

类型映射是这一环节容易踩坑的地方。plpy.execute 返回的 numeric 字段通常被转换为 Python 的 Decimal 对象,pandas 可能将其保留为 object 类型,导致后续数值运算异常。遇到这种情况,可以使用 pd.to_numeric 进行显式转换。日期字段则可能以 datetime.datedatetime.datetime 对象返回,必要时通过 pd.to_datetime 统一格式。如果结果集很大,先将全部行放入 Python 列表再构造 DataFrame 会占用较多内存,建议在 SQL 层尽量过滤和聚合,只把必要的行拉到 Python 端。

在函数内处理并返回数据

pandas 处理完的数据通常需要返回给调用方。PL/Python 函数可以返回标量、复合类型记录或者 jsonb 等类型。如果希望以表格形式返回多行结果,可以在创建函数时使用 RETURNS TABLE 语法,并在函数体中返回字典列表。PL/Python 会自动将字典列表映射为对应的复合类型结果集。

下面的示例定义了一个函数,根据订单表中的客户名称分组汇总,并返回客户名、总金额和订单数量三列:

CREATE OR REPLACE FUNCTION order_summary()
RETURNS TABLE(customer_name text, total_amount numeric, order_count bigint)
LANGUAGE plpython3u
AS $$
import pandas as pd

rows = list(plpy.execute("SELECT name, amount FROM orders"))
df = pd.DataFrame(rows)
summary = df.groupby('name').agg(
    total_amount=('amount', 'sum'),
    order_count=('amount', 'count')
).reset_index()
return summary.to_dict('records')
$$;

如果调用方更习惯接收 JSON,可以直接返回 jsonb 类型。pandas 的 to_json 方法可以生成 JSON 字符串,但为了让 PostgreSQL 正确识别为 jsonb 对象,建议先用 json.loads 解析成 Python 的字典或列表再返回。这样 PL/Python 会将其转换为 jsonb,而不是简单的字符串。

import pandas as pd
import json

df = pd.DataFrame(list(plpy.execute("SELECT name, amount FROM orders")))
out = df.groupby('name').agg(total=('amount', 'sum')).reset_index()
return json.loads(out.to_json(orient='records'))

错误处理同样值得关注。PL/Python 中的异常会触发事务回滚,如果希望给调用方返回明确的错误信息,可以使用 plpy.error('错误描述')。对于 pandas 处理过程中可能出现的异常,应该用 try/except 捕获后再决定是抛出数据库错误还是忽略继续。不要直接让 pandas 的原始异常穿透到数据库层,这样不利于排查问题。

性能优化与使用边界

PL/Python 配合 pandas 适合处理中小规模数据,通常数万行以内比较合适。因为数据从数据库结果集到 Python 对象,再到 DataFrame,会经历多次复制和类型转换。对于大数据集,应当尽量在 SQL 中完成过滤、连接、聚合等重活,只把最终需要 Python 处理的少量行带入函数。如果确实需要处理数百万行,更推荐在数据库外部使用专门的 Python 脚本分批读取和计算,而不是把重负载放在数据库进程内。

如果函数需要频繁访问某张参考表,并且该表更新不频繁,可以使用 GD 全局字典进行缓存。例如:

if 'ref_df' not in GD:
    GD['ref_df'] = pd.DataFrame(list(plpy.execute("SELECT * FROM ref_table")))
ref_df = GD['ref_df']

这样可以避免每次调用函数都重新查询并构造 DataFrame。但 GD 仅作用于当前数据库会话,数据修改后不会自动失效。如果参考表发生了变化,需要有机制清理缓存,或者在缓存键中加入版本号来标识数据更新时间。

最后要认识到 PL/Python 的资源消耗。pandas 的计算可能占用较多 CPU 和内存,在高并发写入场景下可能拖慢整个数据库实例。最好为这类函数设置合适的超时时间,并结合连接池、资源监控等手段进行控制。总体来说,当转换逻辑足够复杂、数据量可控时,PL/Python 加 pandas 是一个高效的组合;当逻辑简单或数据量巨大时,单一 SQL 或外部处理框架仍然是更安全的选择。

PL/Pythonpandas数据处理修改时间:2026-08-20 15:46:01

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。