SQL_MACRO比PL/SQL函数更适合复用SQL逻辑,因其在解析阶段将参数化SQL表达式内联展开为原生SQL片段,消除上下文切换、优化器全程可见、支持谓词下推与索引推导;SCALAR用于替换标量表达式(如SELECT/WHERE中),TABLE用于替换表源(仅限FROM子句)。
SQL_MACRO 是 Oracle 19c(19.7+)引入的真正替代 PL/SQL 函数嵌入 SQL 的方案,它不是“封装逻辑”,而是把参数化 SQL 表达式直接内联进查询执行计划——没有上下文切换、不触发函数调用开销、优化器全程可见。
为什么 SQL_MACRO 比 PL/SQL 函数更适合复用 SQL 逻辑?
pl/sql 函数在 where 或 select 中调用时,oracle 无法展开其内部逻辑,导致:优化器估算严重失准(比如预估 1 行实际扫 10 万行)、每行都触发一次 pl/sql 上下文切换、无法利用索引谓词推导。而 sql_macro 在解析阶段就被展开成原生 sql 片段,和手写 sql 完全等价。
SQL_MACRO 的两种类型怎么选?
必须明确声明类型,否则报错 ORA-62225: macro must be declared as either SCALAR or TABLE:
-
SCALAR:用于替换单个表达式,比如计算字段、条件判断,返回标量值;适用于
SELECT列或WHERE条件中 -
TABLE:用于替换整个表源,返回行集;只能用在
FROM子句,类似带参数的视图
别混淆:SCALAR 不是“返回一行”,而是“返回一个值”;TABLE 不是“返回多行”,而是“提供一个可 JOIN 的结果集”。例如 fiscal_year_start 逻辑应定义为 SCALAR;按部门动态过滤员工列表应定义为 TABLE。
写一个带参数的 SQL_MACRO 函数要注意什么?
核心限制比 PL/SQL 函数更严格:
- 不能引用包变量、会话状态(如
USER、SYS_CONTEXT需显式传参) - 不能调用非 deterministic 的函数(如
SYSDATE、DBMS_RANDOM.VALUE),除非你接受每次展开结果可能不同 - 参数类型仅支持
NUMBER、VARCHAR2、DATE等基础类型,不支持 RECORD、OBJECT 类型 - 宏体里不能有 DML、DDL、COMMIT,但可以包含子查询、WITH、JOIN —— 因为它最终只是 SQL 文本拼接
示例(SCALAR):
CREATE OR REPLACE FUNCTION fiscal_year_start(p_date DATE)
RETURN VARCHAR2
SQL_MACRO(SCALAR) AS
BEGIN
RETURN q'[
ADD_MONTHS(TRUNC(ADD_MONTHS(p_date, -6), 'YYYY'), 6)
]';
END;
调用时:SELECT fiscal_year_start(hire_date) FROM employees,会被展开为 ADD_MONTHS(TRUNC(ADD_MONTHS(hire_date, -6), 'YYYY'), 6),完全透明。
如何安全地把旧 PL/SQL 函数迁移到 SQL_MACRO?
不是简单改个关键字,重点在剥离副作用和状态依赖:
- 若原函数依赖
DBMS_SESSION.SET_IDENTIFIER或缓存表,必须重构:把状态变量转为显式参数传入 - 若含
UTL_HTTP或DBMS_LOB调用,不能迁——SQL_MACRO只生成 SQL,不执行任何过程 - 若原函数做了复杂字符串拼接(如 XML 构建),确认目标字段是否支持
XMLSERIALIZE等内建函数;否则仍需保留 PL/SQL 层,仅用SQL_MACRO包装查询入口 - 迁移后务必检查执行计划:对比前后
PLAN_HASH_VALUE和ACCESS PREDICATES是否变化,避免误用导致索引失效
最易被忽略的一点:SQL Macro 的参数名在展开后直接替换,不经过绑定变量处理。如果传入的是用户输入,必须由调用方做 SQL 注入防护——宏本身不提供参数化安全机制。











