As you may know, calling a PL/SQL function from a SQL query introduces a context switch between the SQL engine and the PL/SQL engine. This context switching can have a negative impact on query performance.
For example, the following two statements return the same result, but their execution times are significantly different.
Query without using a function
SQL> select sum(sal) from(select salary+60 as sal from person);
SUM(SAL)
———
8891924480
Elapsed: 00:00:00.81
Query using a function
SQL> select sum(sal) from(select sal_func(salary) as sal from person);
SUM(SAL)
———
8891924480
Elapsed: 00:00:10.91
Note: The sal_func function is defined as follows:
create or replace function sal_func(sal_param number)
return number
is
begin
return sal_param +60 ;
end;
/
As you can see, the first query takes less than one second to execute, whereas the second query takes approximately 11 seconds.
This comparison might lead us to avoid calling PL/SQL functions from SQL queries. However, avoiding functions altogether can introduce significant maintenance overhead. For example, if a formula needs to be modified, we may have to update multiple parts of an application. Therefore, avoiding function calls in SQL statements is not always the best solution.
To address this performance issue, Oracle introduced SQL Macros in Oracle Database 21c.
SQL Macros allows Oracle Database to expand a function call into its corresponding SQL expression or query during SQL macro expansion. As a result, the SQL statement can be optimized as if the expanded SQL expression had been written directly in the original statement.
For example, Oracle transforms the following query:
select sum(sal)
from(
select sal_func(salary) as sal
from person
);
Into the following query:
Final query after transformations:******* UNPARSED QUERY IS *******
SELECT SUM(“PERSON”.”SALARY”+60) “SUM(SAL)” FROM “USEF”.”PERSON” “PERSON”
According to Oracle’s feature classification, SQL Macros are a new feature of Oracle Database 21c. However, they were also documented in Oracle Database 20c documentation and are available in Oracle Database 19c starting with Release Update 19.6.
Creating a SQL Macro
To create a SQL Macro, you must specify the SQL_MACRO clause in the function declaration.
The return type of a SQL Macro function must be a character data type, such as VARCHAR2 or CLOB, rather than the data type of the value returned by the expanded SQL expression.
Additionally, the SQL expression or query returned by the function must be enclosed in a quoted string, commonly using Oracle’s alternative quoting mechanism (q’{}’).
For example, we can rewrite the sal_func function as a Scalar SQL Macro:
create or replace function sal_func(sal_param number)
return varchar2 sql_macro(scalar) is
begin
return q'{
sal_param +60
}’;
end;
/
With this modification, the execution time of the query presented earlier decreases from approximately 11 seconds to just over one second:
SQL> select sum(sal) from(select sal_func(salary) as sal from person);
SUM(SAL)
———-
8891924480
Elapsed: 00:00:01.11
Types of SQL Macros
Oracle Database supports two types of SQL Macros:
Scalar SQL Macros: Can be used in SQL expressions, including the SELECT list and applicable clauses such as WHERE and HAVING.
Table SQL Macros: Can be used in the FROM clause and return a SQL query that acts as a table expression.
Therefore, the SAL_FUNC function is an example of a Scalar SQL Macro.
The following function, which returns the size (in MB) of the largest segment belonging to a specified schema, is an example of a Table SQL Macro:
create or replace function table_macro_Mesal(p_tab varchar2)
return varchar2 sql_macro(table) is
begin
return q'{
select max(bytes/1024/1024) SIZE_MB from
dba_segments d
where
owner =table_macro_Mesal.p_tab
}’;
end;
/
We can call this Table SQL Macro as follows:
SQL> select * from table_macro_Mesal(‘SYS’);
SIZE_MB
———
46
Passing Multiple Parameters to a Table SQL Macro
The following function accepts two input parameters:
cnt: The number of segments to return.
typ: the segment type to filter by.
It returns the largest segments based on their size, limited to the specified number of rows.
create or replace function table_macro_Mesal2(cnt number,typ varchar2)
return varchar2 sql_macro(table) is
begin
return q'{
select owner, segment_name, bytes / 1024 / 1024 SIZE_MB
from dba_segments
where segment_type=table_macro_Mesal2.typ
order by bytes desc fetch first cnt rows only
}’;
end;
/
For example, to retrieve the five largest table segments in the database, we can execute:
SQL> select * from table_macro_Mesal2(5,’TABLE’);
OWNER SEGMENT_NAME SIZE_MB
———- ————————- ———-
USEF JADVAL_TEST 312
USEF PERSON 104
SYS IDL_UB2$ 16
SYS WRI$_SQLSET_PLAN_LINES 15
SYS IDL_UB1$ 12
As demonstrated in these examples, SQL Macros provide a way to encapsulate reusable SQL expressions and queries while allowing the SQL optimizer to work with the expanded SQL statement.
Top comments (0)