execute immediate 是 oracle 10g+ 执行动态 sql 的首选标准方式,替代 dbms_sql;支持 ddl(自动提交)、dml(需显式 commit)、单行 select 和 pl/sql 块,但不支持多行查询(须用 ref cursor),表名/字段名需字符串拼接,参数传值必须用 using 子句防注入。

EXECUTE IMMEDIATE 是首选,不是可选项
在 Oracle 10g 及以后版本中,EXECUTE IMMEDIATE 是执行动态 SQL 的标准方式,它替代了早期繁琐的 DBMS_SQL 包。除非你必须支持 Oracle 8i 或需处理极复杂的多行结果集(且无法用 REF CURSOR),否则别碰 DBMS_SQL —— 它写法冗长、易出错、调试困难。
关键点在于:EXECUTE IMMEDIATE 能直接处理 DDL(CREATE/DROP)、DML(INSERT/UPDATE)、单行 SELECT,甚至带绑定变量的 PL/SQL 块,但不能执行返回多行的查询。
- DDL 语句自动提交,无需额外
COMMIT - DML 语句不自动提交,必须显式加
COMMIT或在事务外调用 - 语句末尾**不能加分号**(
;),否则报ORA-00911: invalid character - PL/SQL 块末尾**必须加分号**,例如
EXECUTE IMMEDIATE 'BEGIN dbms_output.put_line(''hi''); END;';
表名/字段名要动态?只能靠字符串拼接
PL/SQL 编译期就检查静态 SQL 中的对象名(如 SELECT * FROM my_table),所以 my_table 必须是真实存在的表名,不能是变量。一旦你写 SELECT * FROM p_table_name,编译直接失败 —— 这不是运行时报错,是根本过不了编译。
真正可行的方式是把表名、字段名作为字符串拼进 SQL 语句里:
DECLARE v_table_name VARCHAR2(30) := 'EMP'; v_sql VARCHAR2(500); BEGIN v_sql := 'SELECT COUNT(*) FROM ' || v_table_name; EXECUTE IMMEDIATE v_sql INTO v_count; END;
- 务必校验输入:若
v_table_name来自用户或外部参数,先查ALL_TABLES确认存在,避免 SQL 注入风险 - 字段名同理,拼接前用
UPPER()统一大小写,防止因大小写敏感导致对象找不到 - 不要在拼接时加引号包裹表名(如
'"' || v_table_name || '"'),除非你明确需要双引号标识符(通常不需要)
传参必须用 USING,别手写字符串替换
动态 SQL 中的值(比如 WHERE 条件里的 ID、INSERT 的字段值)绝不能通过字符串拼接传入,否则会引发 SQL 注入和类型转换错误。正确做法是用占位符 :1、:name 配合 USING 子句:
EXECUTE IMMEDIATE 'UPDATE emp SET sal = :new_sal WHERE empno = :emp_id' USING p_new_salary, p_empno;
-
USING后的参数顺序必须与 SQL 中占位符出现顺序严格一致 - 只支持
IN参数(默认),如需OUT或IN OUT,需配合RETURNING INTO或显式声明方向 - 占位符不能出现在 DDL 语句中(如
CREATE TABLE :tname是非法的),DDL 只能拼接对象名 - 如果参数是 NULL,
USING仍可正常传递,不会导致语句语法错误
查多行数据?REF CURSOR 是唯一干净解法
EXECUTE IMMEDIATE 不支持直接获取多行结果(即不能 INTO 一个数组或记录集合)。硬要用,只能临时建表、插入再查,既低效又污染数据。
标准做法是声明 REF CURSOR,用 OPEN ... FOR 动态打开游标:
TYPE t_cursor IS REF CURSOR;
v_cur t_cursor;
v_empno EMP.EMPNO%TYPE;
v_ename EMP.ENAME%TYPE;
BEGIN
OPEN v_cur FOR 'SELECT empno, ename FROM ' || p_table_name || ' WHERE deptno = :dno'
USING p_deptno;
LOOP
FETCH v_cur INTO v_empno, v_ename;
EXIT WHEN v_cur%NOTFOUND;
-- 处理每一行
END LOOP;
CLOSE v_cur;
END;
-
REF CURSOR可以作为存储过程的OUT参数返回给调用方(如 Java JDBC),这是最常用交互模式 - 游标打开后必须
CLOSE,否则可能耗尽会话游标资源(ORA-01000: maximum open cursors exceeded) - 不要试图用
EXECUTE IMMEDIATE+BULK COLLECT INTO替代 —— 它仅适用于已知结构的单次批量查询,不解决“表名动态”问题
最常被忽略的是:动态 SQL 的错误堆栈不包含原始拼接字符串,只显示 EXECUTE IMMEDIATE 所在行号。调试时务必先 DBMS_OUTPUT.PUT_LINE(v_sql) 打印出完整语句,再手动在 SQL*Plus 里执行验证 —— 这比在 PL/SQL 块里反复改、反复编译快得多。











