相关文章推荐
坚强的猴子  ·  PostgreSQL遍历Json_posgr ...·  1 月前    · 



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

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


关于脚本语言

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

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

左看右看,这个简单的脚本,就一个普通的IF语句,怎么会有语法错误呢?原来,原因就在这里,脚本里存在控制语句 如 if then 等时,必须选择脚本的 language 为plpgsql, 并且函数体头尾要加上

begin

end

存储过程里的脚本,加入控制语句很常见的事情,这样突然冒出语法错误,让人丈二和尚摸不着头脑,e而在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

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....)

还是要定义个type表示一条数据,实际上还是换汤不换药,这给开发人员调试脚本增加了很多额外的工作量。而多表查询的情况是司空见惯的,上面的用法实在让人烦心,也有另外一种方法,返回一个游标 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 stmt = conn.prepareCall( "{ ? = call function1(?,?) }" );

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

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

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

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

ORM产品除了能对付基本的CRUD之外,其它稍微复杂的操作都比较吃力,必须借助数据库本身的功能, 这就是为什么Hibernate逐渐式微,大家慢慢都用MyBatis的原因,因为MyBatis使用原生的数据库脚本更方便。实际上,由于关系数据库与OO设计语言的结构性矛盾,笔者认为Mybatis也算是一个鸡肋,不用任何ORM才是最好的方案。 这样看来,数据库的选型就非常重要了。


过程/函数支持重载


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

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

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

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

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

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

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

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

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

如果你在调用某个过程函数,仅仅是参数有些不对,它会告诉你,找不到这个函数,而不是参数不对,是不是有些误导?

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

在开发的过程中修改参数名称类型个数等是很常见的事情,但是在postgresql里如果你打开这个函数然后修改/调整某个过程或函数的名称类型等,然后保存,它会认为你是要 重载 ,直接给你存成了另一个函数了,原来的就放那不动了!是不是觉得很无厘头?

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

那啥, 对于OO程序语言比如Java/C++等来说,重载确实是不允许两个重载方法返回不同值,开发人员觉得很正常,但是你这是结构化的SQL脚本啊,不同的功能通过函数名区别就行了,犯不着在参数上区别。SQL脚本支持重载给除了给自己挖坑外毫无益处,开发人员为这一点会碰到各种各样的无厘头问题,相当的无语。。。

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

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

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

以上只是本人针对已了解到的信息发表的一些浅见,如果有不同见解,欢迎不吝指正。