如果在 main SQL 中使用别名,则子查询函数被调用两次

Subquery function invoked twice if alias is used in main SQL

我试图了解 Oracle 如何处理 SQL 以研究优化复杂 SQL 的方法。考虑下面的测试函数:

CREATE OR REPLACE FUNCTION FCN_SLOW
  RETURN NUMBER IS
BEGIN
  DBMS_LOCK.SLEEP (5); --5 seconds
  RETURN 0;
END FCN_SLOW;

和下面的 SQL,使用创建的函数:

SELECT A1 + A1
  FROM (SELECT FCN_SLOW () AS A1
          FROM DUAL)

为什么执行需要 10 秒,而不是 5 秒?有没有办法强制重用FCN_SLOW计算出来的值,不被执行两次?

SQL 引擎选择不具体化子查询,并将函数调用推送到外部查询,在那里它为每一行调用多次。您需要强制在调用它的子查询中评估函数,而不是允许 SQL 引擎重写查询。

一种方法是使用ROWNUM强制实现内部查询:

SELECT A1 + A1
  FROM (SELECT FCN_SLOW () AS A1
          FROM DUAL
         WHERE ROWNUM >= 1)

另一种方法是使用 CTE 和 (undocumented) materialize 提示:

WITH slow_query(a1) AS (
  SELECT /*+ materialize */
         FCN_SLOW ()
  FROM   DUAL
)
SELECT A1 + A1
  FROM slow_query

db<>fiddle here [需要 20 秒才能 运行... 而不是 30 秒。]

您可以在 .

中看到一个具有具体化序列值(而不是休眠)的类似示例

因为你没有指明下次该函数不会产生不同的结果。 compiler/optimizer 不能也不应该这样假设。

这告诉编译器它从相同的输入产生相同的结果(在这种情况下没有)

create or replace function fcn_slow return number deterministic is
begin
  dbms_lock.sleep(5); --5 seconds
  return 0;
end fcn_slow;

所以 5 秒后 运行 秒。

此致

** 编辑 我想说明的是,如果你不知道函数中的值是如何获取的,那么你应该而不是强制它只运行一次。

  • 可能是从消息队列中获取号码
  • 里面可能有一些随机元素
  • 可能是从高 DML 中读取数字 table
  • 等...

以此为例:

with function a1 return number is 
  l_num number;
begin
  select round(to_number(to_char(systimestamp, 'SSxFF')) * 100000, 0) into l_num from dual;
  dbms_output.put_line(l_num);
  return l_num;
end;
select a1 + a1 from dual

您认为正确的行为是什么? 运行 a1 两次还是一次?

我的示例输出不同的数字,这是正确的行为,因为每次调用都会产生不同的 return:

1088614
1088645

如果您确实知道该函数使用相同的输入产生相同的值,那么您应该使其具有确定性,让编译器知道它可以重用以前的值。

示例:

with function pi(p_multi in number default 1) return number deterministic is 
  l_pi number;
begin
    select 2*asin(1) * p_multi into l_pi from dual;
    dbms_output.put_line(l_pi);
    return l_pi;
end;
select pi + pi(2) + pi(2) from dual

只输出两行,这是正确的行为:

3,1415926535897932384626433832795028842
6,2831853071795864769252867665590057684