存储过程练手(入门)
以下存储过程基于oracle,主要包含了存储过程中的一些常用基本语句的语法。
1、无参存储过程
CREATE OR REPLACE PROCEDURE PRO_1
AS
BEGIN -- 执行
DBMS.OUTPUT.PUT_LINE('无参构造过程');
EXCEOPTION -- 异常
WHEN OTHERS THEN
ROLLBACK;
END;
-- 调用(方式一)
BEGIN
PRO_1();
END;
-- 调用(方式二)
BEGIN
CALL PRO_1();
END;
DROP PROCEDURE PRO_1;
2、带参存储过程
CREATE OR REPLACE PROCEDURE PRO_2( -- 定义参数,使用该存储过程时需要传参
VAL IN NUMBER,
SNAME OUT VARCHAR,
DT_CODE IN OUT VARCHAR)
AS ICOUNT NUMBER; --声明该存储过程中需要用到的变量。
BEGIN
SELECT COUNT(*) INTO ICOUNT FROM D_DEPT WHERE DT_ID>VAL;
IF ICOUNT=1 THEN
SNAME:='MLB事业部1'||VAL;
DT_CODE:='MLB1'||DT_CODE;
ELSE
SNAME:='MLB事业部2'||VAL;
DT_NAME='MLB2'||DT_CODE;
END IF;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('返回值多于一行');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('在PRO_PAR过程中出错!')
END;
--调用
declare
REVAL NUMBER;
RENAME varchar(40);
RECODE varchar(40);
BEGIN
REVAL:='20230822';
RENAME:='';
RECODE:='JUNIOR';
-- 指定值对应变量顺序可变(关系箭头方向是形参指向实参)
PRO_2(SNAME=>RENAME,VAL=>REVAL,DT_CODE=>RECODE);
DBMS_OUTPUT_PUTLINE(RENAME||' '||RECODE);
END;
3、带if,else,elseif存储过程
CREATE OR REPLACE PROCEDURE PRO_3(Y IN NUMBER)
AS
BEGIN
IF Y=2020 THEN
DBMS_OUTPUT.PUT_LINE('YEAR OF 2020');
ELSIF Y=2021 THEN
DBMS_OUTPUT.PUT_LINE('YEAR OF 2021');
ELSE
DBMS_OUTPUT.PUTLINE('UNKNOWN YEAR');
END IF;
END;
-- 调用
BEGIN
PRO_3(Y=>2021);
END;
4、带while循环的存储过程
CREATE OR REPLACE PRO_4(I IN NUMBER)
AS
J NUMBER;
BEGIN
J:=1;
WHILE J<=I LOOP
DBMS_OUTPUT.PUTLINE('J='||J);
J:=j+1;
END LOOP;
END;
BEGIN
PRO_4(I=>100);
END;
5、带select into的存储过程
存储过程中在不使用cursor的前提下,使用SELECT语句时必须带着INTO(SELECT … INTO … FROM … )
CREATE OR REPLACE PROCEDURE PRO_5(ID IN NUMBER)
AS
M VARCHAR(50);
G VARCHAR(50);
BEGIN
-- 在利用SELECT…INTO…语法时,必须先确保数据库中有该条记录,否则会报出"NO_DATA_FOUND"异常。
-- 可先利用SELECT COUNT(*) FROM 查看数据库中是否存在该记录,存在则使用SELECT…INTO。
-- 在存储过程中,别名不能和字段名称相同,否则虽然编译可以通过,但在运行阶段会报错。
SELECT MONTH,ORG_OID INTO M,G FROM D_DEPT WHERE DT_ID=ID;
DBMS_OUTPUT.PUTLINE('M='||M||'G'||G);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUTLINE('数据不存在');
END;
-- 调用
BEGIN
PRO_5(ID=>21);
END;
6、带for存储过程
CREATE OR REPLACE PROCEDURE PRO_6
AS
BEGIN
FOR D IN (SELECT * FROM D_LESSON) LOOP
IF(D.LN_ID>10) THEN
DBMS_OUTPUT.PUTLINE(D.LN_ID);
END IF;
END LOOP;
COMMIT;
END;
-- 调用
BEGIN
CALL PRO_6();
END;
7、带有execute immediate(动态SQL)
CREATE OR REPLACE PROCEDURE PRO_7(V_EMPNO IN NUMBER)
AS
V_ENAME VARCHAR(20);
V_SAL NUMBER(6);
v_STR VARCHAR(100);
BEGIN
V_STR:='SELECT ENAME,SAL FROM EMP WHERE EMPNO=:1'; -- 占位符:1相当于函数的形式参数,使用:作为前缀,然后使用using将V_EMPNO在运行时替换:1
EXECUTE IMMEDIATE V_STR -- 执行动态sql语句
INTO V_ENAME,V_SAL -- 将查询出的结果传给变量(输出参数列表)
USING V_EMPNO; -- 往SQL语句中传入的参数(绑定参数列表)
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUTLINE('没有此员工');
END;
8、带有游标的存储过程
游标四大属性
属性 | 返回类型 | 作用 |
---|---|---|
sql%isopen | 布尔型 | 判断游标是否开启 |
sql%found | 布尔型 | 判断游标是否获取到值 |
sql%notfound | 布尔型 | 判断游标是否没有获取到值(常用于退出循环) |
sql%rowcount | 整型 | 当前成功执行的数据行数(是已执行的行数,而不是总行数) |
显示游标
游标分显式和隐式的,在使用DML操作和单行SELECT操作时会自动使用隐式游标,在此不做说明,细说显示游标。
声明游标
声明游标有两种方式:
方式一:在声明变量的位置声明游标
CREATE OR REPLACE PROCEDURE PRO_8
AS
-- 1、声明游标
CURSOR cursor_stu_info -- 游标名
IS select * from stu_info; -- 游标要执行的sql语句
v_stu_info stu_info%ROWTYPE; -- 游标存储结构名 数据类型(表名%ROWTYPE -》表示该存储结构和数据表表相同)
BEGIN
-- 2、开启游标
OPEN cursor_stu_info;
-- 3、提取(每次获取一行)
FETCH cursor_stu_info -- 取出游标中的一行数据
INTO v_stu_info; -- 将这行数据放入游标的存储结构中
-- 输出结果
dbms_output.putline(v_stu_info.id||':'||v_stu_info.name);
-- 4、关闭游标
close cursor_stu_info;
END PRO_8;
方式二:在BEGIN和END语句块内声明游标
CREATE OR REPLACE PROCEDURE PRO_8
AS
BEGIN
DECLARE
-- 1、声明游标
CURSOR cursor_stu_info
IS select * from stu_info;
v_stu_info stu_info%ROWTYPE;
BEGIN --在BEGIN和END语句块汇内声明游标时必须再写一个BEGIN和END语句块。
-- 2、开启游标
OPEN cursor_stu_info;
-- 3、提取数据(每次获取一行)
FETCH cursor_stu_info
INTO v_stu_info;
-- 输出结果
dbms_output.putline(v_stu_info.id||':'||v_stu_info.name);
-- 4、关闭游标
CLOSE cursor_stu_info;
END;
END PRO_8;
遍历游标
declare
-- 1、声明游标
CURSOR cursor_stu_info
is
SELECT * from stu_info;
v_stu_info stu_info%ROWTYPE ;
BEGIN
-- 2、开启游标
OPEN cursor_stu_info;
-- 3、提取游标(每次获取一行)
FETCH cursor_stu_info
INTO v_stu_info;
-- 输出结果
dbms_output.putline(v_stu_info.id||':'||v_stu_info.name);
-- 4、关闭游标
CLOSE cursor_stu_info;
END;
while循环遍历
上述例子只能显示出一条结果,如果要得到所有结果,则需要循环。
声明接收数据的结果集类型的方法有两种
方式一:
-- 检索EMP表中的所有JOB为MANAGER的员工信息
DECLARE
-- 声明游标
CURSOR CUR_EMP(VAR_JOB IN VARCHAR(20))
-- 此游标所使用的查询语句
IS SELECT EMPNO,ENAME,SAL FROM EMP WHERE JOB = VAR_JOB;
-- 声明一个RECORD类型的记录变量,用于接收查询出的数据集
TYPE RECORD_EMP IS RECORD(
VAR_EMPNO EMP.EMPNO%TYPE,
VAR_ENAME EMP.ENAME%TYPE,
VAR_SAL EMP.SAL%TYPE);
EMP_ROW RECORD_EMP; -- record类型的变量名为EMP_ROW
BEGIN
-- 开启游标
OPEN CUR_EMP('MANAGER');
-- 首先需要将游标指向查询结果集中的第一个数据
FETCH CUR_EMP
INTO EMP_ROW;
-- 如果结果集中有多条数据,则需要循环
-- 先判断目前游标是否获取到值
WHILE CUR_EMP%FOUND LOOP
DBMS_OUTPUT.PUTLINE('编号:'||EMP_ROW.VAR_EMPNO||',姓名'||CUR_ROW.VAR_ENAME||',薪水'||EMP_ROW.VAR_SAL);
-- 输出完一条数据以后立即让游标指向下一条数据
FETCH CUR_EMP
INTO EMP_ROW;
END LOOP;
-- 关闭游标
CLOSE CUR_EMP;
END;
方式二:
-- 检索EMP表中的所有JOB为MANAGER的员工信息
DECLARE
CURSOR CUR_EMP(VAR_JOB IN VARCHAR(20))IS
-- 使用FETCH+%ROWTYPE查询,这里的查询字段必须和表中字段顺序及数量一致
SELECT EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO FROM EMP;
VAR_EMP_TYPE EMP%ROWTYPE;
BEGIN
OPEN CUR_EMP;
FETCH CUR_EMP
INTO VAR_EMP_TYPE;
WHILE CUR_EMP%FOUND LOOP
DBMS_OUTPUT.PUT_LINE('编号:'||VAR_EMP_TYPE.EMPNO||',姓名'||VAR_EMP_TYPE.ENAME||',薪水'||VAR_EMP_TYPE.SAL);
FETCH CUR_EMP
INTO VAR_EMP_TYPE;
END LOOP;
CLOSE CUR_EMP;
END;
for循环遍历游标
上述是使用while循环实现结果集遍历的,不过我们也可以使用for循环遍历,并且有以下优点:
- 自动打开、关闭游标
- 当执行完最后一条数据时,自动跳出循环,无需写exit
-- 检索EMP表中的所有JOB为MANAGER的员工信息
DECLARE
CURSOR CUR_EMP(VAR_JOB IN VARCHAR(20)) IS
-- 使用FETCH+%ROWTYPE查询,这里的查询字段必须和表中字段顺序及数量一致
SELECT EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO FROM EMP;
VAR_EMP_TYPE EMP%ROWTYPE;
BEGIN
-- 每次从CUR_EMP中取出一个结果VAR_EMP_TYPE
FOR VAR_EMP_TYPE IN CUR_EMP LOOP
DBMS_OUTPUT.PUT_LINE('编号:'||VAR_EMP_TYPE.EMPNO||',姓名'||VAR_EMP_TYPE.ENAME||',薪水'||VAR_EMP_TYPE.SAL);
END LOOP;
END;
fetch循环遍历
-- 检索EMP表中的所有JOB为MANAGER的员工信息
DECLARE
CURSOR CUR_EMP(VAR_JOB IN VARCHAR(20)) IS
-- 使用FETCH+%ROWTYPE查询,这里的查询字段必须和表中字段顺序及数量一致
SELECT EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO FROM EMP;
VAR_EMP_TYPE EMP%ROWTYPE;
BEGIN
OPEN CUR_EMP;
LOOP
FETCH CUR_EMP INTO CUR;
EXIT WHEN CUR_EMP%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('编号:'||VAR_EMP_TYPE.EMPNO||',姓名'||VAR_EMP_TYPE.ENAME||',薪水'||VAR_EMP_TYPE.SAL);
END LOOP;
CLOSE CUR_EMP;
END;
9、其他注意事项
-
在利用select…into…语法时,必须先确保数据库中有该条记录,否则会报出”no data found”异常。
可以在该语法之前,先利用select count(*) from 查看数据库中是否存在该记录,如果存在,再利用select…into…
-
在oracle中,数据表别名不能加as
-
如果在存储过程中,使用如下语句:
select sum(vcount) into fcount from A where bid= 'xxxxxx';
如果A表中不存在bid=”xxxxxx”的记录,则fcount=null(即使fcount定义时设置了默认值,如:fcount number(8):=0依然无效,fcount还是会变成null),这样以后使用fcount时就可能有问题,所以在这里最好先判断一下:
if fcount is null then
fcount:=0;
end if;