Oracle 宏和 UDF 的区别

Dav*_*542 4 oracle user-defined-functions sql-macro

在 Oracle 中创建“普通”UDF 与宏 UDF 有什么区别?例如,对于宏,他们给出了一个例子:

CREATE FUNCTION date_string(dat DATE) 
                    RETURN VARCHAR2 SQL_MACRO(SCALAR) IS
BEGIN
   RETURN q'{
             TO_CHAR(dat, 'YYYY-MM-DD')
          }';
END;
/
Run Code Online (Sandbox Code Playgroud)

并为一个函数。

在什么用例中宏比普通函数更有用?什么时候使用函数比宏更好?其中一个是否比另一个具有性能优势或局限性?

Chr*_*xon 9

SQL 和 PL/SQL 语言具有单独的运行时引擎。这意味着每次 SQL 语句调用 PL/SQL UDF 时,都会发生上下文切换(反之亦然)。

虽然每次切换速度很快,但在 SQL 语句中调用 PL/SQL 函数数千或数百万次可能会使其速度显着变慢。

SQL 宏的工作方式不同。在解析时,数据库解析表达式以使其成为语句的一部分。它在返回字符串中搜索参数名称。然后,使用您为这些参数传递的任何文本有效地查找/替换这些内容。

例如,如果您运行:

select date_string ( date_col ) 
from   some_table;
Run Code Online (Sandbox Code Playgroud)

最终的 SQL 语句实际上是:

select to_char ( date_col, 'yyyy-mm-dd' )
from   some_table;
Run Code Online (Sandbox Code Playgroud)

这意味着没有运行时上下文切换。这可以带来良好的性能提升,例如:

create function date_string_macro ( dat date ) 
   return varchar2 sql_macro(scalar) is
begin
   return q'{ to_char(dat, 'yyyy-mm-dd') }';
end;
/

create function date_string_plsql ( dat date ) 
   return varchar2 is
begin
   return to_char(dat, 'yyyy-mm-dd' );
end;
/

declare
  start_time pls_integer;
begin
  start_time := dbms_utility.get_time ();
  for rws in (
    select *
    from   dual
    where  date_string_plsql ( sysdate + level ) > '2021'
    connect by level <= 1000000
  ) loop
    null;
  end loop;
  dbms_output.put_line ( 
    'PL/SQL runtime = ' || ( dbms_utility.get_time () - start_time ) 
  );
  
  start_time := dbms_utility.get_time ();
  for rws in (
    select *
    from   dual
    where  date_string_plsql ( sysdate + level ) > '2021'
    connect by level <= 100000
  ) loop
    null;
  end loop;
  dbms_output.put_line ( 
    'Macro runtime  = ' || ( dbms_utility.get_time () - start_time ) 
  );
end;
/

PL/SQL runtime = 570
Macro runtime  = 54
Run Code Online (Sandbox Code Playgroud)

在这种情况下,速度大约快 10 倍!

由于表达式成为 SQL 语句的一部分,因此优化器的另一个好处是可以完全了解底层表达式。这可能会带来更好的执行计划。

这使您能够获得 PL/SQL 函数(例如通用公式、字符串格式化等)的代码重用优势以及纯 SQL 的性能。

那么为什么不制作所有现有的 PL/SQL 函数宏呢?

它们之间还有其他一些重要的区别。

首先,论证评估的顺序。PL/SQL 使用应用程序顺序,宏使用普通顺序。在某些情况下,这可能会导致行为差异:

create or replace function first_not_null ( 
  v1 int, v2 int
) 
  return int as
begin
  return coalesce ( v1, v2 );
end first_not_null;
/

select first_not_null ( 1, 1/0 ) from dual;

ORA-01476: divisor is equal to zero

create or replace function first_not_null  ( 
  v1 int, v2 int
) 
  return varchar2 sql_macro ( scalar ) as
begin
  return ' coalesce ( v1, v2 ) ';
end first_not_null;
/

select first_not_null ( 1, 1/0 ) from dual;

FIRST_NOT_NULL(1,1/0)   
                       1 
Run Code Online (Sandbox Code Playgroud)

其次,更重要的是,解析表达式仅发生在 SQL 中。如果您在 PL/SQL 中调用 SQL 宏,它将按原样返回字符串:

exec dbms_output.put_line ( first_not_null ( 1, 0 ) );

coalesce ( v1, v2 ) 
Run Code Online (Sandbox Code Playgroud)

最后,SQL 宏还有一个表变体。这允许您创建可以向其传递表和列的模板查询。

我在几篇博客文章中讨论了表宏,展示了如何使用表宏来编写通用的每组前 N 个函数和可重用的 CSV 到行函数