Oracle数据库数据编程SQL<3.4 PL/SQL 自定义函数(Function)>

发布于:2025-04-01 ⋅ 阅读:(16) ⋅ 点赞:(0)

自定义函数是Oracle数据库中可重用的PL/SQL代码块,它接受参数、执行操作并返回一个值。函数可以在SQL语句中直接调用,极大增强了SQL的处理能力。

分类 调用
没有参数 没有输入输出 写SQL不加参数
有参数 有输入无输出 写SQL加参数
无输入有输出 输出变量 写PL/SQL匿名块或者自定义函数、存储过程调用
输出动态游标
有输入有输出 写PL/SQL匿名块或者自定义函数、存储过程调用

目录

一、函数基本结构

1. 创建语法:必须有返回值

2. 基本示例

二、函数参数模式

三、无参数:没有输入输出

四、有参数:只输入IN--用select调用

五、有参数:有输出OUT--plsql调用

1、输出变量

2、输出游标

六、有参数:有输入输出值IN OUT--必须用宏代换&

七、函数返回值

1. 返回标量值

2. 返回复合类型

(1)使用%ROWTYPE

(2)使用自定义类型

八、函数中的DML操作

示例:审计函数

九、确定性函数与并行处理

1. 确定性函数(DETERMINISTIC)

2. 并行处理(PARALLEL_ENABLE)

十、管道表函数(PIPELINED)

1. 基本管道函数

2. 带参数的管道函数

十一、函数的调用方式

1. 在PL/SQL中调用

2. 在SQL语句中调用

3. 在DML语句中调用

十二、函数的管理与维护

1. 查看函数定义

2. 重新编译函数

3. 删除函数

4. 函数权限控制

十三、函数设计最佳实践

1. 单一职责原则:

2. 合理命名:

3. 参数设计:

4. 错误处理:

5. 性能考虑:

6. 文档注释:

十四、高级函数特性(了解)

1. 函数重载(在包中)

2. 递归函数

3. 使用上下文函数(12c+)


一、函数基本结构

1. 创建语法:必须有返回值

【语法】关键词【function,return】
CREATE [OR REPLACE] FUNCTION function_name
    [(parameter1 [IN|OUT|IN OUT] datatype [DEFAULT|:= value],
     parameter2 [IN|OUT|IN OUT] datatype [DEFAULT|:= value],
     ...)]
RETURN return_datatype
[IS|AS]
    [declaration_section]
BEGIN
    executable_section
    [RETURN value;]
[EXCEPTION
    exception_section]
END [function_name];


create {or replace} function fun_name
(v1 in|out|in out 数据类型{无长度}/*可以加多各参数*/)
return 类型{无长度}
as|is 
  {声明变量/声明游标}---如果没有变量{类型长度}声明,也可以不用声明
begin
  要执行的语句;
  [return 值|游标;]
  [exception]
  [return 值|游标;]
end;
/

2. 基本示例

CREATE OR REPLACE FUNCTION get_employee_name(
    p_emp_id IN employees.employee_id%TYPE
) RETURN VARCHAR2
IS
    v_full_name VARCHAR2(100);
BEGIN
    SELECT first_name || ' ' || last_name INTO v_full_name
    FROM employees
    WHERE employee_id = p_emp_id;
    
    RETURN v_full_name;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RETURN '员工不存在';
END get_employee_name;
/

二、函数参数模式

模式 描述 示例 调用
IN 只读参数(默认)

输入值{可以不用写}

p_id IN NUMBER

sql调用

OUT 只写参数

输出值/*必须写*/

p_result OUT VARCHAR2

plsql调用

IN OUT 可读写参数

既输入也输出

p_counter IN OUT NUMBER

plsql调用

sys_refcursor

动态游标

dbms_output.put_line

输出

三、无参数:没有输入输出

--创建函数
create or replace function π return number as
begin 
 return ACOS(-1);
end;

--使用函数,求半径为1的圆的面积
select π*1*1 from dual

四、有参数:只输入IN--用select调用

【举例】
--手动制作一个函数sign()
--创建
 create or replace function mysign(v1 in number) return number as
 begin
   if v1 > 0 then
     return 1;/*这里的意思是输出一个表*/
   elsif v1 < 0 then
     return - 1;
   else
     return 0;
   end if;
 end;
/
--调用
select mysign(23333333) from dual--只输入用select
/*############################################################################*/

【例题】
/*=====================================================================================*/
【1】制作一个函数,输入一个数值,返回它的相反数
--创建
CREATE OR REPLACE FUNCTION FUN_XFS(V1 NUMBER) RETURN NUMBER AS
BEGIN
  IF V1 > 0 THEN
    RETURN '-' || V1;
  ELSIF V1 < 0 THEN
    RETURN ABS(V1);
  ELSE
    RETURN 0;
  END IF;
END;
/
--引用
SELECT fun_xfs(0) FROM dual

--调用
CREATE OR REPLACE FUNCTION FUN_XFS1(V1 NUMBER) RETURN NUMBER AS
BEGIN
    RETURN(-V1);
END;
/
--调用
SELECT fun_xfs1(-1) FROM dual
/*=====================================================================================*/
【2】编写一个函数,输入一个员工编号,返回其工资等级
--创建
create or replace function 工资等级1(v1 number) return number as
  v_g salgrade.grade%type;
begin
  select b.grade
    into v_g
    from emp a, salgrade b
   where a.empno = v1
     and a.sal between b.losal and b.hisal;
  return v_g;
end;
/
--调用
select 工资等级1(7788) from dual
----------------------------------------------
--创建
create or replace function 工资等级2(v1 number) return number as
  v_g salgrade.grade%type;
begin
  select b.grade
    into v_g
    from emp a
   inner join salgrade b
      on a.sal between b.losal and b.hisal
   where a.empno = v1;
  return v_g;
end;
/
--调用
select 工资等级2(7788) from dual
select 查询工资等级(7369) from dual--有输入,
没有 dnms_output.put_line()输出,用select调用
 
/*=====================================================================================*/
【3】输入一个日期,返回这个月有多少个周一
--创建
create or replace function fun_3(v1 date) return number as
  v2 number;
begin
  select count(trunc(v1, 'mm') - 1 + level)
    into v2
    from dual
   where to_char((trunc(v1, 'mm') - 1 + level), 'd') = 2 
   connect by level <= to_char(last_day(v1), 'dd');
  return v2;
end;----有输入,没有 dnms_output.put_line()输出,用select调用
/
--调用
select fun_3(date'2019-4-1') from dual
--------------------------------------------------------------------
--创建
create or replace function 某月有多少周一(v1 date) return number as
  v_count number;
begin
  select count(mm)
    into v_count
    from (select level + (trunc(v1, 'mm') - 1) mm
            from dual
          connect by level <= to_char(last_day(v1), 'dd'))
   where to_char(mm, 'd') = 2;
  return v_count;
end;
/
--调用
select 某月有多少周一(date'2019-01-02') from dual

/*=====================================================================================*/
【4】输入一个部门编号和日期,返回这个部门在这个日期之后入职的员工人数
--创建
CREATE OR REPLACE FUNCTION FUN_4(V1 NUMBER, V2 DATE) RETURN NUMBER AS
  V3 NUMBER;
BEGIN
  SELECT COUNT(*)
    INTO V3
    FROM EMP
   WHERE DEPTNO = V1
     AND HIREDATE > V2;
  RETURN V3;
END;
/
--调用
SELECT fun_4(10,DATE'1981-12-01') FROM dual

五、有参数:有输出OUT--plsql调用

1、输出变量

【举例】
CREATE OR REPLACE FUNCTION calculate_tax(
    p_salary IN NUMBER,
    p_tax_rate OUT NUMBER
) RETURN NUMBER
IS
    v_tax_amount NUMBER;
BEGIN
    IF p_salary <= 5000 THEN
        p_tax_rate := 0;
    ELSIF p_salary <= 10000 THEN
        p_tax_rate := 0.1;
    ELSE
        p_tax_rate := 0.2;
    END IF;
    
    v_tax_amount := p_salary * p_tax_rate;
    RETURN v_tax_amount;
END calculate_tax;
/

-- 调用示例
DECLARE
    v_rate NUMBER;
    v_tax NUMBER;
BEGIN
    v_tax := calculate_tax(15000, v_rate);
    DBMS_OUTPUT.PUT_LINE('税率: ' || v_rate || ', 税额: ' || v_tax);
END;
/

【练习】
/*=====================================================================================*/
【1】输入一个部门编号获取部门的工资总和和总人数
-- 创建
create or replace function fun3(v1 number, v_sum out number) 
return number as
  v_count number;
begin
  select sum(sal), count(*) into v_sum, v_count from emp where deptno = v1;
  return v_count;
end;
-- 调用
declare
  v_sum   number;
  v_count number;
begin
  v_count := fun3(10, v_sum);--给返回值赋值:=fun_name(参数,参数)
  dbms_output.put_line('总工资:' || v_sum || ' 总人数:' || v_count);
end;
/


/*=====================================================================================*/
【2】输入一个员工编号,输出它的月薪和职位
-- 创建
 create or replace function 查询月薪和职位(v1 number, v_s out number)
   return varchar2 as
   v_j varchar2(20);
 begin
   select e.sal,e.job into v_s,v_j from emp e where e.empno = v1;
   return v_j;
 end;
-- 调用
 declare
 v_s number;
 v_j varchar2(20);
 begin
   v_j:=查询月薪和职位(&a,v_s);
   dbms_output.put_line('月薪'||v_s||'职位'||v_j);
end;
/

/*=====================================================================================*/
【2】输入一个工资等级,输出这个等级的最低工资,最高工资
CREATE OR REPLACE FUNCTION FUN_5(V1 NUMBER, V2 OUT NUMBER) RETURN NUMBER AS
  V3 NUMBER;
BEGIN
  SELECT LOSAL, HISAL INTO V2, V3 FROM SALGRADE WHERE GRADE = V1;
  RETURN V3;
END;
/
-- 调用示例
DECLARE
  V1 NUMBER := &A;
  V2 NUMBER;
  V3 NUMBER;
BEGIN
  V3 := FUN_5(V1, V2);
  DBMS_OUTPUT.PUT_LINE('最低工资' || V2 || CHR(10) || '最高工资' || V3);
END;
/

2、输出游标

-------------------------
(一)【返回一个动态游标】
-------------------------
【1】输入一个部门编号,输入部门员工信息,
--创建
create or replace function fun4(v1 number) return sys_refcursor as
  cur_emp sys_refcursor;
begin
  open cur_emp for
    select * from emp where deptno =v1;
  return cur_emp;
end;
--调用                                     
declare
  cur_a sys_refcursor;
  v_emp emp%rowtype;
begin
  cur_a := fun4(&a);
  loop
    fetch cur_a
      into v_emp;
    exit when cur_a%notfound;
    dbms_output.put_line(v_emp.empno || v_emp.ename);
  end loop;
end;

/*#####################################################################################*/
-------------------------------
(二)【输出和返回都是动态游标】
-------------------------------
【1】输入一个部门编号,输出这个部门的员工信息和部门信息
--创建
create or replace function fun5(v1 number, cur_dept out sys_refcursor)--输出是动态游标
  return sys_refcursor as--返回是动态游标
  cur_emp sys_refcursor;
begin
  open cur_emp for
    select * from emp where deptno = v1;
  open cur_dept for
    select * from dept where deptno = v1;
  return cur_emp;
end;
--调用
declare
  cur_emp  sys_refcursor;
  cur_dept sys_refcursor;
  v_emp    emp%rowtype;
  v_dept   dept %rowtype;
begin
  cur_emp := fun5(20, cur_dept);
  loop
    fetch cur_emp
      into v_emp;
    exit when cur_emp%notfound;
    dbms_output.put_line(v_emp.empno || v_emp.ename || v_emp.deptno);
  end loop;
  loop
    fetch cur_dept into v_dept;
    exit when cur_dept%notfound;
    dbms_output.put_line(v_dept.deptno || v_dept.dname);
  end loop;
end;

【2】输入一个经理编号,输出他所有的下级姓名和员工编号和他自己的员工编号、入职日期
--创建
create or replace function fun6(v1 number, cur_1 out sys_refcursor)
  return sys_refcursor as
  cur_2 sys_refcursor;
begin
  open cur_1 for
    select empno, ename from emp where mgr = v1;
  open cur_2 for
    select empno, hiredate from emp where empno = v1;
  return cur_2;
end;
--调用
declare
  cur_1 sys_refcursor;
  cur_2 sys_refcursor;
  v1    emp.empno%type;
  v2    varchar2(20);
  v3    number;
  v4    date;
begin
  cur_2 := fun6(&a, cur_1);
  loop
    fetch cur_1
      into v1, v2;
    exit when cur_1%notfound;
    dbms_output.put_line('下级编号:' || v1 || '下级姓名:' || v2);
  end loop;
  loop
    fetch cur_2
      into v3, v4;
    exit when cur_2%notfound;
    dbms_output.put_line('经理编号:' || v3 || '入职日期:' || v4);
  end loop;
end;

六、有参数:有输入输出值IN OUT--必须用宏代换&

--创建
create or replace function fun7(v_empno number, v_jia in out number)
  return varchar2 as
  v_name emp.ename%type;
begin
  update emp001
     set sal = sal + v_jia
   where empno = v_empno
  returning ename, sal into v_name, v_jia;
  return v_name;
end;
--调用
declare
  v_empno emp.empno%type := &empno;---必须用宏代换
  v_name  emp.ename%type;
  v_jia   emp.sal%type := &jia;
begin
  v_name := fun7(v_empno, v_jia);
  dbms_output.put_line('姓名:' || v_name || '新工资:' || v_jia);
end;

七、函数返回值

1. 返回标量值

CREATE OR REPLACE FUNCTION get_department_name(
    p_dept_id IN departments.department_id%TYPE
) RETURN VARCHAR2
IS
    v_dept_name departments.department_name%TYPE;
BEGIN
    SELECT department_name INTO v_dept_name
    FROM departments
    WHERE department_id = p_dept_id;
    
    RETURN v_dept_name;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RETURN '部门不存在';
END get_department_name;
/

2. 返回复合类型

(1)使用%ROWTYPE

CREATE OR REPLACE FUNCTION get_employee_info(
    p_emp_id IN employees.employee_id%TYPE
) RETURN employees%ROWTYPE
IS
    v_emp employees%ROWTYPE;
BEGIN
    SELECT * INTO v_emp
    FROM employees
    WHERE employee_id = p_emp_id;
    
    RETURN v_emp;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RETURN NULL;
END get_employee_info;
/

(2)使用自定义类型

-- 先创建对象类型
CREATE OR REPLACE TYPE emp_obj AS OBJECT (
    emp_id NUMBER,
    emp_name VARCHAR2(100),
    salary NUMBER,
    hire_date DATE
);
/

-- 创建返回对象的函数
CREATE OR REPLACE FUNCTION get_employee_obj(
    p_emp_id IN NUMBER
) RETURN emp_obj
IS
    v_emp emp_obj;
BEGIN
    SELECT emp_obj(
        employee_id, 
        first_name || ' ' || last_name,
        salary,
        hire_date
    ) INTO v_emp
    FROM employees
    WHERE employee_id = p_emp_id;
    
    RETURN v_emp;
END get_employee_obj;
/

八、函数中的DML操作

函数可以包含DML操作,但有以下限制:

  • 不能执行事务控制语句(COMMIT/ROLLBACK)

  • 调用时可能需要特殊权限

示例:审计函数

CREATE OR REPLACE FUNCTION update_salary_with_log(
    p_emp_id IN employees.employee_id%TYPE,
    p_new_salary IN employees.salary%TYPE
) RETURN VARCHAR2
IS
    v_old_salary employees.salary%TYPE;
    v_result VARCHAR2(100);
BEGIN
    -- 获取旧薪资
    SELECT salary INTO v_old_salary
    FROM employees
    WHERE employee_id = p_emp_id;
    
    -- 更新薪资
    UPDATE employees
    SET salary = p_new_salary
    WHERE employee_id = p_emp_id;
    
    -- 记录审计日志
    INSERT INTO salary_audit (
        audit_id, employee_id, 
        old_salary, new_salary, 
        change_date
    ) VALUES (
        audit_seq.NEXTVAL, p_emp_id,
        v_old_salary, p_new_salary,
        SYSDATE
    );
    
    v_result := '薪资从 ' || v_old_salary || ' 更新为 ' || p_new_salary;
    RETURN v_result;
EXCEPTION
    WHEN OTHERS THEN
        RETURN '错误: ' || SQLERRM;
END update_salary_with_log;
/

九、确定性函数与并行处理

1. 确定性函数(DETERMINISTIC)

对于相同输入总是返回相同结果的函数可以声明为DETERMINISTIC,提高性能:

CREATE OR REPLACE FUNCTION calculate_bonus(
    p_salary IN NUMBER,
    p_years IN NUMBER
) RETURN NUMBER DETERMINISTIC
IS
BEGIN
    RETURN p_salary * p_years * 0.01;
END calculate_bonus;
/

2. 并行处理(PARALLEL_ENABLE)

CREATE OR REPLACE FUNCTION process_large_data(
    p_input IN NUMBER
) RETURN NUMBER PARALLEL_ENABLE
IS
BEGIN
    -- 复杂计算
    RETURN p_input * 2;
END process_large_data;
/

十、管道表函数(PIPELINED)

返回结果集的函数,可以逐行返回数据:

1. 基本管道函数

-- 创建集合类型
CREATE OR REPLACE TYPE emp_table_type AS TABLE OF emp_obj;
/

-- 创建管道函数
CREATE OR REPLACE FUNCTION get_dept_employees(
    p_dept_id IN NUMBER
) RETURN emp_table_type PIPELINED
IS
BEGIN
    FOR emp_rec IN (
        SELECT employee_id, first_name, last_name, salary, hire_date
        FROM employees
        WHERE department_id = p_dept_id
    ) LOOP
        PIPE ROW(emp_obj(
            emp_rec.employee_id,
            emp_rec.first_name || ' ' || emp_rec.last_name,
            emp_rec.salary,
            emp_rec.hire_date
        ));
    END LOOP;
    
    RETURN;
END get_dept_employees;
/

-- 调用示例
SELECT * FROM TABLE(get_dept_employees(10));

2. 带参数的管道函数

CREATE OR REPLACE FUNCTION filter_employees(
    p_min_sal IN NUMBER DEFAULT 0,
    p_max_sal IN NUMBER DEFAULT 999999
) RETURN emp_table_type PIPELINED
IS
BEGIN
    FOR emp_rec IN (
        SELECT employee_id, first_name, last_name, salary, hire_date
        FROM employees
        WHERE salary BETWEEN p_min_sal AND p_max_sal
    ) LOOP
        PIPE ROW(emp_obj(
            emp_rec.employee_id,
            emp_rec.first_name || ' ' || emp_rec.last_name,
            emp_rec.salary,
            emp_rec.hire_date
        ));
    END LOOP;
    
    RETURN;
END filter_employees;
/

-- 调用示例
SELECT * FROM TABLE(filter_employees(5000, 10000));

十一、函数的调用方式

1. 在PL/SQL中调用

DECLARE
    v_name VARCHAR2(100);
    v_tax NUMBER;
BEGIN
    v_name := get_employee_name(100);
    v_tax := calculate_tax(8000);
    
    DBMS_OUTPUT.PUT_LINE(v_name || '的税额: ' || v_tax);
END;
/

2. 在SQL语句中调用

-- 在SELECT中使用
SELECT employee_id, 
       get_employee_name(employee_id) AS full_name,
       calculate_bonus(salary, 5) AS bonus
FROM employees
WHERE department_id = 10;

-- 在WHERE条件中使用
SELECT * FROM employees
WHERE calculate_tax(salary) > 1000;

-- 在ORDER BY中使用
SELECT * FROM employees
ORDER BY calculate_bonus(salary, years_of_service) DESC;

3. 在DML语句中调用

-- 在INSERT中使用
INSERT INTO employee_audit
VALUES (audit_seq.NEXTVAL, 
        get_employee_name(100),
        SYSDATE);

-- 在UPDATE中使用
UPDATE employees
SET salary = salary + calculate_bonus(salary, years_of_service)
WHERE employee_id = 100;

十二、函数的管理与维护

1. 查看函数定义

-- 查看源代码
SELECT text FROM user_source 
WHERE name = 'GET_EMPLOYEE_NAME' 
ORDER BY line;

-- 查看函数状态
SELECT object_name, status 
FROM user_objects 
WHERE object_type = 'FUNCTION';

2. 重新编译函数

ALTER FUNCTION function_name COMPILE;

3. 删除函数

DROP FUNCTION function_name;

4. 函数权限控制

-- 授予执行权限
GRANT EXECUTE ON get_employee_name TO user1;

-- 创建公有同义词
CREATE PUBLIC SYNONYM get_emp_name FOR hr.get_employee_name;

十三、函数设计最佳实践

1. 单一职责原则

  • 每个函数只完成一个明确的任务

2. 合理命名

  • 使用动词+名词形式,如calculate_taxget_employee_name

3. 参数设计:

  • 限制参数数量(通常不超过5个)
  • 使用默认参数减少重载
  • 避免使用OUT参数(影响可读性)

4. 错误处理

  • 捕获并处理预期异常
  • 对意外异常提供有意义的错误信息

5. 性能考虑

  • 对频繁调用的函数使用DETERMINISTIC
  • 避免在函数中执行复杂SQL
  • 考虑使用管道函数处理大数据集

6. 文档注释

CREATE OR REPLACE FUNCTION calculate_tax(
    p_salary IN NUMBER  -- 输入:员工薪资
) RETURN NUMBER         -- 返回:计算出的税额
IS
/*
* 功能: 根据薪资计算应缴税额
* 逻辑: 
*   - 5000以下: 免税
*   - 5000-10000: 10%
*   - 10000以上: 20%
* 创建: 2023-01-01
* 作者: DBA
*/
BEGIN
    -- 函数实现
END;
/

十四、高级函数特性(了解)

1. 函数重载(在包中)

关于包的内容会在后面详细讲到

CREATE OR REPLACE PACKAGE employee_pkg AS
    -- 根据ID获取员工名
    FUNCTION get_name(p_emp_id IN NUMBER) RETURN VARCHAR2;
    
    -- 根据邮箱获取员工名
    FUNCTION get_name(p_email IN VARCHAR2) RETURN VARCHAR2;
END employee_pkg;
/

CREATE OR REPLACE PACKAGE BODY employee_pkg AS
    FUNCTION get_name(p_emp_id IN NUMBER) RETURN VARCHAR2 IS
        v_name VARCHAR2(100);
    BEGIN
        SELECT first_name || ' ' || last_name INTO v_name
        FROM employees
        WHERE employee_id = p_emp_id;
        RETURN v_name;
    END;
    
    FUNCTION get_name(p_email IN VARCHAR2) RETURN VARCHAR2 IS
        v_name VARCHAR2(100);
    BEGIN
        SELECT first_name || ' ' || last_name INTO v_name
        FROM employees
        WHERE email = p_email;
        RETURN v_name;
    END;
END employee_pkg;
/

2. 递归函数

CREATE OR REPLACE FUNCTION factorial(
    p_num IN NUMBER
) RETURN NUMBER
IS
BEGIN
    IF p_num <= 1 THEN
        RETURN 1;
    ELSE
        RETURN p_num * factorial(p_num - 1);
    END IF;
END factorial;
/

-- 调用示例
SELECT factorial(5) FROM dual;  -- 返回120

3. 使用上下文函数(12c+)

CREATE OR REPLACE FUNCTION get_session_user
RETURN VARCHAR2
ACCESSED UNDER CURRENT_USER
IS
BEGIN
    RETURN SYS_CONTEXT('USERENV', 'SESSION_USER');
END;
/

自定义函数是Oracle PL/SQL编程的核心组件,合理设计和使用函数可以显著提高代码的可重用性、可维护性和性能。


网站公告

今日签到

点亮在社区的每一天
去签到