Oracle SQL 编写(DDL / DML / PL/SQL)
何时使用
满足以下任一情况时使用(本技能覆盖 DDL、DML 与承载它们的 PL/SQL 程序单元的编写与审阅):
- 需要新建或修改数据库对象:表、索引、约束、分区、表空间、序列、视图、物化视图、数据库链接、虚拟列、用户
- 需要编写查询或增删改语句,尤其是要落到生产库上的
- 需要编写或修改存储过程、函数、包、触发器、匿名块
- 有语句执行报错,需要定位原因并改正
- 需要判断一段 SQL 是否合法、是否存在注入风险、是否该被拦截
目录结构
oracle-sql-authoring/
├── SKILL.md
├── scripts/
│ └── check_sql.py
└── references/
├── admin/
├── agent/
├── appdev/
├── design/
├── devops/
├── features/
├── performance/
├── plsql/
└── sql-dev/
参考文档索引
| 主题 | 位置 |
|---|---|
| 写库前的模式探查、危险操作护栏、幂等写法、ORA- 报错对照、意图澄清、自然语言转 SQL | references/agent/ |
| SQL 写法规范、查询模式(分析函数、CTE)、动态 SQL、注入规避 | references/sql-dev/ |
| 包设计、异常处理、游标、集合、性能、模式、安全、命名规范、调试、编译选项 | references/plsql/ |
| 序列与标识列、事务管理、锁与并发 | references/appdev/ |
| 数据建模、范式与 ER 设计、分区策略与语法、表空间创建与容量 | references/design/ |
大表在线变更(DBMS_REDEFINITION、ALTER TABLE … ONLINE) |
references/devops/ |
| 物化视图、数据库链接、虚拟列 | references/features/ |
索引类型与 CREATE INDEX 策略 |
references/performance/ |
CREATE/ALTER/DROP USER、概要文件 |
references/admin/ |
常用入口
references/sql-dev/sql-best-practices.mdreferences/sql-dev/sql-patterns.mdreferences/agent/schema-discovery.mdreferences/agent/safe-dml-patterns.mdreferences/agent/idempotency-patterns.mdreferences/agent/ora-error-catalog.mdreferences/plsql/plsql-error-handling.mdreferences/design/data-modeling.md
静态检查脚本
scripts/check_sql.py 对 SQL 做纯文本层面的检查,不连接数据库、不执行任何语句。
python3 scripts/check_sql.py 脚本.sql # 检查文件
python3 scripts/check_sql.py - # 从标准输入读取
cat 脚本.sql | python3 scripts/check_sql.py -
python3 scripts/check_sql.py --json 脚本.sql # 结构化输出
python3 scripts/tests/run_matrix.py # 自检:13 用例回归矩阵,全过应退出 0
检查三类问题:
- 语句合法性——括号与引号配对、
BEGIN/END配对、必备子句(INSERT … INTO、UPDATE … SET、DELETE … FROM、MERGE … USING/ON)、起始关键字拼写、UPDATE/DELETE是否漏写WHERE - 注入风险——动态 SQL(
EXECUTE IMMEDIATE、OPEN … FOR、DBMS_SQL)用||拼接、拼接变量未经DBMS_ASSERT校验、DBMS_SQL.PARSE缺BIND、恒真条件残留 - 危险语句闸门——按下面的环境变量决定是否放行
环境变量 ORACLE_SQL_ACCESS_MODE
| 取值 | 含义 |
|---|---|
readonly |
默认。只允许查询类语句;任何会改数据的语句一律拦截——DML(INSERT/UPDATE/DELETE/MERGE)、DDL(CREATE/ALTER/DROP/TRUNCATE/COMMENT/RENAME…)、DCL(GRANT/REVOKE)、事务控制、PL/SQL 对象定义、含写操作的匿名块、SELECT … FOR UPDATE |
readwrite |
允许写操作;DROP、TRUNCATE、PURGE 等不可逆语句仍会提示,SELECT … FOR UPDATE 给出阻塞警告 |
未设置时按 readonly 处理——默认只读是刻意的:不显式声明允许写,就不放行写操作。取值非法时退回 readonly 并在标准错误输出告警。
闸门按语句逐条判定,不是只看第一条——SELECT 1 FROM DUAL; DROP TABLE users; 里的 DROP 照样会被拦下。匿名块内部同样逐条扫描;块内的动态 SQL 会按其语句文本判定,因此 EXECUTE IMMEDIATE 'DROP TABLE orders' 被识别为写操作,而语句文本来自变量的(如 EXECUTE IMMEDIATE v_sql)因无法静态确认,在只读模式下一律拦截。
# 只读(默认)直接跑;要写库时加前缀:ORACLE_SQL_ACCESS_MODE=readwrite python3 …
python3 scripts/check_sql.py query.sql
退出码
| 码 | 含义 |
|---|---|
| 0 | 通过 |
| 1 | 被只读模式拦截 |
| 2 | 存在语法问题 |
| 3 | 存在注入风险 |
多类问题同时存在时返回其中最大的码值。非零即不应执行。
若环境中装有 sqlglot,脚本会用它复核普通 SQL(SELECT/INSERT/UPDATE/DELETE/MERGE/DDL)的语法;未安装时退化为内置结构检查,无需任何第三方依赖。
PL/SQL 程序体不做语法校验。 包、触发器、匿名块只会被检查块配对、括号引号、写操作与注入特征;sqlglot 对这类结构会退化成不透明的命令节点,交出去只会得到"看似合法"的假结论。脚本会在结论中明确标注哪些语句属于这一情况,这些语句的语法正确性需在数据库中编译确认。
常用组合流程
| 任务 | 建议顺序 |
|---|---|
| 新建一张表 | schema-discovery → sql-best-practices → index-strategy → idempotency-patterns |
| 从需求设计全新模式 | data-modeling → erd-design → schema-discovery → index-strategy |
| 变更已有大表 | schema-discovery → destructive-op-guards → online-operations → idempotency-patterns |
| 对生产数据写 DML | intent-disambiguation → safe-dml-patterns → transaction-management |
| 编写可重复执行的脚本 | idempotency-patterns → safe-dml-patterns |
| 语句运行报错 | ora-error-catalog → schema-discovery |
| 脚本里拼动态 SQL | dynamic-sql → sql-injection-avoidance |
| 编写包或过程 | plsql-package-design → plsql-error-handling → plsql-code-quality |
| PL/SQL 里批量处理数据、遍历查询结果 | plsql-performance → plsql-collections / plsql-cursors → sql-best-practices |
| 包编译不过或行为异常 | plsql-compiler-options → plsql-debugging → plsql-error-handling |
| 设计表分区 | partitioning-strategy → tablespace-design → online-operations |
失败与恢复
遇到下列情况按对应分支处理,不要跳过:
| 触发条件 | 一线处理 | 仍失败时的兜底 |
|---|---|---|
check_sql.py 报 × [语法](拼写、缺 INTO/SET/FROM 等) |
按提示修正,重跑脚本 | 重跑仍报错 → 把脚本输出贴给用户并说明已试过的修改 |
check_sql.py 报 × [注入风险](动态拼接) |
改绑定变量(USING)或 DBMS_ASSERT 校验后重跑 |
必须拼接(如表名动态)→ DBMS_ASSERT.SIMPLE_SQL_NAME 并在交付中标注风险点 |
check_sql.py 报 1 被只读模式拦截 |
这不是错误:确认当前任务是否真的要写;要写则换 readwrite 模式重跑 |
任务只是查询 → 保留只读模式,改写语句去掉写操作 |
拿到的 DDL 是 Hive/Spark 等非 Oracle 方言(STRING、STORED AS parquet、IF NOT EXISTS) |
声明方言并按映射转换后写 Oracle 版;IF NOT EXISTS 改为数据字典存在性检查 |
目标库本身不确定 → 停下问用户,不要替用户选方言 |
运行期报 ORA- 错误 |
查 ora-error-catalog.md 对应条目处理 |
目录没有该码 → 向用户要 SQLERRM 全文与语句上下文,不要凭码猜 |
| 用户口径模糊("状态不对的""最近""活跃") | 列澄清问题(≤3),确认后再写 | 用户说"先按假设写" → 交付中逐条列假设及替代写法 |
| 现有模式与需求冲突(列名已存在、类型不符、表已存在) | 先跑 schema-discovery 查询确认现状 |
涉及业务取舍 → 给用户两个方案及代价让其选 |
sqlglot 未安装(解析器显示"内置结构检查") |
照常交付,注明"未做完整语法解析,需在库上编译确认" | 可安装时提示 pip install sqlglot 后重跑 |
| PL/SQL 程序体需语法确认 | 说明"本工具不校验 PL/SQL 语法",给编译命令(ALTER … COMPILE) |
编译报错 → 按 plsql-debugging.md、plsql-error-handling.md 排查 |
每条分支的产出仍然是"过检的语句 + 说明",与「工作方式」第 4、5 条一致。
工作方式
- 先明确目标库与口径:方言(DDL 若非 Oracle——如 Hive/Spark 的
STRING、STORED AS parquet——先声明并映射转换STRING→VARCHAR2、BIGINT→NUMBER(19,0),不要直接按 Oracle 写)、指标含义、时间字段、粒度、去重键、空值处理。口径不唯一时先问,不要猜。 - 写 DDL 前先探查现有模式,避免重复创建、命名冲突、类型不匹配。
- 写 DML 前先确认影响范围,对生产库的写操作先预估行数。
- 写完必须过脚本:交付前对生成的语句运行
scripts/check_sql.py,确认语法与注入两项均无×级问题。 - 有问题就改,改完再跑:脚本报告
×级问题时,必须修正语句并重新执行脚本,直到语法与注入检查通过;不得把带问题的语句交付给用户。若某条×只是口径不清导致的(例如确实需要对全表UPDATE),先向用户确认,而不是带病交付。
🔴 检查点(必须停下等用户确认)
到达下列任一情况时,停止生成、先向用户提问,得到明确答复前不得继续交付:
| 🔴 检查点 | 停下来问什么 | 为什么必须停 |
|---|---|---|
🛑 交付会改生产库数据的语句(UPDATE/DELETE/MERGE 影响 >0 行) |
影响行数、是否已备份、执行窗口 | 不可逆;写错即事故 |
🛑 需要从 readonly 切到 readwrite 模式 |
当前任务是否真的要写库 | 只读是默认闸门;切换即授权写操作 |
🛑 要 DROP / TRUNCATE 任何对象,或对生产表无备份 DELETE |
是否有备份、依赖对象、回滚方案 | 不可逆且连带依赖;先给备份方案(CREATE TABLE … AS SELECT) |
检查点通过后,用户答复写入「口径假设」小节可追溯。
🔴 禁止事项(反例黑名单)
以下写法一律不得出现在交付中:
| ❌ 不要做 | 为什么 | ✅ 改为 |
|---|---|---|
UPDATE / DELETE 不带 WHERE,或未过 scripts/check_sql.py 就交付 |
全表改动难回退;未过检语句可能带语法错或注入 | 始终带精确 WHERE;交付前必跑检查器(见「工作方式」4、5 条) |
用 \|\| 把变量拼进 SQL 文本(含 EXECUTE IMMEDIATE、OPEN … FOR、DBMS_SQL.PARSE) |
注入风险,check_sql.py 会判 × |
绑定变量 USING;标识符用 DBMS_ASSERT |
SELECT * 进生产代码或 PL/SQL 记录 |
列变更即静默破坏;SELECT * INTO 遇列数不匹配直接报错 |
显式列出列名 |
对生产表直接 DROP / TRUNCATE / 无备份 DELETE |
不可逆 | 先给备份/归档方案(CREATE TABLE … AS SELECT),🔴 确认后再执行 |
把检查器报的 × 用注释、/* */ 或改写规避掉而不是修正 |
那是在掩盖问题不是修问题 | 按「失败与恢复」分支真正修正 |
在 PL/SQL 里用 WHEN OTHERS THEN NULL 吞异常 |
错误静默丢失,排查无从下手 | 记录后 RAISE,参考 plsql-error-handling.md |
对大数据集逐行 SELECT INTO 循环,或大批量 DML 一次跑完 |
逐行上下文切换慢一个数量级;长事务撑爆 undo、锁全表 | 集合 SQL / BULK COLLECT+FORALL;分批提交(LIMIT+COMMIT),参考 plsql-performance.md、safe-dml-patterns.md |
依赖隐式类型转换(字符串与 DATE/NUMBER 直接比较、'2024-01-01' 裸写) |
转换规则随 NLS 参数与版本变化,索引失效、结果漂移 |
显式 TO_DATE/TO_NUMBER 并带格式模型,参考 sql-best-practices.md |
违反任意一条即未完成,必须改到合规。
不覆盖的范围
数据库运维(实例参数、redo/undo、Data Guard、RAC、多租户)、性能诊断(AWR、ASH、执行计划、优化器统计)、异构库迁移、应用侧驱动与 ORM、命令行工具链、数据库内置 AI 特性——这些不属于本技能。