zoukankan      html  css  js  c++  java
  • PL-SQL基础知识与使用

    PL/SQL

    PL/SQL是对SQL语句的扩展。增加了编程语言的特点,把数据操作和查询语句组织通过逻辑判断、循环等操作实现复杂的功能的程序语言。

    在PL/SQL中不能直接写select,如果要写select查询必须要先通过变量接收,但可以直接写insert,update,delete

    PL/SQL结构

    declare
    /* 声明部分:在此声明PL/SQL用到的变量,类型及游标,以及局部的存储过程和函数 */
    begin
    /* 执行部分:过程及 SQL 语句,即程序的主要部分 */
    exception
    /* 执行异常部分:错误处理 */
    end;
    

    变量

    /*
    定义一个变量:变量名 数据结构(number)
    				 %type 与表中某个属性列的数据类型相一致,存储一个数据
    				 %rowtype 表的行类型变量,数据类型和表的数据结构相一致,存储一行数据
    := 赋值运算符
    || 拼接符
    可以用 SELECT 语句对记录变量进行赋值,, 只要保证记录字段与查询结果列表中的字段相配即可。
    select into empno from emp 
    
    set serveroutput on 打开输出
    dbms_output.put_line(); 输出一行
    */
    
    1. 根据一个员工的工资加奖金。

      declare
      	v_sal emp.sal%type;
      begin
      	select sal into v_sal from emp where empno = 7876;
      	if v_sal>3000 then
      		update emp set comm = nvl(comm,0)+500
              where empno = 7876;
          elsif v_sal>2000 then
      		update emp set comm = nvl(comm,0)+300
              where empno = 7876;
          elsif v_sal>1000 then
      		update emp set comm = nvl(comm,0)+100
              where empno = 7876;
        end if;
      commit;
      end;
      
    2. 查询编号7876员工的信息。

      declare
      	row_emp emp%rowtype;
      	v_name emp.ename%type;
      begin
      	select * into row_emp from emp where empno = '7876';
      	select dname into v_name from dept where deptno = row_emp.deptno;
        dbms_output.put_line('工号'||row_emp.empno||'姓名'||row_emp.ename||'工资'||row_emp.sal||'部门名'||v_name);
      end;
      

    选择

    /*
    if 条件 then
    	代码块;
    	continue;跳出本次循环
    	exit;退出循环
    	return;退出程序
    	goto 锚; 跳到描点继续执行
    elsif 条件 then
    	代码块
    else
    	代码块
    end if;
    */
    

    循环

    用于遍历游标(集合)。

    /*
    loop:直接开始循环,当满足指定条件后结束循环
    while for:当满足指定条件后开始循环
    */
    
    1. loop循环输出1,2,3,5,6

      declare
      	v_i number(2):=0;
      begin
      	loop
      		exit when v_i>=6;--退出条件
      		v_i:=v_i+1;
      		if v_i = 4 then
      			continue;--跳出本次循环
      			/*exit; 退出循环*/
      			/*return; 退出程序*/
      		end if;
      		dbms_output.put_line(v_i);
      	end loop;
      end;
      
    2. while循环输出1,2,3,4,5

      declare
      	v_i number(2):=1;
      begin
      	while v_i<=5 loop
      		dbms_output.put_line(v_i);
      		v_i:=v_i+1;
      	end loop;
      end;
      
    3. for循环输出1,2,3,4,5

      declare
      begin
      	for v_i in 1..5 loop
      		dbms_output.put_line(v_i);
      	end loop;
      end;
      
    4. 打印九九乘法表。

      declare
      begin
      	for i in 1..9 loop
      		for j in 1..9 loop
      			if (j<=i) then
      				dbms_output.put(j||'*'||i||'='||i*j||'  ');
      			end if;
      		end loop;
      	dbms_output.put_line(chr(10));/*chr(10):换行*/
      	end loop;
      end;
      

    异常

    程序在执行时报错,导致无法继续运行。通过异常处理,让程序继续运行。

    Oracle预定义的21种异常

    declare
         v_i  emp.sal%type;
    begin
         dbms_output.put_line('程序开始执行');
         select sal into v_i from emp where empno=7788; 
         dbms_output.put_line('程序执行完成');
         /*异常处理模块,写在最后*/
         exception
                when TOO_MANY_ROWS then   
                    dbms_output.put_line('返回多行结果');
                when others then
                	dbms_output.put_line('r');
    end;
    

    自定义异常名称

    declare
         /*自定义异常类型的名称*/
         NO_ClASS_ID exception;
         /*把自定义的异常类型名称跟异常编号进行绑定*/
         pragma exception_init(NO_ClASS_ID,-02291);
    begin
         update studentinfo set  sclassid=99 where stuid=11;
         exception
                when NO_ClASS_ID then   
                        dbms_output.put_line('没有该班级编号');
    end;
    

    游标

    游标(cursor):相当于Java中的集合。

    处理多行记录使用游标。

    不能重复打开一个游标,关闭游标后可以重新打开游标提取数据。

    定义游标->打开游标->提取游标数据->关闭游标

    /*定义游标*/
    cursor cursor_name sys_refcursor;--系统数据类型
    type cur_emp_type is ref cursor return emp%rowtype;--自定义游标类型
    /*打开游标*/
    open cursor_name is select...
    /*提取游标数据*/
    loop
    	exit when cursor_name%notfound;
    	fetch cursor_name into v_i;
    	dbms_output.put_line(v_i);
    /*关闭游标*/
    close cursor;
    

    显式游标

    显式游标是定义的游标,分为静态和动态游标。

    静态游标在定义时里面的数据就固定了。

    静态游标

    loop循环遍历游标

    declare
      row_dept dept%rowtype;
      cursor cur_dept is select * from dept;
    begin
      open cur_dept;
      loop
        exit when cur_dept%notfound;
        fetch cur_dept into row_dept;
        dbms_output.put_line('部门编号:'||row_dept.deptno||',部门名:'||row_dept.dname);
      end loop;
      close cur_dept;
    end;
    

    for循环遍历游标

    declare
      row_dept dept%rowtype;
      cursor cur_dept is select * from dept;
    begin
      for row_dept in cur_dept loop
      	dbms_output.put_line('部门编号:'||row_dept.deptno||',部门名:'||row_dept.dname);
      end loop;
    end;
    

    动态游标

    在程序运行时不能确定需要的数据,需要动态的获取。

    declare
    cursor cur_dept is select * from dept;
    row_dept cur_dept%rowtype;
    /*定义动态游标的数据类型:ref cursor*/
    /*return emp%rowtype指定该类型游标只能存放emp的数据*/
    type cur_emp_type is ref cursor return emp%rowtype;
    /*定义游标变量,变量类型是cur_emp_type*/
    cur_emp cur_emp_type;
    /*cur_emp sys_refcursor 系统动态游标类型*/
    row_emp cur_emp%rowtype;
    
    begin
     open cur_dept;
     	loop
     		fetch cur_dept into row_dept;/*fetch循环将游标d数据赋给变量*/
     		exit when cur_dept%notfound;/*游标没有数据了就返回true*/
     		dbms_output.put_line('部门名称'||row_dept.dname);
     		open cur_emp for select * from emp where deptno = row_deptno;
     		loop
     			fetch cur_emp into row_emp;
     			exit when cur_emp%notfound;
     			dbms_output.put_line('员工姓名'||ename)
     		end loop;
     		close cur_emp;
     	end loop;
     close cur_dept;
    

    隐式游标

    当进行insert,update,delete操作时,系统会默认生成隐式游标。名字默认为sql

    declare
    begin
    	update emp set sal = 666 where empno = 7788;
    	/*isopen:游标是否打开,当insert,update,delete语句执行完后隐式游标自动关闭*/
    	if sql%isopen then 
    		dbms_output.put_line('sql游标打开');
    	else 
    		dbms_output.put_line('sql游标未打开');
    	end if;
    	/*
    	found:用于隐式游标判断sql语句是否执行成功
    		  用于显式游标判断是否还有数据
    	not found:与found相反
    	*/
    	if sql%found then
    		dbms_output.put_line('sql游标有数据');
    	else
    		dbms_output.put_line('sql游标没有数据');
    	end if;
    	/*rowcount:返回影响的行数*/
    	dbms_output.put_line(sql%rowcount);
    end;
    

    事务

    一段SQL语句的集合。

    这段SQL语句要么全部执行,要么全不执行。

    原子性(Atomicity):事务中SQL语句集合作为一个整体。

    一致性(Consistency):事务执行前后数据保持相对一致。

    隔离性(Isolation):一个事务的执行不能受到另一个事务的影响。

    持久性(Durability):事务执行完成后对数据库的影响是持久的。

    并发的后果

    数据库是多用户使用的共享资源,若用户并发地对一个数据进行操作,就会产生意料之外的后果。

    1. 脏读:一个事务查询到了另一个事务修改了但还未提交的数据。
    2. 幻读:一个事务在两次查询中间被另一个事务删行,导致返回了不同的行。
    3. 不可重复读:一个事务在两次查询中间数据被另一个事务修改了。

    隔离级别

    Oracle数据库中PLsql自带隔离机制。

    /*转账*/
    declare
    begin
    	update bank set money = money-500 where id = 1;
    	savepoint a;--锚点
    	update bank set money = money+500 where id = 2;
    	commit;
    	dbms_output.put_line('转账成功');
    	exception
    		when others then
    			rollback to savepoint a;
    			commit;
    			dbms_output.put_line('转账失败,数据回滚');
    end;
    

    防止事务并发产生的意料之外的后果。

    DML锁:在针对表进行DML操作时,保障数据的安全。包括表级锁和行级锁。


    TM锁(表级锁):不能操作表。包括共享锁和排他锁。

    /*共享锁:一个数据可以加多个共享锁,但需要等待其他事务执行完成才能对表进行写入。*/
    lock table emp in share mode;
    
    /*排他锁:一个数据只能加一个排他锁。不允许读写。*/
    lock table emp in exclusive mode;
    

    TX锁(行级锁):不能操作该行数据。

    当事务执行数据库插入、更新、删除操作时,该事务自动获得操作表中操作行的排他锁。

    /*显式的添加行锁*/
    select * from emp where empno = 7788 for update;
    update emp set sal = 10000 where empno = 7788;
    

    存储过程

    类似于Java中的方法。没有返回值。

    参数有输入输出类型:

    ​ in:把外面的数据传入到存储过程中。

    ​ out:把存储过程中产生的数据传到外面。

    ​ inout:既能传入又能传出

    1. 根据员工的编号,查询员工工资

      /*创建或替代一个存储过程*/
      create or replace procedure query_sal_by_empno(
          /*定义变量,如果没有c小括号也不要写*/
      	v_empno in emp.empno%type,/*输入变量*/
          out_sal out emp.sal%type/*输出变量*/
      ) is /*或as*/
      /*定义变量*/
      begin
      	select sal into out_sal from emp where empno = v_empno;
      end;
      
      /*调用存储过程必须使用PLsql*/
      declare
      	/*调用存储过程时输入类型的参数没必要定义变量,但是输出类型的参数必须要定义参数*/
      	out_sal emp.sal%type;
      begin
      	query_sal_by_empno(7788,out_sal);
      	dbms_output.put_line(out_sal);
      end;
      
    2. 根据用户名称模糊查询用户。

      create or replace procedure query_emp_by_ename(v_ename in emp.ename%type,cur_emp out sys_refcursor) 
      is
      begin
        	open cur_emp for select * from emp where ename like concat('%',concat(v_ename,'%'));
      end;
      
      /*调用*/
      declare
        	cur_emp sys_refcursor;
        	row_emp emp%rowtype;
        	v_ename emp.ename%type:='S';
      begin
        	query_emp_by_ename(v_ename,cur_emp);
      	loop
      	exit when cur_emp%notfound;
          fetch cur_emp into row_emp;
      		dbms_output.put_line(row_emp.ename);
        	end loop;
        	close cur_emp;
      end;
      

    函数

    函数必须有返回值。

    可以嵌入到sql语句中执行。

    1. 计算工资:工资加奖金,并且入职年限月多加的工资越多。

      /*创建或替代一个函数*/
      create or replace function fun_sal(v_empno emp.empno%type)
      return number/*函数必须有返回值*/
      is
      	v_year number(2);
      	v_sal emp.sal%type;
      begin
        	select (sysdate-hiredate)/365 into v_year from emp where empno = v_empno;
        	select (sal+nvl(comm,0)) into v_sal from emp where empno = v_empno;
        	if v_year>30 then
          	v_sal := v_sal+3000;
        	elsif v_year>20 then 
          	v_sal := v_sal+2000;
        	elsif v_year>10 then
          	v_sal := v_sal+1000;
        	end if;
        	return v_sal;
      end;
      
      /*嵌入到select语句中使用*/
      select ename,fun_sal(empno) from emp ;
      
    2. 根据用户名称模糊查询用户。

      create or replace function fun_like_ename(v_ename emp.ename%type)
      return sys_refcursor
      is
        	cur_emp sys_refcursor;
      begin
        	open cur_emp for select * from emp where ename like concat('%',concat(v_ename,'%'));
        	return cur_emp;
      end;
      
      /*调用*/
      declare
        	cur_emp sys_refcursor;/*接收游标*/
        	row_emp emp%rowtype;/*接收行数据*/
      begin
        	cur_emp := fun_like_ename('S');
        	loop
          exit when cur_emp%notfound;
          fetch cur_emp into row_emp;
          	dbms_output.put_line(row_emp.ename);
        	end loop;
        	close cur_emp;
      end;
      

    对函数和存储过程进行整理。

    /*包的定义,相当于java中的接口*/
    create or replace package pack_emp
    is
    	/*定义全局变量,这里不能定义游标类型变量*/
    	v_test_q number(4) := 40;
    	/*可以定义游标数据类型*/
    	type cur_emp_type is ref cursor return emp%type;
    	/*定义存储方法*/
    	procedure query_emp_ename(v_ename in varchar2,v_cur_emp out sys_refcursor);
    	/*定义函数*/
    	function sum_sal(v_empno varchar2) return number;
    end pack_emp;
    
    /*包的主体,对定义的实现*/
    create or replace package body pack_emp
    is
    	/*定义局部变量*/
    	v_test_j number(4) := 20;
    	
    	/*实现存储过程*/
    	procedure query_emp_ename(v_ename in varchar2,v_cur_emp out sys_refcursor)
    	begin
    		open v_cur_emp for select * from emp where ename = v_ename;
    	end query_emp_ename;
    	
    	/*实现函数*/
    	function sum_sal(v_empno varchar2) return number
    	is
    		v_sal number(4);
    	begin
    		select (sal+nvl(comm,0)) into v_sal from emp where empno = v_empno;
    		return v_sal;
    	end sum_sal;
    end pack_emp;
    

    动态SQL语句

    1. 根据不同条件分页查询emp表数据。

      /*
      分页查询emp数据:
         1. 根据姓名模糊查询
         2. 工资范围查询
         3. 根据部门查询
         4. 页码
      */
      create or replace procedure fy_emp(
          v_ename varchar2,/*通过姓名查询*/
          v_min_sal number,/*最小工资查询*/
          v_max_sal number,/*最大工资查询*/
          v_deptno number,/*部门查询*/
          v_page number,/*页码查询*/
          cur_emp out sys_refcursor/*存储数据*/
      )
      is
      	/*该变量用于拼接动态sql语句的查询条件*/
      	v_where_sql varchar2(1000):=' where 1=1 ';
      	/*用于拼接存储查询语句*/
      	v_query_sql varchar2(2000);
      begin
      	/*判断是否输入条件拼接条件语句*/
      	if v_ename is not null then 
      		v_where_sql:=v_where_sql||' and ename like ''%'||v_ename||'%'' ';
      	end if;
      	
      	if v_min_sal is not null then
      		v_where_sql:=v_where_sql||' and sal>='||v_min_sal;
      	end if;
      	
      	if v_max_sal is not null then
      		v_where_sql:=v_where_sql||' and sal<='||v_max_sal;
      	end if;
      	
      	if v_deptno is not null then
      		v_where_sql:=v_where_sql||' and deptno='||v_deptno;
      	end if;
      	
      	/*拼接查询语句*/
      	/*此处查询结果必须是表结构的所有并顺序且不能写*h*/
      	/*单引号在字符串中为转义符号,''是输出一个单引号*/
      	v_query_sql:='select empno,ename,job,mgr,hiredate,sal,comm,deptno from (select e.*,rownum r from (select * from emp '||v_where_sql||' )e) where r>'||(v_page-1)*3 ||' and r<='|| v_page*3;
      	open cur_emp for v_query_sql;
      end;
      
      
      /*调用*/
      declare 
      	cur_emp sys_refcursor;/*接收数据*/
      	row_emp emp%rowtype;/*接收行数据*/
      begin
      	fy_emp(null,null,9999,30,20,cur_emp);/*调用存储过程,参数必须一一对应且不能缺省*/
          loop
          	fetch cur_emp into row_emp;
          	exit when cur_emp%notfound;
          	dbms_output.put_line(row_emp.ename); 
          end loop;
          close cur_emp;
      end;
      

    触发器

    触发器(Trigger)是数据库的回调函数,在指定的数据库事件发生时自动执行。

    行级触发器:每执行一行都会触发该触发器。

    块级触发器:不管操作了几行数据,只会触发一行。

    1. 行触发器要求当一个DML语句操作影响数据库中的多行数据时,对于其中的每个数据行,只要它们符合触发约束条件,均激活一次触发器

    2. 语句触发器将整个语句操作作为触发事件,当它符合约束条件时,激活一次触发器

    3. 当省略for each row 选项时,before 和after 触发器为语句触发器,而instead of 触发器则只能为行触发器。

    /*
    1. 记录操作类型
    2. 操作的行数据
    3. 操作人
    4. 操作时间
    */
    /*操作记录表*/
    create table bank_info(
    	id number(5) primary key,
        bank_type number(1),/*1:表示修改,2:表示删除*/
        bank_name varchar2(30),
        bank_admin varchar2(30),
        bank_date date
    )
    
    /*创建主键序列*/
    create sequence seq_bank_info_id
    minvalue 1
    maxvalue 99999
    increment by 1;
    
    /*创建主键触发器*/
    create trigger tri_bank_info_id
    /*在对表bank_info进行插入操作前*/
    before insert on bank_info
    for each row
    begin
    	select seq_bank_info_id.nextval into :new.id from dual;
    end;
    
    /*通过触发器实现记录操作日志*/
    create or replace trigger tri_bank_to_bank_info
    /*在对表bank进行删除或修改之后*/
    after delete or update on bank
    for each row/*h*/
    /*触发的条件*/
    when(old.id<60) 
    declare
    	v_type number(1);
    begin
    	/*正在进行删除操作*/
    	if deleting then
    		v_type = 2;
    	/*正在进行修改操作*/
    	elsif updating then
    		v_type = 1;
    	end if;
    	insert into bank_info(bank_type,bank_name,bank_admin,bank_date) values(v_type,:old.name,'lee',sysdate);
    end;
    
  • 相关阅读:
    常用的虚拟化产品
    KVM虚拟化
    Jenkins流水线项目发布流程
    Jenkins安装,界面说明
    敏捷估算与规划—总结
    敏捷估算与规划—跟踪与交流
    敏捷估算与规划—进度安排
    敏捷估算与规划—为价值做规划
    敏捷估算与规划—问题与目标
    用户故事与敏捷方法笔记 --- 估算与计划
  • 原文地址:https://www.cnblogs.com/hermitlee/p/15175203.html
Copyright © 2011-2022 走看看