在PL/SQL中,DDL语句必须使用动态SQL(EXECUTE IMMEDIATE)执行,因为PL/SQL在编译时会检查语法和对象存在性,而DDL涉及的对象(如表)在编译时可能不存在或不确定。
静态SQL直接执行DDL会报错,而动态SQL将SQL语句作为字符串在运行时解析执行,从而绕过编译检查。
常见DDL操作(CREATE/DROP/TRUNCATE等)都需要动态SQL实现,而DML语句可直接使用。
核心区别在于编译时检查(静态SQL)与运行时解析(动态SQL),这也是DDL必须使用动态SQL的根本原因。
DDL 语句必须使用动态SQL来执行
在 PL/SQL 程序块中,DDL 语句必须使用动态SQL(EXECUTE IMMEDIATE)来执行。
📌 为什么 DDL 必须用动态SQL?
核心原因
PL/SQL 是编译时检查语法和对象存在性,而 DDL 语句(如CREATE、DROP、TRUNCATE)涉及的对象在编译时可能不存在或不确定。
对比说明
| 语句类型 | 能否直接在PL/SQL中使用 | 原因 |
|---|---|---|
| DML(INSERT/UPDATE/DELETE) | ✅ 可以 | 对象在编译时必须存在 |
| SELECT | ✅ 可以 | 表在编译时必须存在 |
| DDL(CREATE/DROP/TRUNCATE) | ❌ 不可以 | 编译时对象可能不存在,语法检查会失败 |
❌ 错误示例(直接使用DDL会报错)
sql
BEGIN -- 这样写会报错! TRUNCATE TABLE EMP_0508; END; /
报错信息:
PLS-00103: Encountered the symbol "TRUNCATE"
✅ 正确示例(使用动态SQL)
sql
BEGIN -- 必须用 EXECUTE IMMEDIATE EXECUTE IMMEDIATE 'TRUNCATE TABLE EMP_0508'; END; /
📚 常见DDL语句的动态SQL写法
1. TRUNCATE 表
sql
BEGIN EXECUTE IMMEDIATE 'TRUNCATE TABLE EMP_0508'; END;
2. DROP 表
sql
BEGIN EXECUTE IMMEDIATE 'DROP TABLE EMP_0508'; END;
3. CREATE 表
sql
BEGIN EXECUTE IMMEDIATE 'CREATE TABLE EMP_TEST AS SELECT * FROM EMP WHERE 1=2'; END;
4. 带变量的DDL(动态表名)
sql
DECLARE V_TABLE_NAME VARCHAR2(100) := 'EMP_20240509'; V_SQL VARCHAR2(1000); BEGIN -- 动态构造表名 V_SQL := 'CREATE TABLE ' || V_TABLE_NAME || ' AS SELECT * FROM EMP'; EXECUTE IMMEDIATE V_SQL; END;
🎯 你笔记中的示例回顾
示例1:批量创建100张表
sql
CREATE OR REPLACE PROCEDURE P_CRE_TABLE IS V_STR VARCHAR2(100); BEGIN FOR I IN 1..100 LOOP V_STR := 'CREATE TABLE EMP__' || LPAD(I, 3, 0) || ' AS SELECT * FROM EMP WHERE 1=2'; EXECUTE IMMEDIATE V_STR; -- ✅ DDL必须用动态SQL END LOOP; END;
示例2:TRUNCATE表
sql
CREATE OR REPLACE PROCEDURE P_014 IS BEGIN EXECUTE IMMEDIATE 'TRUNCATE TABLE EMP_0508'; -- ✅ DDL必须用动态SQL INSERT INTO EMP_0508 SELECT * FROM EMP; COMMIT; END;
📊 总结对比表
| 语句类型 | 是否可直接写 | 是否需要动态SQL | 原因 |
|---|---|---|---|
| SELECT | ✅ 可以 | ❌ 不需要 | 表在编译时必须存在 |
| INSERT | ✅ 可以 | ❌ 不需要 | 表在编译时必须存在 |
| UPDATE | ✅ 可以 | ❌ 不需要 | 表在编译时必须存在 |
| DELETE | ✅ 可以 | ❌ 不需要 | 表在编译时必须存在 |
| CREATE | ❌ 不可以 | ✅必须 | 表在编译时不存在 |
| DROP | ❌ 不可以 | ✅必须 | 表可能不存在 |
| TRUNCATE | ❌ 不可以 | ✅必须 | DDL语句,编译时不识别 |
| ALTER | ❌ 不可以 | ✅必须 | DDL语句 |
✅ 记忆口诀
DML操作直接写,DDL语句套动态;
CREATE、DROP、TRUNCATE,EXECUTE IMMEDIATE来执行。
为什么动态SQL可以执行DDL
核心原因:编译时 vs 运行时
这个问题问得很好!让我从Oracle PL/SQL的执行机制来解释。
📖 根本原因:两阶段执行机制
1. 静态SQL(直接写的SQL)
在PL/SQL代码编译阶段就会进行语法检查和对象存在性验证:
sql
BEGIN TRUNCATE TABLE EMP_0508; -- 编译时就要验证 END;
编译时发生了什么?
Oracle检查
TRUNCATE关键字是否合法检查表
EMP_0508是否存在检查当前用户是否有权限
如果表不存在,编译直接失败
2. 动态SQL(EXECUTE IMMEDIATE)
SQL语句被写成字符串,编译时不检查,只在运行时才执行:
sql
BEGIN EXECUTE IMMEDIATE 'TRUNCATE TABLE EMP_0508'; -- 编译时不检查内容 END;
编译时发生了什么?
Oracle只检查
EXECUTE IMMEDIATE语法是否正确不检查字符串里面的SQL语句
字符串内容只在运行到这一行时才被解析和执行
🔍 详细对比
| 阶段 | 静态SQL | 动态SQL |
|---|---|---|
| 编译时 | ✅ 语法检查 ✅ 对象存在性检查 ✅ 权限检查 | ✅ 只检查EXECUTE IMMEDIATE语法❌不检查字符串内容 |
| 运行时 | 直接执行(已编译好) | 1. 解析字符串 2. 语法检查 3. 对象存在性检查 4. 权限检查 5. 执行 |
💡 举个例子说明
场景:创建一个新表,然后查询它
❌ 静态SQL做不到
sql
BEGIN -- 编译时:EMP_NEW表还不存在,编译失败! CREATE TABLE EMP_NEW AS SELECT * FROM EMP; SELECT COUNT(*) INTO V_CNT FROM EMP_NEW; END; /
✅ 动态SQL可以做到
sql
BEGIN -- 编译时:只检查字符串拼接语法,不检查表是否存在 EXECUTE IMMEDIATE 'CREATE TABLE EMP_NEW AS SELECT * FROM EMP'; -- 运行时:表已经创建成功,可以查询 EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM EMP_NEW' INTO V_CNT; END; /
🎯 类比理解
静态SQL = 提前订餐
你必须提前知道餐厅有什么菜
菜单上没写的菜不能点
餐厅关门了你就吃不了
动态SQL = 现场点菜
你可以运行时根据情况决定点什么
可以让厨师现做菜单上没有的菜
灵活性强,但稍微慢一点
📊 为什么DDL必须用动态SQL?
因为DDL语句涉及的对象在编译时通常不存在或不确定:
| DDL语句 | 编译时问题 | 解决方案 |
|---|---|---|
CREATE TABLE | 表还不存在 | 动态SQL |
DROP TABLE | 表可能不存在 | 动态SQL |
TRUNCATE TABLE | DDL在PL/SQL中不直接支持 | 动态SQL |
ALTER TABLE | 表结构可能变化 | 动态SQL |
⚡ 性能角度(了解即可)
sql
-- 静态SQL:编译一次,执行多次(快) FOR I IN 1..10000 LOOP INSERT INTO EMP VALUES(...); -- 已经编译好 END LOOP; -- 动态SQL:每次都要重新解析(慢) FOR I IN 1..10000 LOOP EXECUTE IMMEDIATE 'INSERT INTO EMP VALUES(...)'; -- 每次都要解析 END LOOP;
💡最佳实践:能用静态SQL就不用动态SQL,DDL才被迫用动态SQL。
✅ 总结
| 问题 | 答案 |
|---|---|
| 为什么DDL必须用动态SQL? | 因为DDL的对象在编译时可能不存在 |
| 动态SQL为什么能执行DDL? | 因为它在运行时才解析SQL语句 |
| 核心区别 | 编译时检查 vs 运行时检查 |
一句话记忆
静态SQL编译查,动态SQL运行查;
DDL对象常暂无,必须动态来执行。