数据驱动非线性MPC:Matlab实现与工程实践指南
2026/10/6 9:51:26
PL/SQL(Procedural Language/SQL)是 Oracle 数据库专有的过程化编程语言,它在 SQL 的基础上增加了变量、控制结构、异常处理等程序设计能力,适用于编写存储过程、函数、触发器等数据库逻辑。
前提:已安装 Oracle Database(如 19c/21c)或 Oracle Express Edition (XE)
-- 以 sysdba 登录CONNECT/ASSYSDBA-- 解锁并设置密码ALTERUSERscott ACCOUNTUNLOCKIDENTIFIEDBYtiger;-- 连接 scottCONNECTscott/tiger-- 在 SQL*Plus 或 SQL Developer 中执行SETSERVEROUTPUTON;✅ 推荐工具:
- SQL*Plus(命令行)
- Oracle SQL Developer(图形界面,免费)
- VS Code + Oracle 插件
PL/SQL 程序由匿名块或命名块(过程、函数)组成。
[DECLARE -- 声明部分:变量、常量、游标等 ] BEGIN -- 执行部分:SQL 和 PL/SQL 语句 [EXCEPTION -- 异常处理部分 ] END; /⚠️ 注意:匿名块必须以
/结尾(在 SQL*Plus 中)才能执行。
-- 输出 "Hello, PL/SQL!" BEGIN DBMS_OUTPUT.PUT_LINE('Hello, PL/SQL!'); END; /
DBMS_OUTPUT.PUT_LINE是 Oracle 内置过程,用于向控制台输出信息。
-- 单行注释 /* 多行注释 可跨越多行 */_、美元符$、井号#✅ 推荐命名规范:
v_employee_idc_max_salarycur_emp'包围'It''s OK'DECLARE v_msg VARCHAR2(100) := 'He said: ''Hello!'''; BEGIN DBMS_OUTPUT.PUT_LINE(v_msg); -- 输出:He said: 'Hello!' END; /| 类型 | 说明 | 示例 |
|---|---|---|
NUMBER(p,s) | 数值 | NUMBER(10,2) |
VARCHAR2(n) | 可变长字符串 | VARCHAR2(50) |
CHAR(n) | 定长字符串(空格填充) | CHAR(10) |
DATE | 日期时间 | SYSDATE |
BOOLEAN | 布尔值(仅 PL/SQL 内部使用) | TRUE/FALSE |
| 类型 | 说明 |
|---|---|
%TYPE | 引用表列的数据类型 |
%ROWTYPE | 引用整行记录的结构 |
RECORD | 自定义复合类型 |
TABLE | PL/SQL 表(类似数组) |
DECLARE -- 基本变量 v_emp_id NUMBER(4) := 7369; v_name VARCHAR2(20); v_hire_date DATE := SYSDATE; -- 使用 %TYPE(推荐!避免硬编码类型) v_salary emp.sal%TYPE; -- 自动匹配 emp 表 sal 列类型 -- 常量(必须初始化,且不能修改) c_pi CONSTANT NUMBER := 3.14159; c_company CONSTANT VARCHAR2(20) := 'Oracle Corp'; -- 布尔变量 v_is_manager BOOLEAN := FALSE; BEGIN -- 从表中查询赋值 SELECT ename, sal INTO v_name, v_salary FROM emp WHERE empno = v_emp_id; DBMS_OUTPUT.PUT_LINE('Employee: ' || v_name || ', Salary: ' || v_salary); END; /✅ 优势:使用
%TYPE可使代码随表结构自动适应,提高可维护性。
支持算术、比较、逻辑、字符串连接等操作。
DECLARE v_a NUMBER := 10; v_b NUMBER := 3; v_result NUMBER; BEGIN v_result := v_a + v_b * 2; -- 16 v_result := MOD(v_a, v_b); -- 1 v_result := POWER(v_a, 2); -- 100 IF v_a > v_b AND v_b != 0 THEN DBMS_OUTPUT.PUT_LINE('Valid calculation'); END IF; END; /IF condition THEN statements; END IF;IF salary > 5000 THEN bonus := 1000; ELSE bonus := 500; END IF;DECLARE v_grade CHAR(1) := 'B'; BEGIN IF v_grade = 'A' THEN DBMS_OUTPUT.PUT_LINE('Excellent'); ELSIF v_grade = 'B' THEN DBMS_OUTPUT.PUT_LINE('Good'); ELSIF v_grade = 'C' THEN DBMS_OUTPUT.PUT_LINE('Fair'); ELSE DBMS_OUTPUT.PUT_LINE('Unknown grade'); END IF; END; /CASE v_deptno WHEN 10 THEN DBMS_OUTPUT.PUT_LINE('Accounting'); WHEN 20 THEN DBMS_OUTPUT.PUT_LINE('Research'); WHEN 30 THEN DBMS_OUTPUT.PUT_LINE('Sales'); ELSE DBMS_OUTPUT.PUT_LINE('Other Dept'); END CASE;DECLARE i NUMBER := 1; BEGIN LOOP DBMS_OUTPUT.PUT_LINE('Count: ' || i); i := i + 1; EXIT WHEN i > 5; END LOOP; END; /DECLARE i NUMBER := 1; BEGIN WHILE i <= 5 LOOP DBMS_OUTPUT.PUT_LINE('While: ' || i); i := i + 1; END LOOP; END; /BEGIN FOR i IN 1..5 LOOP DBMS_OUTPUT.PUT_LINE('For loop: ' || i); END LOOP; -- 反向循环 FOR i IN REVERSE 1..3 LOOP DBMS_OUTPUT.PUT_LINE('Reverse: ' || i); END LOOP; END; /游标用于处理多行查询结果。
DECLARE -- 1. 声明游标 CURSOR cur_emp IS SELECT empno, ename, sal FROM emp WHERE deptno = 20; -- 声明记录变量 v_emp_rec cur_emp%ROWTYPE; BEGIN -- 2. 打开游标 OPEN cur_emp; -- 3. 循环获取数据 LOOP FETCH cur_emp INTO v_emp_rec; EXIT WHEN cur_emp%NOTFOUND; -- 无更多数据时退出 DBMS_OUTPUT.PUT_LINE( 'ID: ' || v_emp_rec.empno || ', Name: ' || v_emp_rec.ename || ', Salary: ' || v_emp_rec.sal ); END LOOP; -- 4. 关闭游标 CLOSE cur_emp; END; /Oracle 自动维护,可通过属性访问:
| 属性 | 说明 |
|---|---|
SQL%FOUND | DML 影响至少一行? |
SQL%NOTFOUND | 未影响任何行? |
SQL%ROWCOUNT | 影响的行数 |
SQL%ISOPEN | 总为 FALSE(隐式游标自动关闭) |
BEGIN UPDATE emp SET sal = sal * 1.1 WHERE deptno = 50; IF SQL%FOUND THEN DBMS_OUTPUT.PUT_LINE('Updated ' || SQL%ROWCOUNT || ' rows.'); ELSE DBMS_OUTPUT.PUT_LINE('No employees in dept 50.'); END IF; END; /| 游标类型 | %FOUND | %NOTFOUND | %ROWCOUNT | %ISOPEN |
|---|---|---|---|---|
| 显式 | 是 | 是 | 是 | 是 |
| 隐式 | 是 | 是 | 是 | 否(始终 FALSE) |
用于动态 SQL 或跨过程传递结果集(高级用法,略)。
DECLARE CURSOR cur_high_sal IS SELECT ename, sal FROM emp WHERE sal > 2500; BEGIN -- 自动 OPEN, FETCH, CLOSE FOR rec IN cur_high_sal LOOP DBMS_OUTPUT.PUT_LINE(rec.ename || ' earns $' || rec.sal); END LOOP; END; /✅ 优点:代码简洁,不易出错。
BEGIN -- 可能出错的代码 EXCEPTION WHEN exception_name THEN -- 处理特定异常 WHEN OTHERS THEN -- 处理所有其他异常 END; /| 异常名 | 触发条件 |
|---|---|
NO_DATA_FOUND | SELECT INTO 未返回行 |
TOO_MANY_ROWS | SELECT INTO 返回多行 |
ZERO_DIVIDE | 除零错误 |
DUP_VAL_ON_INDEX | 唯一约束冲突 |
INVALID_NUMBER | 字符串转数字失败 |
DECLARE v_ename emp.ename%TYPE; BEGIN SELECT ename INTO v_ename FROM emp WHERE empno = 9999; -- 不存在的员工 EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('Error: Employee not found!'); WHEN TOO_MANY_ROWS THEN DBMS_OUTPUT.PUT_LINE('Error: Multiple employees found!'); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Unexpected error: ' || SQLERRM); END; /
SQLERRM返回错误消息文本。
DECLARE -- 声明自定义异常 e_invalid_salary EXCEPTION; v_sal emp.sal%TYPE := 100; BEGIN IF v_sal < 500 THEN -- 抛出自定义异常 RAISE e_invalid_salary; END IF; EXCEPTION WHEN e_invalid_salary THEN DBMS_OUTPUT.PUT_LINE('Error: Salary too low!'); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('System error: ' || SQLERRM); END; /也可使用
RAISE_APPLICATION_ERROR(-20001, '自定义消息')抛出带编号的错误。
需求:
CREATETABLEemp_salary_log(log_id NUMBER GENERATED ALWAYSASIDENTITY,empno NUMBER(4),old_sal NUMBER(7,2),new_sal NUMBER(7,2),update_timeDATEDEFAULTSYSDATE);DECLARE -- 自定义异常 e_salary_too_high EXCEPTION; PRAGMA EXCEPTION_INIT(e_salary_too_high, -20001); -- 游标:每个部门最低工资员工 CURSOR cur_min_sal_dept IS SELECT e.empno, e.ename, e.sal, e.deptno, d.dname FROM emp e JOIN dept d ON e.deptno = d.deptno WHERE e.sal = ( SELECT MIN(sal) FROM emp e2 WHERE e2.deptno = e.deptno ); v_new_sal emp.sal%TYPE; v_max_allowed CONSTANT NUMBER := 10000; BEGIN DBMS_OUTPUT.PUT_LINE('Starting salary adjustment...'); FOR rec IN cur_min_sal_dept LOOP BEGIN v_new_sal := rec.sal * 1.1; -- 检查是否超过上限 IF v_new_sal > v_max_allowed THEN RAISE e_salary_too_high; END IF; -- 更新工资 UPDATE emp SET sal = v_new_sal WHERE empno = rec.empno; -- 记录日志 INSERT INTO emp_salary_log (empno, old_sal, new_sal) VALUES (rec.empno, rec.sal, v_new_sal); DBMS_OUTPUT.PUT_LINE( 'Adjusted: ' || rec.ename || ' (Dept: ' || rec.dname || ') from ' || rec.sal || ' to ' || v_new_sal ); EXCEPTION WHEN e_salary_too_high THEN DBMS_OUTPUT.PUT_LINE( 'Warning: ' || rec.ename || '''s new salary exceeds limit. Skipped.' ); WHEN DUP_VAL_ON_INDEX THEN DBMS_OUTPUT.PUT_LINE('Log entry already exists for ' || rec.empno); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Error for ' || rec.ename || ': ' || SQLERRM); END; END LOOP; COMMIT; -- 提交事务 DBMS_OUTPUT.PUT_LINE('Adjustment completed.'); EXCEPTION WHEN OTHERS THEN ROLLBACK; DBMS_OUTPUT.PUT_LINE('Transaction rolled back due to error: ' || SQLERRM); END; /Starting salary adjustment... Adjusted: SMITH (Dept: RESEARCH) from 800 to 880 Adjusted: JAMES (Dept: SALES) from 950 to 1045 Adjusted: MILLER (Dept: ACCOUNTING) from 1300 to 1430 Adjustment completed.| 模块 | 核心知识点 |
|---|---|
| 块结构 | DECLARE/BEGIN/EXCEPTION/END |
| 变量/常量 | %TYPE,%ROWTYPE, CONSTANT |
| 控制结构 | IF, CASE, LOOP, FOR |
| 游标 | 显式/隐式,FOR 循环游标 |
| 异常处理 | 预定义异常、自定义异常、SQLERRM |
| 最佳实践 | 使用绑定变量、避免硬编码、合理使用 COMMIT/ROLLBACK |
💡学习建议:
- 从简单匿名块开始练习
- 多使用
DBMS_OUTPUT.PUT_LINE调试- 优先使用
FOR循环游标- 异常处理要覆盖
OTHERS
掌握 PL/SQL 是成为 Oracle 开发者或 DBA 的关键一步。后续可深入学习存储过程、函数、包、触发器等高级特性。