在 Oracle 数据库开发中,PROCEDURE + OUT 参数 是非常常见的开发模式。尤其是在GIS、电力、erp系统中,最近在我们的客户从oracle迁移到postgresq系(highgoDB)数据库中存在一些这样的场景,下面简单记录一下在迁移到postgresql实现。
Oracle中的格式
PROCEDURE xxx( p_id IN NUMBER, p_result OUT SYS_REFCURSOR )
-- or --
create type xx as object(xx,xx);
create type yy as table of xx;
PROCEDURE xxx( p_id IN NUMBER, p_result OUT xx );
但在 PostgreSQL 中:
- 没有 Oracle 风格
OBJECT - 没有
TABLE OF - Procedure 返回结果集方式不同
- 更推荐
FUNCTION RETURNS TABLE
在Oracle中的out 自定义type
select * from t_user
ID USER_NAME AGE
---------- ------------------------------ ----------
1 Tom 18
2 Jerry 20
3 Lucy 22
-- 创建 out 自定义type
CREATE OR REPLACE PROCEDURE proc_user_list( p_age IN NUMBER, p_users OUT user_table )
AS
BEGIN
SELECT user_obj( id, user_name, age )
BULK COLLECT INTO p_users
FROM t_user
WHERE age >= p_age;
END;
/
DECLARE
v_users user_table;
BEGIN
proc_user_list(18, v_users);
FOR i IN 1 .. v_users.COUNT LOOP
DBMS_OUTPUT.PUT_LINE( v_users(i).user_name );
END LOOP;
END;
/
-- 调用
CREATE OR REPLACE PROCEDURE proc_user_cursor( p_age IN NUMBER, p_cursor OUT SYS_REFCURSOR )
AS
BEGIN
OPEN p_cursor FOR
SELECT id, user_name, age
FROM t_user
WHERE age >= p_age;
END;
/
在Oracle中的out SYS_REFCURSOR
CREATE OR REPLACE PROCEDURE proc_user_cursor( p_age IN NUMBER, p_cursor OUT SYS_REFCURSOR )
AS
BEGIN
OPEN p_cursor FOR
SELECT id, user_name, age
FROM t_user
WHERE age >= p_age;
END;
/
-- 调用
DECLARE
v_cursor SYS_REFCURSOR;
v_id NUMBER;
v_name VARCHAR2(100);
v_age NUMBER;
BEGIN
proc_user_cursor( 18, v_cursor );
LOOP
FETCH v_cursor
INTO v_id,
v_name,
v_age;
EXIT WHEN v_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_name);
END LOOP;
CLOSE v_cursor;
END;
/
Oracle vs PostgreSQL 对应关系
| Oracle | PostgreSQL |
|---|---|
| SYS_REFCURSOR | refcursor |
| AS OBJECT | composite type |
| TABLE OF | array / RETURNS TABLE |
| BULK COLLECT | array_agg |
| PROCEDURE OUT | procedure refcursor |
| WHILE LOOP | RECURSIVE CTE |
| procedure返回结果 | Function |
| select * from table() | fetch all from |
PostgreSQL:
| 类型 | 能返回记录集 | 调用方式 |
|---|---|---|
| FUNCTION | ✅ 推荐 | SELECT |
| PROCEDURE | ❌ 较弱 | CALL |
| 需求 | 推荐方式 |
|---|---|
| 返回单行对象 | RETURNS TABLE |
| 返回复杂对象 | RETURNS TYPE |
| 返回多行 | RETURNS TABLE + RETURN QUERY |
| Oracle 风格 OUT 参数 | FUNCTION OUT |
| 真 PROCEDURE | 不推荐做查询返回 |
像上面ORACLE的使用out传出参数的话,在Postgresql中是建议使用function 代替 procedure, 如果返回一行记录,也可以使用function(xx, out xx int)这种方式, 当然也可以使用 return table的方式
CREATE OR REPLACE FUNCTION get_user_info(
p_user_id bigint
)
RETURNS TABLE (
user_id bigint,
user_name text,
age int
)
AS
$$
BEGIN
RETURN QUERY
SELECT id, name, user_age
FROM users
WHERE id = p_user_id;
END;
$$
LANGUAGE plpgsql;
但是调用的话,需要有原来call procedure改为select * from function.
当然如果一定要procedure + out参数的话,从pg 11以后也是支持的。但一般不推荐。
CREATE OR REPLACE PROCEDURE get_user_info(
IN p_user_id bigint,
OUT user_id bigint,
OUT user_name text
)
LANGUAGE plpgsql
AS
$$
BEGIN
SELECT id, name
INTO user_id, user_name
FROM users
WHERE id = p_user_id;
END;
$$;
-- call
CALL get_user_info(1, NULL, NULL);
返回单行使用returns table, 而返回多行使用returns table return query ,如下
CREATE OR REPLACE FUNCTION find_next_point(
p_point integer,
p_line integer
)
RETURNS TABLE
(
new_point integer,
new_line integer,
status integer
)
AS
$$
BEGIN
RETURN QUERY
SELECT
1001,
2001,
0;
END;
$$
LANGUAGE plpgsql;
但是PostgreSQL中不支持像oracle一样procedure out table,那就要使用数组 out, 或使用refcursor. 数组out我们不建议,下面演示postgresql中refcursor的用法
CREATE TABLE t_user
(
id integer,
user_name varchar(100),
age integer
);
INSERT INTO t_user VALUES (1, 'Tom', 18);
INSERT INTO t_user VALUES (2, 'Jerry', 20);
INSERT INTO t_user VALUES (3, 'Lucy', 22);
CREATE OR REPLACE PROCEDURE proc_get_user(
IN p_min_age integer,
INOUT p_cursor refcursor DEFAULT 'user_cursor'
)
LANGUAGE plpgsql
AS
$$
BEGIN
OPEN p_cursor FOR
SELECT
id,
user_name,
age
FROM t_user
WHERE age >= p_min_age
ORDER BY id;
END;
$$;
-- 调用
highgo=# begin;
BEGIN
highgo=*# CALL proc_get_user( 20, 'user_cursor');
p_cursor
-------------
user_cursor
(1 row)
highgo=*# FETCH ALL FROM user_cursor;
id | user_name | age
----+-----------+-----
2 | Jerry | 20
3 | Lucy | 22
(2 rows)
highgo=*# COMMIT;
COMMIT
在应用程序中的调用
-- java
CallableStatement cs =
conn.prepareCall(
"{ call proc_get_user(?, ?) }");
cs.setInt(1, 20);
cs.registerOutParameter(
2,
Types.REF_CURSOR
);
cs.execute();
ResultSet rs =
(ResultSet) cs.getObject(2);
while(rs.next()) {
System.out.println(
rs.getInt("id")
);
}
--myBatis
<select id="callProc"
statementType="CALLABLE">
{call proc_get_user(
#{minAge, mode=IN, jdbcType=INTEGER},
#{cursor, mode=OUT, jdbcType=CURSOR,
resultMap=userMap}
)}
</select>
总结
Oracle 与 PostgreSQL 在存储过程体系上存在明显差异。在oracle中如果使用procedure+out参数,在迁移到PostgreSQL后建议优先使用function + out参数,而procedure要放在事务中调用,而返回结果集时也没有对应的as table of. 推荐使用function + return table的方式。