EXECUTE IMMEDIATE 可执行 DDL,但须用完整字符串且禁止未校验的变量拼接对象名;参数值必须用 USING 绑定;单行用 INTO,多行用 BULK COLLECT INTO;须捕获 NO_DATA_FOUND、TOO_MANY_ROWS 及 DDL 相关异常。
EXECUTE IMMEDIATE 不能直接执行 DDL 语句的变量拼接
很多人以为把 create table 语句拼成字符串,再传给 execute immediate 就能运行,结果报 ora-06550: line x, column y: pls-00103: encountered the symbol "..." —— 其实不是语法错,而是 pl/sql 编译器在编译阶段就拒绝了未解析的 ddl 字符串。
真正能动态执行的,是运行时才确定结构的语句。比如表名、列名、WHERE 条件值这些可变部分,必须用绑定变量或字符串拼接(但后者要格外小心)。
- DDL(如
CREATE、DROP、ALTER)允许用EXECUTE IMMEDIATE,但语句必须是完整字符串,不能含未声明的标识符变量(如不能写'CREATE TABLE ' || table_name || ' (...)',除非table_name是已知合法标识符且做了严格校验) - 推荐对动态对象名做白名单过滤或用
DBMS_ASSERT.SIMPLE_SQL_NAME校验,避免 SQL 注入 - DDL 执行后不会自动提交,需显式
COMMIT(除非在自治事务中)
绑定变量比字符串拼接更安全、更高效
动态 SQL 中混入用户输入时,用 || 拼接字符串是最常见翻车点:引号嵌套混乱、空值变 NULL 导致语法错误、甚至被注入恶意代码。
EXECUTE IMMEDIATE 支持 USING 子句传参,和静态 SQL 的绑定机制一致,Oracle 能复用执行计划,也天然防注入。
- WHERE 条件值、INSERT 的字段值、UPDATE 的 SET 部分都该走
USING - 不能用绑定变量替代对象名(表名、列名、排序字段),那些必须拼接,但要加引号和校验:
EXECUTE IMMEDIATE 'SELECT * FROM ' || DBMS_ASSERT.SQL_OBJECT_NAME(v_table) || ' WHERE id = :1' INTO v_result USING p_id; - 多个参数按顺序匹配
USING,位置错一位就报ORA-01008: not all variables bound
INTO 和 BULK COLLECT 的使用边界
想把查询结果存到变量里,得看返回几行。单行用 INTO,多行必须用 BULK COLLECT INTO,否则一查多行就报 ORA-01422: exact fetch returns more than requested number of rows。
注意 INTO 只支持单行单列(标量)、单行多列(记录类型);BULK COLLECT 必须配合集合类型(如 TABLE OF ...),且不检查是否溢出——查百万行就真塞百万条进内存。
- 单值查询:
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM emp WHERE deptno = :1' INTO v_cnt USING p_deptno; - 单行多列:
EXECUTE IMMEDIATE 'SELECT ename, sal FROM emp WHERE empno = :1' INTO v_ename, v_sal USING p_empno; - 多行结果:
EXECUTE IMMEDIATE 'SELECT ename FROM emp WHERE deptno = :1' BULK COLLECT INTO v_names USING p_deptno; - 没结果时,
INTO报ORA-01403: no data found;BULK COLLECT不报错,集合长度为 0
异常捕获必须覆盖 ORA-00900 到 ORA-00942 这类硬错误
动态 SQL 的错误发生在运行时,编译期发现不了。最常遇到的是表不存在(ORA-00942)、语句语法错(ORA-00900)、权限不足(ORA-01031)。如果没包在 EXCEPTION 块里,整个过程就崩了。
别只 catch OTHERS,至少区分对象类错误和数据类错误,方便定位是脚本逻辑问题,还是环境配置问题。
- 必须捕获
NO_DATA_FOUND、TOO_MANY_ROWS(对应INTO场景) - DDL 失败常见
ORA-00955: name is already used、ORA-02289: sequence does not exist,建议单独处理 - 用
SQLCODE和SQLERRM记录原始错误,比模糊的 “动态执行失败” 有用得多
动态 SQL 的麻烦不在语法,而在它把编译期检查全推到了运行时——少一个引号、漏一个绑定、对象名多空格,都得等执行那一刻才暴露。校验、绑定、异常这三件事,漏掉哪件都容易半夜被叫起来改生产脚本。










