PL/SQL 编程游标存储过程函数
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PL/SQL 编程游标存储过程函数相关的知识,希望对你有一定的参考价值。
游标--数据的缓存区
游标:类似集合,可以让用户像操作数组一样操作查询出来的数据集,实质上,它提供了一种从集合性质的结果中提取单条记录的手段。
可以将游标形象的看成一个变动的光标,他实质上是一个指针,在一段Oracle存放数据查询结果集或者数据操作结果集的内存中,这个指针可以指向结果集任何一条记录。
游标分静态游标和REF游标两类,静态游标包含显式游标和隐式游标。
显式游标:
在使用之前必须有明确的游标声明和定义,这样的游标定义会关联数据查询语句,通常会返回一行或多行。打开游标后,用户可以利用游标的位置对结果集进行检索,使之返回单一的行记录,用户可以操作此记录。
显式游标需要用户自己写代码完成,一切由用户控制。
显式游标处理需四个 PL/SQL步骤:
l 定义/声明游标:就是定义一个游标名,以及与其相对应的SELECT 语句。
游标参数只能为输入参数。
在指定数据类型时,不能使用长度约束。如NUMBER(4),CHAR(10) 等都是错误的。
l 打开游标:就是执行游标所对应的SELECT 语句,将其查询结果放入工作区,并且指针指向工作区的首部,标识游标结果集合。如果游标查询语句中带有FOR UPDATE选项,OPEN 语句还将锁定数据库表中游标结果集合对应的数据行。
在向游标传递参数时,可以使用与函数参数相同的传值方法,即位置表示法和名称表示法。PL/SQL 程序不能用OPEN 语句重复打开一个游标。
l 提取游标数据:就是检索结果集合中的数据行,放入指定的输出变量中。
执行FETCH语句时,每次返回一个数据行,然后自动将游标移动指向下一个数据行。当检索到最后一行数据时,如果再次执行FETCH语句,将操作失败,并将游标属性%NOTFOUND置为TRUE。所以每次执行完FETCH语句后,检查游标属性%NOTFOUND就可以判断FETCH语句是否执行成功并返回一个数据行,以便确定是否给对应的变量赋了值。
l 对该记录进行处理;
l 继续处理,直到活动集合中没有记录;
l 关闭游标:当提取和处理完游标结果集合数据后,应及时关闭游标,以释放该游标所占用的系统资源,并使该游标的工作区变成无效,不能再使用FETCH 语句取其中数据。关闭后的游标可以使用OPEN 语句重新打开。
注意:定义的游标不能有INTO 子句。
--查询前10名员工的信息。 DECLARE CURSOR c_cursor IS SELECT first_name || last_name, Salary FROM EMPLOYEES WHERE rownum<11; v_ename EMPLOYEES.first_name%TYPE; v_sal EMPLOYEES.Salary%TYPE; BEGIN OPEN c_cursor; FETCH c_cursor INTO v_ename, v_sal; WHILE c_cursor%FOUND LOOP DBMS_OUTPUT.PUT_LINE(v_ename||‘---‘||to_char(v_sal) ); FETCH c_cursor INTO v_ename, v_sal; END LOOP; CLOSE c_cursor; END;
--游标参数的传递方法。 DECLARE DeptRec DEPARTMENTS%ROWTYPE; Dept_name DEPARTMENTS.DEPARTMENT_NAME%TYPE; Dept_loc DEPARTMENTS.LOCATION_ID%TYPE; CURSOR c1 IS SELECT DEPARTMENT_NAME, LOCATION_ID FROM DEPARTMENTS WHERE DEPARTMENT_ID <= 30; CURSOR c2(dept_no NUMBER DEFAULT 10) IS SELECT DEPARTMENT_NAME, LOCATION_ID FROM DEPARTMENTS WHERE DEPARTMENT_ID <= dept_no; CURSOR c3(dept_no NUMBER DEFAULT 10) IS SELECT * FROM DEPARTMENTS WHERE DEPARTMENTS.DEPARTMENT_ID <=dept_no; BEGIN OPEN c1; LOOP FETCH c1 INTO dept_name, dept_loc; EXIT WHEN c1%NOTFOUND; DBMS_OUTPUT.PUT_LINE(dept_name||‘---‘||dept_loc); END LOOP; CLOSE c1; OPEN c2; LOOP FETCH c2 INTO dept_name, dept_loc; EXIT WHEN c2%NOTFOUND; DBMS_OUTPUT.PUT_LINE(dept_name||‘---‘||dept_loc); END LOOP; CLOSE c2; OPEN c3(dept_no =>20); LOOP FETCH c3 INTO deptrec; EXIT WHEN c3%NOTFOUND; DBMS_OUTPUT.PUT_LINE(deptrec.DEPARTMENT_ID||‘---‘||deptrec.DEPARTMENT_NAME||‘---‘||deptrec.LOCATION_ID); END LOOP; CLOSE c3; END;
--给工资低于1200 的员工增加工资50。 DECLARE v_empno EMPLOYEES.EMPLOYEE_ID%TYPE; v_sal EMPLOYEES.Salary%TYPE; CURSOR c_cursor IS SELECT EMPLOYEE_ID, Salary FROM EMPLOYEES; BEGIN OPEN c_cursor; LOOP FETCH c_cursor INTO v_empno, v_sal; EXIT WHEN c_cursor%NOTFOUND; IF v_sal<=1200 THEN UPDATE EMPLOYEES SET Salary=Salary+50 WHERE EMPLOYEE_ID=v_empno; DBMS_OUTPUT.PUT_LINE(‘编码为‘||v_empno||‘工资已更新!‘); END IF; DBMS_OUTPUT.PUT_LINE(‘记录数:‘|| c_cursor %ROWCOUNT); END LOOP; CLOSE c_cursor; END;
--没有参数且没有返回值的游标。 DECLARE v_f_name employees.first_name%TYPE; v_j_id employees.job_id%TYPE; CURSOR c1 --声明游标,没有参数没有返回值 IS SELECT first_name, job_id FROM employees WHERE department_id = 20; BEGIN OPEN c1; --打开游标 LOOP FETCH c1 INTO v_f_name, v_j_id; --提取游标 IF c1%FOUND THEN DBMS_OUTPUT.PUT_LINE(v_f_name||‘的岗位是‘||v_j_id); ELSE DBMS_OUTPUT.PUT_LINE(‘已经处理完结果集了‘); EXIT; END IF; END LOOP; CLOSE c1; --关闭游标 END;
--有参数且没有返回值的游标。 DECLARE v_f_name employees.first_name%TYPE; v_h_date employees.hire_date%TYPE; CURSOR c2(dept_id NUMBER, j_id VARCHAR2) --声明游标,有参数没有返回值 IS SELECT first_name, hire_date FROM employees WHERE department_id = dept_id AND job_id = j_id; BEGIN OPEN c2(90, ‘AD_VP‘); --打开游标,传递参数值 LOOP FETCH c2 INTO v_f_name, v_h_date; --提取游标 IF c2%FOUND THEN DBMS_OUTPUT.PUT_LINE(v_f_name||‘的雇佣日期是‘||v_h_date); ELSE DBMS_OUTPUT.PUT_LINE(‘已经处理完结果集了‘); EXIT; END IF; END LOOP; CLOSE c2; --关闭游标 END;
--有参数且有返回值的游标。 DECLARE TYPE emp_record_type IS RECORD( f_name employees.first_name%TYPE, h_date employees.hire_date%TYPE); v_emp_record EMP_RECORD_TYPE; CURSOR c3(dept_id NUMBER, j_id VARCHAR2) --声明游标,有参数有返回值 RETURN EMP_RECORD_TYPE IS SELECT first_name, hire_date FROM employees WHERE department_id = dept_id AND job_id = j_id; BEGIN OPEN c3(j_id => ‘AD_VP‘, dept_id => 90); --打开游标,传递参数值 LOOP FETCH c3 INTO v_emp_record; --提取游标 IF c3%FOUND THEN DBMS_OUTPUT.PUT_LINE(v_emp_record.f_name||‘的雇佣日期是‘ ||v_emp_record.h_date); ELSE DBMS_OUTPUT.PUT_LINE(‘已经处理完结果集了‘); EXIT; END IF; END LOOP; CLOSE c3; --关闭游标 END;
--基于游标定义记录变量。 DECLARE CURSOR c4(dept_id NUMBER, j_id VARCHAR2) --声明游标,有参数没有返回值 IS SELECT first_name f_name, hire_date FROM employees WHERE department_id = dept_id AND job_id = j_id; --基于游标定义记录变量,比声明记录类型变量要方便,不容易出错 v_emp_record c4%ROWTYPE; BEGIN OPEN c4(90, ‘AD_VP‘); --打开游标,传递参数值 LOOP FETCH c4 INTO v_emp_record; --提取游标 IF c4%FOUND THEN DBMS_OUTPUT.PUT_LINE(v_emp_record.f_name||‘的雇佣日期是‘ ||v_emp_record.hire_date); ELSE DBMS_OUTPUT.PUT_LINE(‘已经处理完结果集了‘); EXIT; END IF; END LOOP; CLOSE c4; --关闭游标 END;
隐式游标:
被plsql自动管理,也被称为sql游标,
用户无法控制,但能得到他的属性信息。
对于非查询语句,如修改、删除操作,由ORACLE 系统自动地为这些操作设置游标并创建其工作区,这些由系统隐含创建的游标称为隐式游标,隐式游标的名字为SQL,这是由ORACLE 系统定义的。对于隐式游标的操作,如定义、打开、取值及关闭操作,都由ORACLE 系统自动地完成,无需用户进行处理。用户只能通过隐式游标的相关属性,来完成相应的操作。在隐式游标的工作区中,所存放的数据是与用户自定义的显示游标无关的、最新处理的一条SQL 语句所包含的数据。
格式调用为: SQL%
注:INSERT, UPDATE, DELETE, SELECT 语句中不必明确定义游标。
--删除EMPLOYEES表中某部门的所有员工,如果该部门中已没有员工,则在DEPARTMENT表中删除该部门。 DECLARE V_deptno department_id%TYPE :=&p_deptno; BEGIN DELETE FROM employees WHERE department_id=v_deptno; IF SQL%NOTFOUND THEN DELETE FROM departments WHERE department_id=v_deptno; END IF; END;
--通过隐式游标SQL的%ROWCOUNT属性来了解修改了多少行。 DECLARE v_rows NUMBER; BEGIN --更新数据 UPDATE employees SET salary = 30000 WHERE department_id = 90 AND job_id = ‘AD_VP‘; --获取默认游标的属性值 v_rows := SQL%ROWCOUNT; DBMS_OUTPUT.PUT_LINE(‘更新了‘||v_rows||‘个雇员的工资‘); --回退更新,以便使数据库的数据保持原样 ROLLBACK; END;
存储过程
存储过程就是一段存储在数据库中执行某种功能的程序。简单来时是存储在数据库服务器中的封装了一段或多段sql语句的plsql代码块。存储过程可以在编程语言中调用,如Java等。
存储过程的优点:
简化复杂的操作,封装。
增加数据独立性,利用存储过程可以把数据库基础数据和程序或用户隔离开来。
提高安全性。
提高性能。
有参存储过程:
存储过程允许带有参数,过程有输入,输出,输入输出三种参数。
输入 :in 默认,可省略
输出 :out
--输出参数 CREATE OR REPLACE PROCEDURE HANQI2(SCLA IN NUMBER, vari OUT number) AS BEGIN UPDATE STUDENT S SET S.SSEX = ‘女‘ WHERE S.CLASS = SCLA; SELECT COUNT(*) INTO vari FROM student s WHERE s.class=scla; DBMS_OUTPUT.PUT_LINE(‘记录已经修改 !‘); END;
--用户连接登记记录; CREATE TABLE logtable (userid VARCHAR2(10), logdate date); CREATE OR REPLACE PROCEDURE logexecution IS BEGIN INSERT INTO logtable (userid, logdate) VALUES (USER, SYSDATE); END;
--删除指定员工记录; CREATE OR REPLACE PROCEDURE DelEmp (v_empno IN employees.employee_id%TYPE) AS No_result EXCEPTION; BEGIN DELETE FROM employees WHERE employee_id = v_empno; IF SQL%NOTFOUND THEN RAISE no_result; END IF; DBMS_OUTPUT.PUT_LINE(‘编码为‘||v_empno||‘的员工已被删除!‘); EXCEPTION WHEN no_result THEN DBMS_OUTPUT.PUT_LINE(‘温馨提示:你需要的数据不存在!‘); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(SQLCODE||‘---‘||SQLERRM); END DelEmp;
--插入员工记录: CREATE OR REPLACE PROCEDURE InsertEmp( v_empno in employees.employee_id%TYPE, v_firstname in employees.first_name%TYPE, v_lastname in employees.last_name%TYPE, v_deptno in employees.department_id%TYPE ) AS empno_remaining EXCEPTION; PRAGMA EXCEPTION_INIT(empno_remaining, -1); /* -1 是违反唯一约束条件的错误代码 */ BEGIN INSERT INTO EMPLOYEES(EMPLOYEE_ID, FIRST_NAME, LAST_NAME, HIRE_DATE,DEPARTMENT_ID) VALUES(v_empno, v_firstname,v_lastname, sysdate, v_deptno); DBMS_OUTPUT.PUT_LINE(‘温馨提示:插入数据记录成功!‘); EXCEPTION WHEN empno_remaining THEN DBMS_OUTPUT.PUT_LINE(‘温馨提示:违反数据完整性约束!‘); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(SQLCODE||‘---‘||SQLERRM); END InsertEmp;
--使用存储过程向departments表中插入数据。 CREATE OR REPLACE PROCEDURE insert_dept (v_dept_id IN departments.department_id%TYPE, v_dept_name IN departments.department_name%TYPE, v_mgr_id IN departments.manager_id%TYPE, v_loc_id IN departments.location_id%TYPE) IS ept_null_error EXCEPTION; PRAGMA EXCEPTION_INIT(ept_null_error, -1400); ept_no_loc_id EXCEPTION; PRAGMA EXCEPTION_INIT(ept_no_loc_id, -2291); BEGIN INSERT INTO departments (department_id, department_name, manager_id, location_id) VALUES (v_dept_id, v_dept_name, v_mgr_id, v_loc_id); DBMS_OUTPUT.PUT_LINE(‘插入部门‘||v_dept_id||‘成功‘); EXCEPTION WHEN DUP_VAL_ON_INDEX THEN RAISE_APPLICATION_ERROR(-20000, ‘部门编码不能重复‘); WHEN ept_null_error THEN RAISE_APPLICATION_ERROR(-20001, ‘部门编码、部门名称不能为空‘); WHEN ept_no_loc_id THEN RAISE_APPLICATION_ERROR(-20002, ‘没有该地点‘); END insert_dept;
--调用实例一: DECLARE ept_20000 EXCEPTION; PRAGMA EXCEPTION_INIT(ept_20000, -20000); ept_20001 EXCEPTION; PRAGMA EXCEPTION_INIT(ept_20001, -20001); ept_20002 EXCEPTION; PRAGMA EXCEPTION_INIT(ept_20002, -20002); BEGIN insert_dept(300, ‘部门300‘, 100, 2400); insert_dept(310, NULL, 100, 2400); insert_dept(310, ‘部门310‘, 100, 900); EXCEPTION WHEN ept_20000 THEN DBMS_OUTPUT.PUT_LINE(‘ept_20000部门编码不能重复‘); WHEN ept_20001 THEN DBMS_OUTPUT.PUT_LINE(‘ept_20001部门编码、部门名称不能为空‘); WHEN ept_20002 THEN DBMS_OUTPUT.PUT_LINE(‘ept_20002没有该地点‘); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(‘others出现了其他异常错误‘); END; --调用实例二: DECLARE ept_20000 EXCEPTION; PRAGMA EXCEPTION_INIT(ept_20000, -20000); ept_20001 EXCEPTION; PRAGMA EXCEPTION_INIT(ept_20001, -20001); ept_20002 EXCEPTION; PRAGMA EXCEPTION_INIT(ept_20002, -20002); BEGIN insert_dept(v_dept_name => ‘部门310‘, v_dept_id => 310, v_mgr_id => 100, v_loc_id => 2400); insert_dept(320, ‘部门320‘, v_mgr_id => 100, v_loc_id => 900); EXCEPTION WHEN ept_20000 THEN DBMS_OUTPUT.PUT_LINE(‘ept_20000部门编码不能重复‘); WHEN ept_20001 THEN DBMS_OUTPUT.PUT_LINE(‘ept_20001部门编码、部门名称不能为空‘); WHEN ept_20002 THEN DBMS_OUTPUT.PUT_LINE(‘ept_20002没有该地点‘); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(‘others出现了其他异常错误‘); END;
函数
函数主要组成部分:
输入部分。输入参数,调用函数时给这些参数赋值。
逻辑计算部分。函数内部将完成对各种数据项目的计算。
输出部分。函数必须有返回值。
--输入参数,计算和 CREATE OR REPLACE FUNCTION CAL_ADD(A IN NUMBER, B IN NUMBER) RETURN NUMBER; AS C NUMBER; BEGIN C := A + B; RETURN C; END;
--获取某部门的工资总和 CREATE OR REPLACE FUNCTION get_salary( Dept_no NUMBER, Emp_count OUT NUMBER) RETURN NUMBER IS V_sum NUMBER; BEGIN SELECT SUM(SALARY), count(*) INTO V_sum, emp_count FROM EMPLOYEES WHERE DEPARTMENT_ID=dept_no; RETURN v_sum; EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE(‘你需要的数据不存在!‘); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(SQLCODE||‘---‘||SQLERRM); END get_salary;
用程序在调用函数时,可以使用以下三种方法向函数传递参数:
第一种参数传递格式:位置表示法。
即在调用时按形参的排列顺序,依次写出实参的名称,而将形参与实参关联起来进行传递。用这种方法进行调用,形参与实参的名称是相互独立,没有关系,强调次序才是重要的。
--计算某部门的工资总和: DECLARE V_num NUMBER; V_sum NUMBER; BEGIN V_sum :=get_salary(10, v_num); DBMS_OUTPUT.PUT_LINE(‘部门号为:10的工资总和:‘||v_sum||‘,人数为:‘||v_num); END;
第二种参数传递格式:名称表示法。
即在调用时按形参的名称与实参的名称,写出实参对应的形参,而将形参与实参关联起来进行传递。这种方法,形参与实参的名称是相互独立的,没有关系,名称的对应关系才是最重要的,次序并不重要。
--计算某部门的工资总和: DECLARE V_num NUMBER; V_sum NUMBER; BEGIN V_sum :=get_salary(emp_count => v_num, dept_no => 10); DBMS_OUTPUT.PUT_LINE(‘部门号为:10的工资总和:‘||v_sum||‘,人数为:‘||v_num); END;
第三种参数传递格式:组合传递。
即在调用一个函数时,同时使用位置表示法和名称表示法为函数传递参数。采用这种参数传递方法时,使用位置表示法所传递的参数必须放在名称表示法所传递的参数前面。也就是说,无论函数具有多少个参数,只要其中有一个参数使用名称表示法,其后所有的参数都必须使用名称表示法。
CREATE OR REPLACE FUNCTION demo_fun( Name VARCHAR2,--注意VARCHAR2不能给精度,如:VARCHAR2(10),其它类似 Age INTEGER, Sex VARCHAR2) RETURN VARCHAR2 AS V_var VARCHAR2(32); BEGIN V_var := name||‘:‘||TO_CHAR(age)||‘岁.‘||sex; RETURN v_var; END; DECLARE Var VARCHAR(32); BEGIN Var := demo_fun(‘user1‘, 30, sex => ‘男‘); DBMS_OUTPUT.PUT_LINE(var); Var := demo_fun(‘user2‘, age => 40, sex => ‘男‘); DBMS_OUTPUT.PUT_LINE(var); Var := demo_fun(‘user3‘, sex => ‘女‘, age => 20); DBMS_OUTPUT.PUT_LINE(var); END;
无论采用哪一种参数传递方法,实际参数和形式参数之间的数据传递只有两种方法:传址法和传值法。
传址法:指在调用函数时,将实际参数的地址指针传递给形式参数,使形式参数和实际参数指向内存中的同一区域,从而实现参数数据的传递。这种方法又称作参照法,即形式参数参照实际参数数据。输入参数均采用传址法传递数据。
传值法:指将实际参数的数据拷贝到形式参数,而不是传递实际参数的地址。默认时,输出参数和输入/输出参数均采用传值法。在函数调用时,ORACLE将实际参数数据拷贝到输入/输出参数,而当函数正常运行退出时,又将输出形式参数和输入/输出形式参数数据拷贝到实际参数变量中。
注意:在CREATE OR REPLACE FUNCTION 语句中声明函数参数时可以使用DEFAULT关键字为输入参数指定默认值。
CREATE OR REPLACE FUNCTION demo_fun( Name VARCHAR2, Age INTEGER, Sex VARCHAR2 DEFAULT ‘男‘) RETURN VARCHAR2 AS V_var VARCHAR2(32); BEGIN V_var := name||‘:‘||TO_CHAR(age)||‘岁.‘||sex; RETURN v_var; END;
具有默认值的函数创建后,在函数调用时,如果没有为具有默认值的参数提供实际参数值,函数将使用该参数的默认值。但当调用者为默认参数提供实际参数时,函数将使用实际参数值。在创建函数时,只能为输入参数设置默认值,而不能为输入/输出参数设置默认值。
DECLARE var VARCHAR(32); BEGIN Var := demo_fun(‘user1‘, 30); DBMS_OUTPUT.PUT_LINE(var); Var := demo_fun(‘user2‘, age => 40); DBMS_OUTPUT.PUT_LINE(var); Var := demo_fun(‘user3‘, sex => ‘女‘, age => 20); DBMS_OUTPUT.PUT_LINE(var); END;
以上是关于PL/SQL 编程游标存储过程函数的主要内容,如果未能解决你的问题,请参考以下文章