【问题标题】:Consuming Oracle Table Function from Stored Procedure从存储过程中使用 Oracle 表函数
【发布时间】:2019-09-28 08:08:38
【问题描述】:

我正在使用一个 ERP 数据库,该数据库将大量数据存储在多年表中。 (每个新年都会创建一个新表来保存该年的数据)我们需要能够查询和报告其中一些表。我目前正在使用视图,但视图只是年复一年地变得越来越大,而且速度越来越慢。我创建了一个流水线表函数,它执行一些动态 sql 并根据作为参数传入的 from 和 to date 查询适当的表。我可以从普通 SQL 调用流水线函数,它工作正常。但是,目标是能够在许多不同的存储过程中重用表函数并与其他数据连接。我们使用的报告系统要求我们使用返回引用游标的存储过程。

我创建了一个测试函数和测试存储过程(为简洁起见的简化版本)以尝试将表函数作为游标返回,但在执行过程时出现错误(PLS-00382:表达式类型错误)。我什至不确定过程是否可以访问管道函数,但我在 SQL Server 中做过类似的事情,所以必须有某种方法。我已经搜索过 hi 和 low 但真的找不到任何有完全相同情况的人。请看我的代码。

以下是我在架构中创建的用户定义类型:

CREATE OR REPLACE TYPE PUCCONNECT.wo_trans_type AS OBJECT
       (GL_YEAR             INT,
        SUBSYSTEM           VARCHAR2(2)
        );

CREATE OR REPLACE TYPE PUCCONNECT.wo_trans_table_test AS TABLE OF PUCCONNECT.wo_trans_type;

以下是函数和过程声明。我在函数 GL101Txx 中选择的表有很多列,所以我只选择前 2 个以保持简单。前 2 列与我的用户定义对象“wo_trans_type”中定义的列具有相同的定义

CREATE OR REPLACE FUNCTION PUCCONNECT.WO_MULTIYEAR_TEST(fromdate date, todate date) 
         RETURN WO_TRANS_TABLE_TEST PIPELINED IS
TYPE            ref0 IS REF CURSOR;
cur0            ref0;
v_year_start    int;
v_year_end      int;
out_rec         wo_trans_type
            := wo_trans_type(NULL,NULL);

BEGIN

    v_year_start := EXTRACT(year FROM fromdate);
    v_year_end := EXTRACT(year FROM todate);

    FOR yearNumber in v_year_start..v_year_end LOOP

        OPEN cur0 FOR

             'SELECT ' || yearNumber || ' "gl_year", GL.SUBSYSTEM

                 FROM fmsdata.GL101T' || SUBSTR(to_char(yearNumber), 3,2) || ' GL

                WHERE (GL.transaction_date BETWEEN ''' || fromdate || ''' AND ''' || todate || ''')';                          

                LOOP
                    FETCH cur0 INTO out_rec.gl_year, out_rec.subsystem;

                    EXIT WHEN cur0 %NOTFOUND;
                    PIPE ROW(out_rec);
                END LOOP;
                CLOSE cur0;

    END LOOP;

    RETURN;
END WO_MULTIYEAR_TEST;

这是我尝试使用该功能的过程:

CREATE OR REPLACE PROCEDURE PUCCONNECT."SP_WO_TRANS_PA" (
    --table_out out wo_trans_table,
    wo_trans_cursor out sys_refcursor
 )
 AS 

BEGIN
           OPEN wo_trans_cursor FOR

           SELECT   gl_year, subsystem
           FROM     TABLE( PUCCONNECT.WO_MULTIYEAR_TEST('01-jan-2019', '05-may-2019'));

END;

有谁知道这是否可以通过存储过程实现?是否可以使用非流水线表函数?任何有关实现此目标和保持性能的最佳方法的建议或意见都值得赞赏。

这是我尝试执行该过程时 TOAD 返回的完整错误:

[Error] ORA-06550: line 12, column 12:
PLS-00382: expression is of wrong type
ORA-06550: line 12, column 6:
PL/SQL: Statement ignored
 (1: 0): >> DECLARE
    -- Declarations
    l_WO_TRANS_CURSOR   SYS_REFCURSOR;
BEGIN
    -- Call
    PUCCONNECT.SP_WO_TRANS_PA (WO_TRANS_CURSOR => l_WO_TRANS_CURSOR);

    -- Transaction Control
    COMMIT;

    -- Output values, do not modify
     :1 := l_WO_TRANS_CURSOR;
END;
Error at line 1
ORA-06550: line 12, column 12:
PLS-00382: expression is of wrong type
ORA-06550: line 12, column 6:
PL/SQL: Statement ignored

这是我在 TOAD 中的称呼:

DECLARE
    -- Declarations
    l_WO_TRANS_CURSOR   SYS_REFCURSOR;
BEGIN
    -- Call
    PUCCONNECT.SP_WO_TRANS_PA (WO_TRANS_CURSOR => l_WO_TRANS_CURSOR);

    -- Transaction Control
    COMMIT;

    -- Output values, do not modify
     :1 := l_WO_TRANS_CURSOR;
END;

【问题讨论】:

  • 您能否编辑您的问题并添加所涉及表的表定义(如果可能,请添加 ddl)以及该表中的一些示例行?我们更容易以这种方式模拟您的问题,而不是猜测。
  • 另外,添加WO_TRANS_TABLE_TEST类型的定义
  • 你正在做的事情是允许的,works with guessed object definitions。所以要么你的对象没有像我猜的那样定义(这很可能),要么通过简化你隐藏了实际问题的代码。除了所有 DDL,请包括您获得的完整错误堆栈,并说明您是在编译过程还是在调用过程时获得的(如果是后者,如何调用它)。
  • 好的,我已经添加了用户定义类型的定义。 Alex Poole - 我将补充一点,当我从 TOAD 中的 SQL 编辑器窗口调用它时,该函数可以完美运行,如下所示:SELECT gl_year, subsystem FROM TABLE( PUCCONNECT.WO_MULTIYEAR_TEST('01-jan-2019', '05-may-2019')); 存储过程编译成功。从 TOAD 执行过程时出现错误。
  • 再次展示你是如何执行它的,以及完整的错误堆栈。如果问题来自通话,那么我们需要看到这一点。 (它也适用于with your type definitions;所以我猜你的称呼不同?)

标签: oracle function stored-procedures table-functions


【解决方案1】:

您可以在一个过程中重写整个内容,例如:

CREATE OR REPLACE PROCEDURE PUCCONNECT."SP_WO_TRANS_PA"(fromdate        IN DATE,
                                                        todate          IN DATE,
                                                        wo_trans_cursor OUT SYS_REFCURSOR) AS
  v_year_start INT;
  v_year_end   INT;

  v_sql CLOB;
  c_union_all CONSTANT VARCHAR2(9) := 'union all';
  v_table_append VARCHAR2(38);
  v_column_append VARCHAR2(10);
BEGIN

  v_year_start := extract(YEAR FROM fromdate);
  v_year_end   := extract(YEAR FROM todate);

  FOR yearnumber IN v_year_start .. v_year_end
  LOOP
    IF yearnumber != v_year_start
    THEN
      v_sql := v_sql || chr(10) || c_union_all || chr(10) ||;
    END IF;

    -- to avoid any sql injection due to the to_char (just in case)
    v_table_append := dbms_assert.qualified_sql_name('fmsdata.GL101T' || substr(to_char(yearnumber), 3, 2));
    v_column_append := dbms_assert.enquote_literal(to_char(yearnumber));

    v_sql := v_sql || 'select to_number(' || v_column_append || ') gl_year, gl.subsystem ' || chr(10)
                   || 'from   v_table_append' || chr(10)
                   || 'where  gl.transaction_date between :fromdate and :todate';

  END LOOP;

  v_sql := v_sql || chr(10) || 'order by 1, 2';

  OPEN wo_trans_cursor FOR v_sql
    USING fromdate, todate;

END sp_wo_trans_pa;
/

这会遍历这些年并生成要在游标中运行的 sql,然后使用相关的绑定变量打开游标。

我在可能的情况下使用了绑定变量,在不可能的情况下,我使用了 dbms_assert 来清理连接的值以避免任何 SQL 注入漏洞。

【讨论】:

  • 我非常喜欢这种方法。我没想过这样做 - 只需在所有相关年份使用联合构建您的 sql 查询并作为游标打开。我会试试这个,看看性能提升是多少。我喜欢表函数的唯一想法是它们可以在许多过程中重用并连接到其他数据。
  • 您可能能够使用 union all 和 table pruning 创建一个视图,当您查询视图时可能会发生
  • 或者你可以在过程传递周围抛出一个流水线函数。
  • 我一直在使用 union all 的视图,并且视图越来越大,需要每年更新。如果我以某种方式查询视图,性能并不可怕,但也不是很好。我不想那样做。
猜你喜欢
  • 2013-06-06
  • 1970-01-01
  • 2011-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-05
相关资源
最近更新 更多