如何在 pl/pgsql 中创建返回 refcursor 和 totalRow 的函数/过程?

Posted

技术标签:

【中文标题】如何在 pl/pgsql 中创建返回 refcursor 和 totalRow 的函数/过程?【英文标题】:How to create function/procedure in pl/pgsql returning refcursor and totalRow? 【发布时间】:2018-07-24 23:14:25 【问题描述】:

pl/pgsql,我想创建函数/过程来返回表和totalRecord的结果列表(返回的记录),所以我创建了这个函数:

CREATE OR REPLACE FUNCTION GET_LIST_NOTIFY(    
        -- Cursor param
             out PO_Cursor refcursor, -- return Resutlset
             out PO_ErrorCode   VARCHAR,
             out PO_ErrorDesc    VARCHAR,
             OUT PO_TotalRow        VARCHAR
     )
      as
      $$
    declare
      -- Variable Declare
       vSqlSel               VARCHAR(20000); -- Sql select
      -- END Variable Declare
    begin       
      PO_ErrorCode := 'CODE';
      PO_ErrorDesc := 'MSG_DESC';

       vSqlSel := 'SELECT ID, TITLE ' ||
                  ' FROM USER_NOTIFICATION ';

       raise info 'sql select : %', vSqlSel;

        -- open cursor
       OPEN PO_Cursor for execute vSqlSel;
        EXECUTE 'SELECT count(*) FROM USER_NOTIFICATION' INTO PO_TotalRow;
       EXCEPTION
        WHEN OTHERS THEN             
        PO_ErrorCode := 'COMMONERROR_CODE';
        PO_ErrorDesc := substr(SQLERRM,1,200);

          RAISE;

    END;
$$  LANGUAGE plpgsql;
     -- END GET_LIST_NOTIFY

但是当我调用这个函数时,返回的结果是:

- refcursor is "<unnamed portal 1>"
- PO_ErrorCode  is "CODE"
- PO_ErrorDesc  is "MSG_DESC"
- PO_TotalRow   is "10"

所以,我不能使用 refcursor 来检索和显示java 中的记录数据。我该如何解决这个问题?

在 oracle pl/sql 中我可以这样写:

PROCEDURE GET_LIST_NOTIFY(
        -- Fields param
             PI_USERNAME    IN   VARCHAR2,
        -- END Fields param

        -- Cursor param
             PO_Cursor        OUT  REF CURSOR,
              PO_TotalRow     OUT   VARCHAR2,
             PO_ErrorCode   OUT VARCHAR2,
             PO_ErrorDesc   OUT VARCHAR2
     )
      AS
          -- Variable Declare
       vSqlSel               VARCHAR2(20000); -- Sql select
          -- END Variable Declare

    BEGIN

      PO_ErrorCode := 'SUCCESS_CODE';
      PO_ErrorDesc := 'SUCCESS_MSG';

       vSqlSel := 'SELECT ID, TITLE ' ||
                  ' FROM USER_NOTIFICATION ';

       dbms_output.put_line('sql select :' || vSqlSel);
       EXECUTE IMMEDIATE 'SELECT count(*) FROM USER_NOTIFICATION' INTO PO_TotalRow;
        -- open cursor
       OPEN PO_Cursor FOR
            vSqlSel;


       EXCEPTION
        WHEN OTHERS THEN
          IF PO_Cursor%ISOPEN THEN
            CLOSE PO_Cursor;
          END IF;

        PO_ErrorCode := 'COMMONERROR_CODE';
        PO_ErrorDesc := substr(DBMS_UTILITY.format_error_backtrace || ' ' ||
                        SQLERRM,1,200);

          RAISE;

    END;
     -- END GET_LIST_NOTIFY

在我的 java 代码中,我这样称呼这个“get_list_notify”函数:

            RowMapper rowMapper = new RowMapper() 
                @Override
                public Object mapRow(ResultSet rs, int rownum) throws SQLException 
                    // TODO Auto-generated method stub
                    OptionDTO dto = new OptionDTO();
                    try 

                        dto.setValue(Utils.validatehtmlParam(rs.getString(1).trim(), false));
                        dto.setText(Utils.validateHTMLParam(rs.getString(2).trim(), false));

                     catch (Exception e) 
                        ErrorHelper.PrintStackTrace(this.getClass().getName(), e, "DataProcessing.getDrowdownData2.OptionMapper.mapRow error : ");
                    

                    return dto;
                
            ;

           SimpleJdbcCall simpleJdbcCall = new SimpleJdbcCall(DBConnector.getConnection())
                        .withFunctionName(procedureName).withoutProcedureColumnMetaDataAccess();
                Map<String, Object> params = new LinkedHashMap();
                for (String key : parameters.keySet()) 
                    simpleJdbcCall.addDeclaredParameter(new SqlParameter(key, parameters.get(key).getType()));
                    if(Utils.isNummericOracleType(parameters.get(key).getType()) && parameters.get(key).getData() != null)
                        params.put(key, new BigDecimal(parameters.get(key).getData().toString()));
                    else
                        params.put(key, parameters.get(key).getData());
                

                simpleJdbcCall.addDeclaredParameter(new SqlOutParameter("PO_Cursor", Oid.REF_CURSOR, rowMapper));
                simpleJdbcCall.addDeclaredParameter(new SqlOutParameter("PO_ErrorCode", Oid.VARCHAR));
                simpleJdbcCall.addDeclaredParameter(new SqlOutParameter("PO_ErrorDesc", Oid.VARCHAR));

                Map<String, Object> map = simpleJdbcCall.execute(params);

但是在执行时,simpleJdbcCall.execute(params) 抛出异常如下:

[err] -----org.springframework.jdbc.UncategorizedSQLException: CallableStatementCallback; uncategorized SQLException for SQL [? = call get_list_notify(?, ?, ?)]; SQL state [34000]; error code [0]; ERROR: cursor "<unnamed portal 1>" does not exist; nested exception is org.postgresql.util.PSQLException: ERROR: cursor "<unnamed portal 1>" does not exist
[err] --------------- At classes : ---------------
[err] ----- at com.bidv.bidvwas.common.DataProccessing.getDrowdownData(DataProccessing.java:399)

如何准确转换 pl/pgsql 并解析 java 中的调用函数。我使用 springjdbc-4.2.5 和 postgres 10.4

【问题讨论】:

【参考方案1】:

你的代码有点奇怪(太奇怪了——我知道几乎所有的反模式:)——在一个例子中)。你试图加入应该分开的东西,你正在尝试糟糕地使用存储过程。通常程序不应该提供视图(不是在所有没有 MSSQL 的数据库中)。使用过程,您的代码应该更具可读性,而不是更少。

你错过了命令FETCH。您应该将其用于&lt;unnamed portal&gt;

CREATE OR REPLACE FUNCTION public.fx(par text, INOUT r refcursor, OUT result integer)
 RETURNS record
 LANGUAGE plpgsql
AS $function$
BEGIN
  result := 10;
  OPEN r FOR SELECT * FROM pg_class WHERE relname LIKE par;
END;
$function$
postgres=# SELECT fx('pg_c%', 'my_name_for_cursor'); ┌──────────────────────────┐ │ 效果 │ ╞═════════════════════════╡ │ (my_name_for_cursor,10) │ └──────────────────────────┘ (1 行) postgres=# FETCH 10 FROM my_name_for_cursor; ┌────────────────────────────────────┬───────────── ─┬─────────┬────────────┬──────────┬────────┬─────── ──────┬────────────── │ relname │ relnamespace │ reltype │ reloftype │ relowner │ relam │ relfilenode │ reltablespac ╞═══════════════════════════════════╪═════════════ ═╪═════════╪═══════════╪══════════╪═══════╪═══════ ══════╪═════════════ │ pg_cast_oid_index │ 11 │ 0 │ 0 │ 10 │ 403 │ 2660 │ │ pg_cast_source_target_index │ 11 │ 0 │ 0 │ 10 │ 403 │ 2661 │ │ pg_class_oid_index │ 11 │ 0 │ 0 │ 10 │ 403 │ 0 │ │ pg_class_relname_nsp_index │ 11 │ 0 │ 0 │ 10 │ 403 │ 0 │ │ pg_class_tblspc_relfilenode_index │ 11 │ 0 │ 0 │ 10 │ 403 │ 0 │ │ pg_collat​​ion_name_enc_nsp_index │ 11 │ 0 │ 0 │ 10 │ 403 │ 3164 │ │ pg_collat​​ion_oid_index │ 11 │ 0 │ 0 │ 10 │ 403 │ 3085 │ │ pg_constraint_conname_nsp_index │ 11 │ 0 │ 0 │ 10 │ 403 │ 2664 │ │ pg_constraint_conrelid_index │ 11 │ 0 │ 0 │ 10 │ 403 │ 2665 │ │ pg_constraint_contypid_index │ 11 │ 0 │ 0 │ 10 │ 403 │ 2666 │ └────────────────────────────────────┴───────────── ─┴─────────┴────────────┴──────────┴───────┴─────── ──────┴────────────── (10 行) postgres=#提交;

更多 - 您过多地使用动态 SQL(命令 EXECUTE)。不需要时使用较新的动态 SQL。在您的示例中,永远不应使用动态 SQL。

捕获所有错误通常也是个坏主意。这是客户端的工作,而不是存储过程中的代码。仅处理您可以正确解决的错误。所有有关异常的信息也可以在客户端获取,您无需编写这些晦涩的包装器。

【讨论】:

亲爱的@pavel stehule,感谢您的回复。实际上,我将代码从 oracle pl/sql 转换为 postgres pl/pgsql。但似乎 pl/pgsql 不同时支持“输出 refcursor 参数”和“输出 varchar 参数”。我在帖子中更新了 oracle pl/sql,如何转换为 pl/pgsql。非常感谢! @vuanhkieu 你可以看到,Postgres 允许 - 服务器端没有任何问题。当我将结果更改为 varchar 时,我的代码正在运行。您移植的函数的结果是正确的。顺便说一句 - 你原来的 PL/SQL 是错误的(部分)。当你重新引发异常时,设置参数是没有用的。 亲爱的@PavelStehule:我从java应用程序调用这个pl/pgsql函数,并引发异常[err] -----org.springframework.jdbc.UncategorizedSQLException: CallableStatementCallback; SQL [? = 调用 get_list_notify(?, ?, ?)]; SQL 状态 [34000];错误代码[0];错误:光标“”不存在;嵌套异常是 org.postgresql.util.PSQLException:错误:游标“”不存在。请帮我解决这个问题。我在我的帖子中更新了代码 java。非常感谢你 @vuanhkieu - 看起来像是一些 JDBC 相关问题,对我来说太暗了。也许问题出在自动提交中 - 游标寿命仅限于当前事务。如果用于 CALL 的事务关闭,则游标也关闭。 @PavelStehule:无论如何,非常感谢您的支持。我关注了***.com/questions/25718791/…的帖子,似乎解决了我的问题

以上是关于如何在 pl/pgsql 中创建返回 refcursor 和 totalRow 的函数/过程?的主要内容,如果未能解决你的问题,请参考以下文章

PL/pgSQL 函数在 pgAdmin 之外无法正确运行

执行 SELECT 语句并丢弃 PL/pgSQL 中的结果

如何在 PL/pgSQL 中按行类型返回表

使用 PL/pgSQL 在 PostgreSQL 中将多个字段作为记录返回

PL/pgSQL - 从 FUNCTION 返回单个记录

PL/pgSQL:触发函数返回不正确的数据