0
点赞
收藏
分享

微信扫一扫

关于postgresql数据库调用存储过程/函数返回数据集等问题

杨沐涵 2022-11-11 阅读 44



笔者之前对postgresql有一些泛泛的了解。前几天从某篇文章看到一则信息说,mysql的使用率接近停滞,而postgresql 使用如日中天,笔者对此有些好奇,这次稍微实际深入了解了一些内容,

总的来看,postgresql的文档比mysql规整完善,更学术化,有一些自己的特色,比如内置支持空间信息管理等,有官方正规的管理工具。但在使用的过程中碰到的一些问题不吐不快,网上很难查到有用的资料,初次接触的开发人员很容易踩坑。


关于脚本语言

postgresql官方的文档里说,编写脚本语言可以用 C, sql, plpgsql, python等, 当选择sql时, 如果你使用了if控制语句,编译存储过程会报错:

SQL Error [42601]: 错误: 语法错误 在 "if" 或附近的

实际上这时你必须选择plpgsql,语句体加上 begin end

查资料发现,如果存在控制语句 如 if then 等,必须选择 language plpgsql,并且函数体头尾要加上

begin  end

对于sql语言在存储过程里加入控制语句很常见,而postgresql自带的管理客户端和dbeaver等工具都是默认语言为sql而不是plpgsql,居然不支持if else

这让初次接触的开发人员会很懵圈。


存储过程不支持返回数据集

早先 postgresql 只有函数(Function),并不支持存储过程(Procedure),可能是设计者认为函数足够用了吧,后来在版本11加入了存储过程, 说是这样可以应付事务,但是并不支持存储过程返回数据集!

如果你在存储过程里select 了数据,然后直接在客户端SQL工具窗口里调用 call ,它会报一个错:

call proc1(1);

SQL Error [42601]: 错误: 对于结果数据,查询没有目标

 Hint: 如果您想要放弃SELECT语句的结果,请使用PERFORM.

意思是你没在脚本中声明数据集的承接对象。而实际上postgresql的存储过程既不能声明返回值,也无法接住内置的SELECT脚本输出的数据。

只有用函数(Function)才能返回数据集,并且数据集的使用非常繁琐,必须要声明类型。 是不是太刻板了?

比如:

CREATE OR REPLACE FUNCTION get_customer() RETURNS SETOF customer AS  

$body$  

SELECT * from customer;  

$body$  

调用:select * from get_customer()  

这里刚好是输出某个表结构一样的情况,customer就是表名,但很多时候,都是多表联合查询,这时就比较麻烦了,有两种方式,

一种做法是事先定义type, 例如

create type my_customer(customerid int,firstname varchar,... supportrepname varchar)

然后函数返回此type:上面的 returns ...改为 returns setof my_customer


函数体内容:

declare rec my_customer%rowtype

begin

for rec in select a.customerid,a.firstname,....b.lastname from customer a left join employee b on a.supportrepid=b.employeeid

loop

return next rec;

end loop;

return;

end;


第二种做法是不事先声明类型type, 返回record:

上面的returns...改为 returns setof record

函数体内容:

declare v_rec record;

begin

for v_rec in select ...

loop  

return  next v_rec;

end loop;

这时你在调用时需要定义一个type结构接住数据.

select * from get_customer(...) as my_customer(customerid int....)

实际上还是换汤不换药,这给开发人员调试脚本增加了很多额外的工作量。

而多表查询的情况是司空见惯的,上面的用法实在让人烦心,也有另外一种方法,返回一个游标 refcursor

CREATE OR REPLACE FUNCTION function1 () RETURNS refcursor AS  

$body$  

DECLARE  result refcursor;  

BEGIN  

open result for select * from table1,table2; --你可以任意选择你想要返回的表和字段  

return result;  

END;  

$body$  

这样稍微好一些,但还有其它问题呢,我们知道,对于其它数据库,JDBC在使用存储过程时都可以统一用  

CallableStatement stmt = conn.prepareCall("{call proc1(?,?) }");  

而postgresql只能用函数返回数据集,调用方式只能像这样了:

CallableStatement proc = conn.prepareCall("{ ? = call function1() }");  

这与其它的数据库有着显著区别,其它的数据库,如Oracle,Mysql, MSSQL,SAP HAHA,DB2等等都支持过程返回数据,且在客户端工具的SQL工具窗口中就能直接看到结果,多个select语句就会在客户端的多个输出窗口tab页展示,

用JDBC调用可以直接按照顺序引用SP的数据集。 而到了postgresql这里,困难重重非常繁琐。

顺便说点题外话:从前在Hibernate流行时,很多人强调Hibernate的一个优点就是,使用它可以屏蔽数据库的差异,可以无缝切换数据库……

而实际上,规模稍微大一点的公司和项目来说,切换数据库都是一件很严肃、超级大的动作,必须分配专门的资源集中处理,远远不是靠某一个ORM产品就能轻松实现数据库切换的。

ORM产品除了能对付基本的CRUD之外,其它复杂的操作都必须借助数据库本身的功能。 因此数据库的选型非常重要。


过程/函数支持重载


在使用的过程中,postgresql与其它数据库重大区别之一是 postgresql过程/函数居然支持重载,也就是说,同一个函数名,参数不同,就成为了不同的函数!

postgresql支持函数重载,听起来高大上,然而笔者个人认为这样设计实际上弊大于利,

毕竟、不像JAVA/C++等本身就是面向对象的语言,SQL是结构化查询语言,提供重载功能,这既增加了数据库设计的复杂性,也让开发人员使用时更加繁琐,

一不留神使用时就会冒出各种各样的错误。

下面是我在通用客户端DB工具dbeaver上,展示的实现同一个功能的一些函数过程截图:

关于postgresql数据库调用存储过程/函数返回数据集等问题_postgresql

看到了没,由于支持重载postgresql 的函数/过程必须列出参数,如果万一有个同名函数,参数有很多个,一眼望去你根本不知道谁是谁了,

也许有人说,设计有这个功能,你不用不就行了吗?

理论上是这样,但是实际上你不得不被动的用。

如果你在调用某个过程函数,仅仅是参数有些不对,它会告诉你

比如你用脚本删除一个函数/过程,删除的时候必须列明它的所有参数类型,是不是想想都头大?

如果你在修改一个函数,改动返回值类型,再保存,它会给你报一个错!说是不能修改函数的返回值!

那啥, OO程序语言比如Java/C++重载确实是不允许两个重载方法返回不同值,但是你这是结构化的SQL脚本啊,这都是要支持重载给自己挖的坑啊,是不是很无语了。。。


总结

虽然postgresql理论上做到了逻辑自洽,相比MySQL有一些自己的功能特色,但由于设计时考虑欠周,过分追求学院派风格,一些常用的功能用起来不是很方便,比较繁琐。

这些缺陷由于是开始时的设计问题,由于要考虑兼容性,postgresql后面的版本大概率也不会对这些再做处理了。

因此大的管理系统并不太适合选择postgresql用于生产环境。 否则后续开发维护扩展都会比较麻烦。

以上只是本人的一些浅见,如果有不同见解,欢迎不吝指正。

举报

相关推荐

0 条评论