如何防止Oracle针对函数结果的每个属性调用函数

Ger*_*rat 0 oracle plsql

我创建了一个包,其中包含一个返回对象的函数。
当通过 sql 检索对象详细信息时,该函数会被多次调用 - 每检索一个详细信息一次。
我相信应该可以只调用一次。

以下是演示该问题的示例:

CREATE OR REPLACE TYPE t_test AS OBJECT (
    v1              VARCHAR2(10),
    v2              VARCHAR2(10),
    v3              VARCHAR2(10),
    times_called    NUMBER
);
/

CREATE OR REPLACE PACKAGE test_pkg AS 
    times_called    NUMBER :=0;
    FUNCTION test(something IN VARCHAR2) RETURN t_test;
    PROCEDURE reset;

END test_pkg;
/

CREATE OR REPLACE PACKAGE BODY test_pkg IS
    PROCEDURE reset IS
    BEGIN
        times_called := 0;
    END;
    
    FUNCTION test(something IN VARCHAR2) RETURN t_test IS
        
    BEGIN
        times_called := times_called + 1;
        RETURN t_test('first', 'second', 'third', times_called);
    END;

END test_pkg;
/
Run Code Online (Sandbox Code Playgroud)

这里我们可以看到该函数被调用了四次:

SQL> SELECT t.r.v1, t.r.v2, t.r.v3, t.r.times_called FROM (
  2      SELECT test_pkg.test('x') r FROM DUAL
  3  ) t;

R.V1       R.V2       R.V3       R.TIMES_CALLED
---------- ---------- ---------- --------------
first      second     third                   4

SQL>
Run Code Online (Sandbox Code Playgroud)

如果我们重置计数器,并且只选择两个属性,我们可以看到它被调用了两次:

SQL> exec test_pkg.reset();

PL/SQL procedure successfully completed.

SQL> SELECT t.r.v1, t.r.times_called FROM (
  2      SELECT test_pkg.test('x') r FROM DUAL
  3  ) t;

R.V1       R.TIMES_CALLED
---------- --------------
first                   2

SQL>
Run Code Online (Sandbox Code Playgroud)

实际的存储过程更昂贵,因此我想避免为列出的每个属性重新调用它。

该解决方案必须适用于 Oracle 10gr2

MT0*_*MT0 5

Oracle 没有具体化子查询,而是将函数调用推送到外部查询。您需要通过以下方式强制 SQL 引擎具体化内部查询:

使用看似不必要的ROWNUM > 0过滤器:

SELECT t.r.v1, t.r.v2, t.r.v3, t.r.times_called
FROM (
  SELECT test_pkg.test('x') r
  FROM   DUAL
  WHERE  ROWNUM > 0
) t;
Run Code Online (Sandbox Code Playgroud)

或者,您应该能够使用(未记录的/*+ materialize */提示,但是由于未知的原因,它似乎不想实现这个特定的查询(尽管它确实适用于类似的问题)。

您还可以(正如William Robertson在评论中指出的那样)使用“导致 Oracle 不合并可合并视图”的/*+ NO_MERGE */提示:

SELECT t.r.v1, t.r.v2, t.r.v3, t.r.times_called
FROM   (
  SELECT /*+ no_merge */
         test_pkg.test('x') r
  FROM   DUAL
) t;
Run Code Online (Sandbox Code Playgroud)

db<>在这里摆弄