如果在 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
我试图了解 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