Oracle数据库支持创建和使用子程序。创建子程序有很多优点:
- 便于维护
- 提高数据处理的安全性和一致性
- 提高数据处理性能
- 增强代码的清晰透明性
Oracle支持的子程序有两类:
- 存储过程 (procedure)
- 函数(function)
在PL/SQL中创建和使用procedure
定义
存储过程是一种执行动作的子程序,可以保存为一种模式对象。创建存储过程提升了代码的可充用和可维护性。
语法
CREATE [OR REPLACE] PROCEDURE procedure_name
[(parameter1 [mode] datatype1,
parameter2 [mode] datatype2, ...)]
IS|AS
[local1_variable_declarations;...]
BEGIN
-- actions;
END [procedure_name];
开发Procedure
参数
参数定义于子程序的头部,在调用者和子程序之间传递数据。有三种类型:
- IN parameter:默认类型,表示传入参数
- OUT parameter:传出参数
- IN OUT:传入传出参数
CREATE PROCEDURE procedure(param [mode] datatype)
Formal 和 Actual 参数
- Formal parameter
在子程序定义部分声明的一种本地变量
CREATE PROCEDURE raise_sal(id NUMBER, sal NUMBER) IS
BEGIN ...
END raise_sal;
- Actual parameters
可以是具体的值也可以是主程序的变量
emp_id := 100;
raise_sal(emp_id, 2000)
使用IN Parameter 举例
CREATE OR REPLACE PROCEDURE raise_salary (id IN employees.employee_id%TYPE, percent IN NUMBER)
IS
BEGIN
UPDATE employees
SET salary = salary * (1 + percent/100)
WHERE employee_id = id;
END raise_salary;
/
EXECUTE raise_salary(176, 10)
使用OUT Parameter 举例
CREATE OR REPLACE PROCEDURE query_emp
(id IN employees.employee_id%TYPE,
name OUT employees.last_name%TYPE,
salary OUT employees.salary%TYPE)
BEGIN
SELECT last_name, salary INTO name, salary
FROM employees
WHERE employee_id = id;
END query_emp;
OUT Parameters 的值
可以用 DBMS_OUTPUT.PUT_LINE 打印变量。
SET SERVEROUTPUT ON
DECLARE
emp_name employees.last_name%TYPE;
emp_sal employees.salary%TYPE;
BEGIN
query_emp(171, emp_name, emp_sal);
DBMS_OUTPUT.PUT_LINE('Name: ' || emp_name);
DBMS_OUTPUT.PUT_LINE('Salary: ' || emp_sal);
END;
也可以用SQL * PLUS host variables 打印变量
VARIABLE name VARCHAR2(25)
VARIABLE sal NUMBER
EXECUTE query_emp(171, :name, :sal)
PRINT name sal
使用 Host Variables
Host Variable 又称“bind”或者“global”变量。定义于PL/SQL之外,可以通过下列方式定义:
- SQL * Plus VARIABLE 命令
- Oracle Forms internal 和 UI variables
- Java variables
引用host variables需要在变量名称前加上“:”。Host Variable 可以在匿名块引用但是不能在过程内部引用,也可以作为参数传入传出过程。
使用IN OUT Parameter 举例
调用环境:
phone_no
# 在调用之前为‘8006330575’
# 在调用之前为‘(800)633-0575’
CREATE OR REPLACE PROCEDURE format_phone
(phone_no IN OUT VARCHAR2) IS
BEGIN
phone_no := '(' || SUBSTR(phone_no, 1, 3) ||
')' || SUBSTR(phone_no, 4, 4) ||
'-' || SUBSTR(phone_no, 7);
END format_phone;
/
传递参数的方式
- 位置传参(Positional)
将 Actual 参数按照 Formal 参数的顺序列出。 - 名称参数(Named)
Actual 参数可以以任意顺序列出,用 association 操作符(=>)将 Formal 参数和相关的 Actual 参数联系起来。 - 复合模式(Combination)
一些参数以位置方式传入,其他的以名称方式传入。
传参举例
CREATE OR REPLACE PROCEDURE add_dept(
name IN departments.department_name%TYPE,
loc IN departments.location_id%TYPE) IS
BEGIN
INSERT INTO departments(department_id, departmemt_name, location_id)
VALUES (departments_seq.NEXTVAL, name, loc);
END add_dept;
/
EXECUTE add_dept('TRAINING', 2500)
EXECUTE add_dept(loc=>2400, name=>'EDUCATION')
传参时使用 DEFAULT 选项
CREATE OR REPLACE PROCEDURE add_dept(
name IN departments.department_name%TYPE := 'Unknown',
loc IN departments.location_id%TYPE DEFAULT 1700)
IS
BEGIN
INSERT INTO departments(department_id, departmemt_name, location_id)
VALUES (departments_seq.NEXTVAL, name, loc);
END add_dept;
/
有了default,传参方式更加灵活
EXECUTE add_dept
EXECUTE add_dept('ADVERTISING', loc => 1200)
EXECUTE add_dept(loc => 1200)
传参模式总结
IN | OUT | IN OUT |
---|---|---|
Default 模式 | 必须指定 | 必须指定 |
值被传入子程序 | 返回值到调用子程序的环境 | 被传入子程序,返回值到调用子程序的环境 |
Formal 参数作为一个常量(constant) | 没有被初始化的Variable | 初始化的Variable |
Actual 参数可以是一个literal,表达式,常量,或者被初始化的Variable | 必须是一个Variable | 必须是一个Variable |
可以被赋Default值 | 不能被赋Default值 | 不能被赋Default值 |
调用Procedures
可以在匿名块中调用存储过程,也可以在其他存储过程中调用存储过程。
CREATE OR REPLACE PROCEDURE process_employees
IS
CURSOR emp_cursor IS
SELECT employee_id
FROM employees;
BEGIN
FOR emp_rec IN emp_cursor
LOOP
raise_salary(emp_rec.employee_id, 10);
END LOOP;
COMMIT;
END process_employees;
/
异常处理
- 异常处理举例
CREATE PROCEDURE add_department(
name VARCHAR2, mgr NUMBER, loc NUMBER) IS
BEGIN
INSERT INTO DEPARTMENTS (department_id, department_name, manager_id, location_id)
VALUES (DEPARTMENTS_SEQ.NEXTVAL, name, mgr, loc);
DBMS_OUTPUT.PUT_LINE('Added Dept: '|| name);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Err: adding dept: '|| name);
END;
/
CREATE PROCEDURE create_departments IS
BEGIN
add_department('Media', 100, 1800); # ok
add_department('Editing', 99, 1800); # error
add_department('Advertising', 101, 1800); #ok
END;
删除Procedures
DROP PROCEDURE procedure_name;
通过数据字典查看 procedure
可以通过两种方式在字典中查看存储过程:
- 通过USER_SOURCE看代码
SELECT text
FROM user_source
WHERE name = 'ADD_DEPARTMENT' and type = 'PROCEDURE'
ORDER BY line;
- 通过USER_OBJECTS看名称和创建时间
SELECT object_name
FROM user_objects
WHERE object_type = 'PROCEDURE';
在PL/SQL中创建和使用function
创建Functions的语法
创建Functions必须要有RETURN statement:
CREATE [OR REPLACE] FUNCTION function_name
[(parameter1 [mode1] datetype1, ...)]
RETURN datatype IS|AS
[local_variable_declarations; ...]
BEGIN
-- actions;
RETURN expression;
END [function_name];
Stored Function 举例
- 创建函数
CREATE OR REPLACE FUNCTION get_sal(
id employees.employee_id%TYPE)
RETURN NUMBER IS
sal employees.salary%TYPE := 0;
BEGIN
SELECT sal
INTO sal
FROM employees
WHERE employee_id = id;
RETURN sal;
END get_sal;
/
EXECUTE dbms_output.put_line(get_sal(100))
函数的执行方法
作为PL/SQL 表达式的一部分
- 用 host variable 来获得结果
VARIABLE salary NUMBER
EXECUTE :salary := get_sal(100)
- 使用local variable 来获得结果
DECLARE sal employees.salary%TYPE;
BEGIN
sal := get_sal(100);...
END;
作为参数传入另一个子程序
EXECUTE dbms_output.put_line(get_sal(100))
在SQL语句中使用(有一些限制)
SELECT job_id, get_sal(employee_id) FROM employees;
在SQL中使用用户自定义函数的好处
在SQL中使用用户自定义函数有很多好处:
- 当活动过于复杂的时候,能扩展SQL
- 较之于在应用中filter数据,用自定义函数在WHERE子句filter数据更有效率
- 能操作数据的值
在SQL中使用函数举例
CREATE OR REPLACE FUNCTION tax(value IN NUMBER)
RETURN NUMBER IS
BEGIN
RETRUN (value * 0.08);
END tax;
/
SELECT employee_id, last_name, salary, tax(salary)
FROM employees
WHERE department_id = 100;
哪些子句中可以调用函数
用户自定义函数和Oracle预定义函数可用于:
- 查询中SELECT的列表或者SELECT子句
- WHERE子句和HAVING子句中的条件表达式
- 查询中的子句:CONNECT BY,START WITH,ORDER BY和GROUP BY
- INSERT语句中的VALUES子句
- UPDATE语句的SET子句
通过SQL调用函数的限制
通过SQL表达式调用函数的限制:
- 函数必须创建函数对象
- 仅可接受IN参数而且只能是SQL data types,而非
PL/SQL-specific类型
- 返回SQL数据类型,而非PL/SQL-specific类型
通过SQL语句中调用函数的限制 - 必须以位置模式传递参数
- 调用者或是函数属主或有执行权限
函数如果通过 - SELECT 或并行UPDATE,DELETE语句调用,则函数不能包含DML
- 针对表T的UPDATE或DELETE语句调用,则函数不能包括任何针对表T的任何SELECT和DML
- SQL语句调用,则函数不可结束事务(that is, cannot execute COMMIT or ROLLBACK operations),不可定义或使用savepoint,不可alter system或session。
调用函数限制举例
CREATE OR REPLACE FUNCTION dml_call_sql(sal NUMBER)
RETURN NUMBER IS
BEGIN
INSERT INTO employees(employee_id, last_name, email, hire_date, job_id, salary)
VALUES(1, 'Frost', '[email protected]', SYSDATE, 'SA_MAN', sal);
RETRUN (sal + 100)
END;
以下语句会抛出异常:
UPDATE employees
SET salary = dml_call_sql(2000)
WHERE employee_id = 170;
# ORA-04091...
# ORA-06512...
删除Functions
删除一个已经保存的函数
DROP FUNCTION function_name
- 删除函数之后,所有赋予函数的权限将被收回。
-
CREATE OR REPLAE
等同于删除函数,然后重建该函数,函数上的权限不变。
通过数据字典查询函数信息
- 通过USER_SOURCE可查看函数代码
SELECT text
FROM user_source
WHERE type = 'FUNCTION'
ORDER BY line;
- 通过USER_OBJECT可查询名称等信息
SELECT object_name
FROM user_objects
WHERE object_type = 'FUNCTION';
存储过程和函数的对比
存储过程 | 函数 |
---|---|
作为PL/SQL语句执行 | 作为一个表达式执行 |
在头部不能包含RETURN语句 | 在头部必须包含RETURN语句 |
可以通过output参数返回值 | 必须返回一个single值 |
可以有一个不包括值的返回语句 | 必须至少包含一个RETURN语句 |