PL/SQL 笔记


游标的类型有两种:隐式游标和显式游标
 
说明:当进行表的数据查询操作, 一般用游标实现,
      查询结果为单行的可以用隐式游标select ... into ...,
	  查询结果为多行的可以用显式游标cursor ...;
   
    ----------------
	1.需要对表结果进行逐行打印(必需一行行的打印)
	2.需要进行数据的分批提交(如:DML语言操作)
	3.需要对表结果进行逐行计算(如:计算每张表对应的行数,需要先获取每一张表名,在分别计算行数)


1.隐式游标(单行值写入变量可以用select ... into ...)
	对变量赋值还可以使用SELECT…INTO 语句从数据库中查询数据对变量进行赋值。
	但是查询的结果只能是一行记录,不能是零行或者多行记录。


	例题:打印出emp中员工编号为7369的姓名和工资。 
	DECLARE
	  V_ENAME VARCHAR2(10);
	  V_SAL NUMBER(7,2);
	BEGIN
	  SELECT ENAME,SAL
		INTO V_ENAME,V_SAL          -- 隐士游标赋值(来源于表的查询结果,查询结果只能是单行)
		FROM EMP WHERE EMPNO=7369;
	  DBMS_OUTPUT.PUT_LINE('员工名称:'||V_ENAME||' 员工工资:'||V_SAL);    -- 不能直接打印表,只能是单行字段拼接后的字符串形式打印
	END;
	
	注意:使用select…into语句对变量赋值,要求查询的结果必须是一行,不能是多行或者没有记录。
	      不能直接打印表,可以通过将表查询的结果赋值给变量,然后打印
	
		   
				   

2.引用数据类型
	%TYPE:   引用表中的某列的数据类型或某个变量的数据类型(单值变量,只能存储单值)。
	%ROWTYPE:引用表中的一行(所有字段)作为数据类型(表变量,也只能一行一行的存储)。
	
	例题:打印出emp中员工编号为7369的姓名和工资。
	-- %TYPE 引用表单个字段类型
	DECLARE
	  v_name emp.ename%TYPE;      -- 引用表的单个字段类型(单值变量)
	  v_sal  emp.sal%TYPE;
	BEGIN
	  SELECT ename, sal
		INTO v_name, v_sal
		FROM emp 
	   WHERE empno=7369;
	  dbms_output.put_line('姓名:' || v_name ||' 工资: ' ||v_sal);
	END;


	-- %ROWTYPE 引用表的所有字段类型
	DECLARE
	  v_emp emp%ROWTYPE;         -- 引用整个表的字段类型(表变量)
	BEGIN
	  SELECT *
		INTO v_emp               -- 插入整行数据(所有字段数据)
		FROM emp 
	   WHERE empno=7369;
	  dbms_output.put_line('姓名:' || v_emp.ename ||' 工资: ' ||v_emp.sal);  -- 不能直接打印整个表,只能是单个值的拼接
	END;


	DECLARE
	  v_emp emp%ROWTYPE;           -- 引用整个表的字段类型(表变量)
	BEGIN
	  SELECT ename, sal
		INTO v_emp.ename,v_emp.sal -- 只插入两个字段数据
		FROM emp 
	   WHERE empno=7369;
	  dbms_output.put_line('姓名:' || v_emp.ename ||' 工资: ' ||v_emp.sal);  -- 不能直接打印整个表,只能是单个值的拼接
	END;

	

    练习:打印员工7499的工作职位和部门名称
	
	
	



3.显示游标(多行值写入变量用显示游标)
	游标的类型有两种:隐式游标和显示游标。
	PL/SQL会为所有的SQL数据操作声明一个隐式的游标,包括只返回一条记录的查询操作。
	显示游标四个步骤:
	1.声明
	2.打开游标
	3.逐行获取数据
	4.关闭游标
		  
	语法结构:声明游标
	CURSOR 游标名[(参数1 数据类型[,参数2 数据类型...])]
	IS SELECT 语句;  --游标的声明
  
	语法结构:执行游标
	OPEN 游标名[(实际参数1[,实际参数2...])];  --打开游标
	FETCH 游标名 INTO 变量名1[,变量名2...];
	或
	FETCH 游标名 INTO 记录变量;  --提取数据
	CLOSE 游标名;  --关闭游标(千万别忘了!)
  
	游标属性:%FOUND、%NOTFOUND
	%FOUND:
	用于判断游标是否从结果集中提取数据。如果提取到数据,则返回值为TRUE,否则返回值为FALSE。
	%NOTFOUND:
	该属性与%FOUND相反,如果提取到数据则返回值为FALSE;如果没有,则返回值为TRUN。

  
	例题1:查询10号部门所有员工的姓名和工资并打印(PL/SQL)
	-- 显式游标
	DECLARE
	  -- 显式游标记录结果集
	  CURSOR C_EMP IS                   -- 先声明游标(放在declare中)
	  SELECT ENAME,SAL
		FROM EMP
	   WHERE DEPTNO=10;
	  -- 变量可以被赋值打印
	  V_EMP C_EMP%ROWTYPE;              -- 声明变量为游标行类型
	BEGIN
	  -- DBMS_OUTPUT.PUT_LINE(C_EMP);   -- 没法直接打印结果集(表的结果集)
	  OPEN C_EMP;                       -- 打开游标
	  LOOP                              -- 游标为结果集所以要有循环
		FETCH C_EMP INTO V_EMP;
		EXIT WHEN C_EMP%NOTFOUND;       -- 退出循环条件
		-- DBMS_OUTPUT.PUT_LINE(C_EMP); -- 没法直接打印结果集(单行的结果集)
		DBMS_OUTPUT.PUT_LINE(V_EMP.ENAME||' -- '||V_EMP.SAL);   -- 只能从变量中取字段
		-- EXIT WHEN C_EMP%NOTFOUND;        -- 放在最后打印结果会多出最后一行
	  END LOOP;
	  -- 关闭游标
	  CLOSE C_EMP;
	END;
	
	
	--FOR循环
	DECLARE
	  CURSOR C_EMP IS
	  SELECT ENAME,SAL
		FROM EMP
	   WHERE DEPTNO=10; 
	BEGIN
	  FOR V_EMP IN C_EMP LOOP
		DBMS_OUTPUT.PUT_LINE('姓名: ' || V_EMP.ENAME || '工资: ' || V_EMP.SAL);
	  END LOOP;
	END;
	

	
	
	-- 游标参数
	例题2:打印某个部门的员工姓名和工资
	DECLARE
	  CURSOR C_EMP(P_DEPTNO EMP.DEPTNO%TYPE) IS   -- 游标参数
	  SELECT ENAME,SAL
		FROM EMP 
	   WHERE DEPTNO=P_DEPTNO;
	BEGIN
	  FOR V_EMP IN C_EMP(&部门号) LOOP    -- 键盘输入变量值(键盘输入值时,字符型要加单引号)/打开游标的时候传入
	  DBMS_OUTPUT.PUT_LINE('姓名: ' || V_EMP.ENAME || '工资: ' || V_EMP.SAL);
	  END LOOP;
	END;
	
	


	
	

1.程序块中的%ROWCOUNT
	1.1 SQL%ROWCOUNT 用于记录修改的条数,必须放在一个增删改等修改类语句后面执行,select语句用于查询的话无法使用,
	    当你执行多条修改语句时,SQL%ROWCOUNT 之前执行的最后一条语句修改数为准。

		BEGIN
		  DELETE FROM emp;
		    DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT);   -- 记录删除数据的条数
		  INSERT INTO emp(empno) VALUES (7777);
		    DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT);   -- 记录插入数据的条数
		  UPDATE emp SET comm=1000 WHERE empno=7369;
		    DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT);   -- 记录更新数据的条数
		END;


	1.2 游标%ROWCOUNT  用于记录游标遍历到第几行。
		
		-- 2000万行数据更新(全部更新完再提交)  -- 长期占用表资源,影响系统
		UPDATE T_2000W SET SAL=SAL+100;
		COMMIT;
		
		-- 2000万行数据分批提交, 每1w行提交一次!
		DECLARE
		  CURSOR C_2000W IS
		    SELECT * FROM T_2000W;
		BEGIN
		  FOR V IN C_2000W LOOP
		    UPDATE T_2000W SET SAL=SAL+100 WHERE ID=V.ID;
			IF MOD(C_2000W%ROWCOUNT,10000)=0 THEN 
			  COMMIT;
			END IF;
		  END LOOP;
		  COMMIT;
		END;
		
	    



1.PL/SQL 中DDL语言

	-- DDL不能直接在PL/SQL块中使用
	BEGIN
	   -- CREATE TABLE EMP_01 AS SELECT * FROM EMP;   
	   -- TRUNCATE TABLE EMP_01;  
	   -- ALTER TABLE EMP_01 ADD CT DATE;
	   DROP TABLE EMP_01;
	END;
	
	
	
	
2.动态SQL	  
	在PL/SQL程序开发中,可以使用DML语句和事务控制语句,但是还有很多语句(比如DDL语句)不能直接在PL/SQL中执行。
	这些语句可以使用动态SQL来实现。
  
	语法格式:动态SQL
	EXECUTE IMMEDIATE 动态语句字符串
	[INTO 变量列表]
	[USING 参数列表]
	
	------------
	BEGIN
	  EXECUTE IMMEDIATE 'CREATE TABLE YYY AS SELECT * FROM EMP';    -- 字符串语句最后不要加分号;
	END;
	或者
	DECLARE
	  V_SQL VARCHAR2(100) :='CREATE TABLE YYY AS SELECT * FROM EMP'  
	BEGIN
	  EXECUTE IMMEDIATE V_SQL;
	END;
	


    例题:查看某个员工的工资并打印。
    -- 隐式游标
    DECLARE
      v_sal emp.sal%TYPE;
    BEGIN
      SELECT sal INTO v_sal FROM emp WHERE empno=&输入员工编号;
      DBMS_OUTPUT.PUT_LINE(v_sal);
    EXCEPTION WHEN OTHERS
      THEN DBMS_OUTPUT.PUT_LINE(SQLERRM);
    END;
    
    
    -- 动态SQL(含参数和赋值)
    DECLARE
      v_sal emp.sal%TYPE;
    BEGIN
      EXECUTE IMMEDIATE 'SELECT sal FROM emp WHERE empno =: 接收参数' -- =: 接收参数传入
  	  INTO v_sal
  	  USING &输入部门编号;
      DBMS_OUTPUT.PUT_LINE(v_sal);
    EXCEPTION WHEN OTHERS
      THEN DBMS_OUTPUT.PUT_LINE(SQLERRM);
    END;
	
	
	
	-- 关于 DBMS_OUTPUT.PUT 和 DBMS_OUTPUT.PUT_LINE
	BEGIN
	  DBMS_OUTPUT.PUT('0710');    -- 不换行(后面必须有DBMS_OUTPUT.PUT_LINE,才能打印)
	  DBMS_OUTPUT.PUT_LINE('BI');
	  DBMS_OUTPUT.PUT_LINE('HAHAHA');
	END;



	-- 双重循环
	BEGIN
	  FOR X IN 1..3 LOOP
		FOR Y IN 4..6 LOOP
			DBMS_OUTPUT.PUT_LINE(X||'*'||Y||'='||X*Y); 
		END LOOP;
	  END LOOP;
	END;  
		  


	
	练习1:打印当前用户下面所有的表名称及每张表其对应的总行数
	       EMP   ...... 15
		   DEPT  ......  4
	       ............
		   
		DECLARE
          -- 获取所有的表名称
        CURSOR c_tbname IS
            SELECT table_name FROM user_tables; 
          -- 变量存储表行数值
          V_NUM NUMBER(10);
          -- 存储动态SQL字符串
          V_SQL VARCHAR2(1000);
        BEGIN
          -- 遍历表名称,分别统计每张表的行数
          FOR V IN c_tbname LOOP
            V_SQL := 'SELECT COUNT(1) FROM ' || V.table_name;
            -- DBMS_OUTPUT.PUT_LINE(V_SQL);
            EXECUTE IMMEDIATE V_SQL
            INTO V_NUM;
            DBMS_OUTPUT.PUT_LINE(V.table_name||'-------'||V_NUM);
          END LOOP;
        EXCEPTION WHEN OTHERS THEN
          DBMS_OUTPUT.PUT_LINE(SQLERRM);
        END;

	      
	注意:1.SELECT table_name FROM user_tables;   -- 查询当前用户下所有的表名称
	      2.from 后面跟变量, 会把变量当作表名,必须用动态sql 拼接语句
	      3.动态sql中的赋值用 execute immediate 动态sql语句 into 变量;
		  4.在执行动态sql前,可以BMS_OUTPUT.PUT_LINE(动态sql语句); 查看语句是否正确
		  
		  
		  
		  
		  
	练习2.编写PLSQL块,统计每个部门名称及其员工人数和平均工资(将统计结果插入到建立的目标表中,目标表在PLSQL块外建立)
		目标表:4个字段 (dname,emp_num,avg_sal,etl_date)-- etl_date为计算数据的时间,用sysdate就行
		注意:数据量特别大的时候,通过游标插入数据时候,实现每插入1万条数据提交一次;
			  数据量不大的时候,可以直接一次性插入数据到目标表,最后提交。 
		-- 全量数据(清空所有数据支持重跑)
		DELETE FROM 表;  
		-- 增量数据(清空当天数据支持重跑)
		DELETE FROM 表 WHERE TRUNC(etl_date)=TRUNC(SYSDATE);

	
	
SQL  -->  PL/SQL 块  --> 过程体(存储过程/函数/触发器) --> 包

事物的特性:一致性、原子性、隔离性、持久性
https://www.cnblogs.com/dwxt/p/8807981.html

存储过程作用?
https://blog.csdn.net/weixin_39805338/article/details/80798706



创建存储过程
	为什么需要创建存储过程?
	(1)封装代码,执行复杂的逻辑
	(2)先编译后执行,第一次编译成功后,之后调用,其效率要比单独运行sql语句要高
	(3)存储过程是一个完整的事物,整体的代码要么全部成功要么全部失败
	存储过程在工作中的实际应用?
	(1)处理多张表关联的逻辑,并将最终的结果集数据写入到目标表
	(2)同一种数据库,可以做跨库数据同步,增量(MERGE INTO ...)或全量
	语法格式:创建存储过程
	CREATE [OR REPLACE] PROCEDURE 过程名[(参数1 [IN|OUT|IN OUT] 数据类型,参数2 [IN|OUT|IN OUT] 数据类型……)]
	IS|AS
	PL/SQL过程体; -- 直接从BEGIN开始
	
	三种参数:传入(in或省去),传出(out),传入或传出(in out)
	注意: 1.数据类型不要带字符长度也不用括号 varchar2/number/date
		  2.如果存储过程不带参数,过程名后不用括号,IS|AS 后可以直接跟声明内容,不要declare
		  3.为in的参数,不能在过程中被赋值,只能作为调用传入值
		  4.省去参数类型则默认为IN传入
		  5.IN OUT 参数必需在声明时候赋初值,调用中用变量名
		  
	语法格式:调用存储过程
	
	注意:存储过程有多少个参数,调用中就要对应多少个参数(默认参数除外)
	BEGIN
	  过程名[(参数)];
	END;
	
	
	
	-- 1.不带参数		
	例题1:每天全量同步emp表的数据到emp_cp(emp_cp要有etl_tm 同步时间)。
    -- 封装成存储过程,以后每天定时调用
    CREATE OR REPLACE PROCEDURE SP_D_EMP_CP
    IS
		-- 有变量/常量/游标此处声明
		/*
		源表:    emp
		目标表:  emp_cp
		创建时间:20201201
		版本号:v01
		*/
    BEGIN
     -- 清空emp_cp的数据,支持重跑
     EXECUTE IMMEDIATE 'TRUNCATE TABLE EMP_CP';
     -- 全量插入数据到emp_cp
     INSERT INTO EMP_CP(EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO,ETL_TM)
     SELECT EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO,SYSDATE AS ETL_TM
       FROM EMP;
     DBMS_OUTPUT.PUT_LINE('插入:'||SQL%ROWCOUNT);
     -- 数据提交
     COMMIT;
    EXCEPTION WHEN OTHERS
     THEN DBMS_OUTPUT.PUT_LINE(SQLCODE||SQLERRM);
    END SP_D_EMP_CP;
   
   
   -- 调用存储过程
   BEGIN
     SP_D_EMP_CP;
   END;
   
   -- 查询
   SELECT * FROM EMP_CP;
   
	
   -- 调用
   -- (1)sql window 的程序块执行
   BEGIN
	 SP_D_EMP_CP; -- SP_D_EMP_CP();
   END;

   -- (2)右键存储过程,test
	
	

    -- 2.只带传入参数
    例题2:每天增量同步emp表的数据到emp_cp(emp_cp要有etl_tm 同步时间)。
    CREATE OR REPLACE PROCEDURE SP_D_EMP_CP_ADD(P_ETL_DT IN VARCHAR2) IS
      V_ETL_DT DATE;
    BEGIN
      V_ETL_DT := TO_DATE(P_ETL_DT, 'YYYY-MM-DD');
      -- 删除增量时间数据,支持重跑
      DELETE FROM EMP_CP WHERE HIREDATE = V_ETL_DT;
      -- 增量插入数据
      INSERT INTO EMP_CP
        (EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO, ETL_TM)
        SELECT EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO, V_ETL_DT AS ETL_TM
          FROM EMP
         WHERE HIREDATE = V_ETL_DT; -- 限定入职日期等于传入日期
      -- 提交
      COMMIT;
    EXCEPTION
      WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE(SQLERRM);
        ROLLBACK;
    END SP_D_EMP_CP_ADD;
	
	
    -- 调用
    BEGIN
      SP_D_EMP_CP_ADD('19810220');
    END;
    或者
    BEGIN
      SP_D_EMP_CP_ADD('&输入日期字符串');
    END;
  	或者 
  	BEGIN
  	  SP_D_EMP_CP_ADD(P_ETL_DT => '19810220');  -- 参数定向调用(多个参数可以改变顺序)
  	END;
     -- 右键存储过程,test
	
   

	-- 3.带传入和传出参数
	例题3:传入一个员工号,传出该员工的姓名和工资。
    CREATE OR REPLACE PROCEDURE sp_in_out_empno(p_empno IN emp.empno%TYPE,
                                                p_ename OUT emp.ename%TYPE,
                                                p_sal   OUT emp.sal%TYPE)
    IS 
    BEGIN
      SELECT ename,sal 
        INTO p_ename,p_sal  -- out 变量可以被赋值
        FROM emp 
       WHERE empno=p_empno; -- in 变量只能传入值
      EXCEPTION
        	WHEN OTHERS THEN
    	  DBMS_OUTPUT.PUT_LINE(SQLERRM);
    END;
    
    -- 调用
    DECLARE
      v_ename emp.ename%TYPE;
      v_sal   emp.sal%TYPE;
    BEGIN
      sp_in_out_empno(&输入员工号,v_ename,v_sal);
      dbms_output.put_line('员工姓名:'||v_ename||' 员工工资:'||v_sal);
    END sp_in_out_empno;
  
  

	-- 4.参数中含IN OUT 类型
    例题4:传入一个员工号,传出该员工的姓名和工资。
    CREATE OR REPLACE PROCEDURE sp_inout_empno(p_empno_sal IN OUT NUMBER,
   											   p_ename OUT emp.ename%TYPE)
    IS 
    BEGIN
   	SELECT ename,sal 
   	  INTO p_ename,p_empno_sal -- 作为out 参数被赋值
   	  FROM emp 
   	 WHERE empno=p_empno_sal;  -- 第一次作为in 参数传值
   	EXCEPTION
   		WHEN OTHERS THEN
   	  DBMS_OUTPUT.PUT_LINE(SQLERRM);
    END sp_inout_empno;
    
    -- 调用
    DECLARE
   	v_empno_sal NUMBER(10):=7369;  -- in out 类型必须声明给初值
   	v_ename emp.ename%TYPE;
    BEGIN
   	sp_inout_empno(v_empno_sal,v_ename);
   	dbms_output.put_line('员工姓名:'||v_ename||' 员工工资:'||v_empno_sal);
    END;
  
	
	
	
	-- 5.注意参数给默认值情况
	CREATE OR REPLACE PROCEDURE SP_EMP5(T_DEPTNO EMP.DEPTNO%TYPE DEFAULT 10,
										T_JOB    EMP.JOB%TYPE)

	 IS
	  T_EMPNO EMP.EMPNO%TYPE;
	  T_SAL   EMP.SAL%TYPE;
	BEGIN
	  SELECT EMPNO, SAL
		INTO T_EMPNO, T_SAL
		FROM EMP
	   WHERE DEPTNO = T_DEPTNO
		 AND JOB = T_JOB;
	  DBMS_OUTPUT.PUT_LINE('员工编号: ' || T_EMPNO || ' 工资: ' || T_SAL);
      EXCEPTION
          WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE(SQLCODE || SQLERRM);
	END;
	
	
	调用1
    BEGIN
	  SP_EMP5(20,'MANAGER');
	END;
	调用2
	BEGIN
	  SP_EMP5(T_JOB=>'MANAGER');
	END;
	调用3
	BEGIN
	  SP_EMP5(T_JOB=>'MANAGER',T_DEPTNO=>30);
	END;
	  
	  
	  
	
	注意:存储过程中的out参数,可以在过程体中被赋值, 外部调用该存储过程后获得out变量的值;
	      存储过程中的变量, 可以在过程体中被赋值, 外部调用该存储过程并不能得到其内部变量的值.
	  
	  
	  
	练习:1.编写存储过程,传入部门号和工作类型,返回员工编号和对应工资。
	      2.编写存储过程,传入一个日期字符串,返回小于等于该日期的入职员工数和程序运行代码(成功则返回'0',否则返回SQLERRM错误代码)



	
	
	
	
	
	
	

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值