SQL的高阶用法-SQL函数编程

SQL的高阶用法-SQL函数编程

引子

一般的SQL语句已经能支撑大规模的增删改查了,但有时候我们不仅要查出数据,还要对查出来的数据做进一步的加工,就像编程语言对数据的加工一样,在编程语言中,通过一系列方法,数据从入到出,变成了我们想要的模样,SQL也提供了类似的功能,唯一的区别是,SQL编程没有返回值,你可以直接对查出来的数据进行编程,结果直接展现在数据库中,这种模式叫做 PL SQL,全称Procedure Language SQL
中文:过程化SQL
如果你已有一门编程语言基础,那么以下内容将不费吹灰之力,尽数拿下

目录

  • SQL的高阶用法-SQL函数编程
    • 引子
    • PL/SQL简单介绍
    • PL/SQL程序语法
      • 程序语法
      • 常量和变量定义
      • if分支
      • 三种语法
      • LOOP循环语句
      • 游标Cursor
      • 使用步骤:
      • 例外
    • 存储过程
      • 创建存储过程语法:
    • 存储函数
      • 存储过程和存储函数的区别
    • 触发器
      • 在触发器中触发语句与伪记录变量的值

PL/SQL简单介绍

PL/SQL(Procedure Language/SQL)(过程语言/SQL)是Oracle对sql语言过程化扩展,把SQL语言的数据操纵能力与过程语言的数据能力结合起来

  • 范例:为职工涨工资,没人10%
update emp set sal=sal*1.1
  • 范例:为职工涨工资、总裁+1000、经理+800、其他人员400

–>使用pl/sql实现

PL/SQL程序语法

程序语法

declare
	说明部分(变量说明、游标申明、例外说明)
begin
	语句序列 (DML语句) ..
exception
	例外处理语句
End ;

常量和变量定义

变量的基本类型就是oracle中建表时字段的变量,如char,varchar2,date,number,boolean,long)

定义语法:

	varl char(15) ; 
	Psal number(9,2) ; 
	/*说明变量名、数据类型和长度,分号必给*/
###############################################	
	
	
	/*常量定义*/
	marrieed  boolean:=true
	##############################################


	/*引用变量*/
	Myname  emp.ename%type
	/*引用型变量Myname的类型与emp表中ename列的类型一样,在sql中使用into来赋值*/
	
	declare
	emprec emp.name%type ; 
	begin
	select t.ename into emprec from emp t where t.empno = 7369 ; 
	dbms_output.put_line(emprec) ; 
	/*输出emprec的值*/
	end ; 
	
	#############################################

/*记录型变量*/

	/*Emprec emp%rowtype*/
	/*记录变量分量(可以对应Java中的对象类型变量)的引用*/
	/*emp rec.ename:='ADAMS';*/
	declare
		p emp%rowtype;
		/*rowtype表emp中的一行当作一个类型*/
	begin
		select * into p from emp t where t.empno = 7369 ; 
		dbms_output_put_line(p.ename||''||p.sal) ; 
	/* ||表示连接 */ 
	end ; 
	
	

if分支

三种语法

  if  条件  then 语句1;
       语句2; 
       end if;
   if  条件  then  语句序列1else   语句序列 2end   if ;

if   条件  then 语句;
elseif  语句  then  语句;
else  语句;
end  if; 
  • 范例1:如果从控制台输入1则输出我是1
declare
	pnum number :=&num ; 
	/* :=& * 表示输入并赋值*/
begin
	if pnum =1 then
	dbms_output.put_line('我是1') ; 
	end if ; 
end ; 
	
  • 范例2:如果从控制台输入1则输出我是1否则输出我不是1
declare
	pnum number :=&num ; 
	/* :=& * 表示输入并赋值*/
begin
	if pnum =1 then
	dbms_output.put_line('我是1') ; 
	else
	dbms_output.put_line('我不是1') ; 
	end if ; 
end ; 
  • 范例3:判断人的不同年龄段18岁以下是未成年人,18岁以上40以下是成年人,40以上是老年人
declare
  mynum number := #
begin
  if mynum < 18 then
    dbms_output.put_line('未成年人');
  elsif mynum >= 18 and mynum < 40 then
    dbms_output.put_line('中年人');
  elsif mynum >= 40 then
    dbms_output.put_line('老年人');
  end if;
end;

LOOP循环语句

语法1

WHILE  total  <= 25000  LOOP
...
total : = total + salary;
END  LOOP; 

语法2(最常用)

Loop
EXIT [when   条件];
……
End loop

语法3

FOR   I   IN   1 . . 3    LOOP
语句序列 ;
END    LOOP ; 
  • 范例:使用语法1输出1到10的数字
declare
	step number :=1 ; 
begin
	while step<= 10 loop
	dbms_output.put_line(step);
	step := step + 1 ; 
	end loop ; 
	end ; 
  • 范例:使用语法2输出1到10的数字
declare
	step number :=1 ; 
begin
	loop
	exit when step>10 ; 
	dbms_output.put_line(step);
	step := step + 1 ; 
	end loop  ;
	end ; 
  • 范例:使用语法3输出1到10的数字
declare
	step number :=1 ; 
begin
	for step in 1..10 loop
	dbms_output.put_line(step);
	end loop ; 
	end ; 

游标Cursor

在写java程序中我们有集合的概念,那么在pl/sql中也会用到多条记录,这时我们就要用到游标,游标可以存储查询返回的多条数据

语法:

CURSOR  游标名  [ (参数名  数据类型,参数名 数据类型,...)]  IS  SELECT   语句;

例如:
curror cl is select ename from emp ; 
/*无参游标*/

使用步骤:

  1. 打开游标: open cl ;(打开游标执行查询)
  2. 取一行游标的值:fetch cl into pjob (取一行游标到变量中)
  3. 关闭游标: close cl ; (关闭游标并释放资源)
  4. 游标的结束方式: exit when cl%notfound

注意:上面的pjob必须与emp表中的job列类型一样

  • 范例:使用游标方式输出emp表中的员工编号和姓名
declare 

	/*无参游标,记录emp表所有*/
	cursor pc is 
		select * from emp ;
		
		/*定义记录型变量*/
		pemp emp%rowtype ;
		
		 begin
		 open pc ; 
		 loop
		 fetch pc 
		 into pemp 
		 exit when pc%notfound ; 
		  dbms_output.put_line(pemp.empno || ''|| pemp.ename);
		  end loop ; 
		  close pc ; 
		  end ; 
		 

SQL的高阶用法-SQL函数编程_第1张图片

  • 范例:按员工的工种涨工资、总裁1000元,经理长800元其他人员长400元。
declare
  cursor pc is
    select * from myemp;
  addsal myemp.sal%type;
  pemp   myemp%rowtype;
begin
  open pc;
  loop
    fetch pc
      into pemp;
    exit when pc%notfound;
    if pemp.job = 'PRESIDENT' then
      addsal := 1000;
    elsif pemp.job = 'MANAGER' then
      addsal := 800;
    else
      addsal := 400;
    end if;
    update myemp t set t.sal = t.sal + addsal where t.empno = pemp.empno;
  end loop;
  close pc;
end;

  • 范例:为部门号为10的员工涨工资
declare
  cursor pc(dno myemp.deptno%type) is
    select empno from myemp where deptno = dno;
   /*保存empno*/
  pno myemp.empno%type;
begin
/*打开游标并传入参数10 */
  open pc(10);
  
  loop
    fetch pc
      into pno;
    exit when pc%notfound;
  
    update myemp t set t.sal = t.sal + 1000 where t.empno = pno;
  end loop;
  close pc;
end;

例外

例外是程序设计语言提供的一种功能,用来增强程序的健壮性和容错性

系统定义的例外

  • no_data_found (没有找到数据)
  • too_many_rows (select …into语句匹配多个行)
  • zero_divide ( 被零除)
  • value_error (算术或转换错误)
  • timeout_on_resource (在等待资源时发生超时)

系统例外-示例

declare
  pnum number;
begin
  pnum := 1 / 0;
exception
  when zero_divide then
    dbms_output.put_line('被0除');
  when value_error then
    dbms_output.put_line('数值转换错误');
  when others then
    dbms_output.put_line('其他错误');
end;

用户自定义例外,在声明中来定义例外

DECLARE
My_job   char(10);
v_sal   emp.sal%type;
No_data    exception; /*定义异常于此*/
cursor c1 is select distinct job from emp    order by job;
  • 查询部门编号为50的员工
declare
	/*异常定义*/
  no_emp_found exception;
  cursor pemp is
    select t.ename from emp t where t.deptno = 50;
    /*存储*/
  pename emp.ename%type;
  
begin
  open pemp;
  fetch pemp
    into pename;
  if pemp%notfound then
  /*抛出异常*/
    raise no_emp_found;
  end if;
  close pemp;
exception /*例外处理语句*/
  when no_emp_found then
    dbms_output.put_line('没有找到员工');
  when others then
    dbms_output.put_line('其他错误');
end;

存储过程

存储过程(Stored Procedure)是在大型数据库系统中,一组为了完成特定功能的SQL 语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。存储过程是数据库中的一个重要对象,任何一个设计良好的数据库应用程序都应该用到存储过程

创建存储过程语法:

create [or replace] PROCEDURE 过程名[(参数名 in/out 数据类型)]  
AS 
begin
        PLSQL子程序体;
End;

或者

create [or replace] PROCEDURE 过程名[(参数名 in/out 数据类型)]  
is
begin
        PLSQL子程序体;
End  过程名;
  • 范例:创建一个输出helloworld的存储过程
create or replace procedure helleworld is
begin
  dbms_output.put_line('helloworld');
end helloword;

  /*调用存储过程*/
begin
helloworld
end;

  • 范例:给指定的员工涨100工资、并打印出涨前和涨后的工资
create or replace procedure addSal(eno in number) is
  pemp myemp%rowtype;
begin
  select * into pemp from myemp where empno = eno;

  update myemp set sal = sal + 100 where empno = eno;
  dbms_output.put_line('涨工资前' || pemp.sal || '涨工资后' || (pemp.sal + 100));
end addSal ; 

/*调用*/
begin
addsal(eno => 7902) ; 
commit ; 
end ; 

存储函数

create or replace function 函数名(Name in type, Name in type, ...) return 数据类型 is
  结果变量 数据类型;
begin
  
  return(结果变量);
end函数名;

存储过程和存储函数的区别

一般来讲,过程和函数的区别在于函数可以有一个返回值而过程没有返回值。 但过程和函数都可以通过out指定一个或多个输出参数。我们可以利用out参数,在过程和函数中实现返回多个值。

范例:使用存储函数查询指定员工的年薪

create or replace function empincome(eno in emp.empno%type)
return number is
psal emp.sal%type ;
pcomm emp.comm%type ;
begin
select t.sal into psal from emp t where t.empno = eno ;

return psal*12+nvl(pcomm,0) ;
end ;

存储过程版:

create or replace procedure empincomep(eno in emp.empno%type, income out number) is
				/*income out -- > return*/

  psal  emp.sal%type;
  pcomm emp.comm%type;

begin

  select t.sal, t.comm into psal, pcomm from emp t where t.empno = eno;
  income := psal * 12 + nvl(pcomm, 0);
  /*can return */
end empincomep ; 

/*调用*/

调用:
declare
  income number;
begin
  empincomep(7369, income);
  dbms_output.put_line(income);
end;

使用JAVA代码调用存储过程和存储函数

/*存储过程*/
Class.forName("oracle.jdbc.OracleDriver");
		Connection conn = null;
		conn = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:orcl",
				"scott", "tiger");
/*获得CallableStatment对象,指定存储函数*/
		CallableStatement call = conn.prepareCall("{call countyearsal(?,?)}");
		/*1代表第一个参数(?)*/
		call.setInt(1, 7369);
/*注册oracle的输出参数类型*/
		call.registerOutParameter(2, OracleTypes.NUMBER);
/*执行存储过程*/
		call.execute();
/*获得返回值*/
		int sum = call.getInt(2);
		System.out.println(sum);

/*存储函数*/		Class.forName("oracle.jdbc.OracleDriver");
		Connection conn = null;
		conn = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:orcl",
				"scott", "tiger");
/*获得CallableStatment对象,指定存储函数*/
	CallableStatement call = conn.prepareCall("{?= call countyearsal1(?)}");
/*注册oracle的输出参数类型*/
		call.registerOutParameter(1, OracleTypes.NUMBER);
		call.setInt(2, 7369);
/*执行存储函数*/
		call.execute();
/*获得返回值*/
		int sum = call.getInt(1);
		System.out.println(sum);

触发器

  • 语句级:在指定的操作语句操作之前或操作之后执行一次
  • 行级(for each row):触发语句作用每一条记录都被触发、在行级触发器中使用old和new伪记录变量、识别值的状态

语法:

CREATE  [or REPLACE] TRIGGER  触发器名
   {BEFORE | AFTER}
   {DELETE | INSERT | UPDATE [OF 列名]}
   ON  表名
   [FOR EACH ROW [WHEN(条件) ] ]
begin
   PLSQL 块 
End 触发器名
  • 范例:插入员工后打印一句话“一个新员工插入成功”
create or replace trigger testTrigger
after insert on person 

declare 
begin
 dbms_output.put_line('一个员工被插入');
 end testTrigger  ;
 
  • 范例:不能在休息时间插入员工
create or replace trigger validInsertPerson
  before insert on person

declare
  weekend varchar2(10);
begin
  select to_char(sysdate, 'day') into weekend from dual;
  if weekend in ('星期一') then
    raise_application_error(-20001, '不能在非法时间插入员工');
  end if;
end validInsertPerson;

在触发器中触发语句与伪记录变量的值

触发语句\ :old :new
Insert 所有字段都是空(null) 将要插入的数据
Update 更新以前该行的值 更新后的值
delete 删除以前该行的值 所有字段都是空(null)
  • 范例:判断员工涨工资之后的工资的值一定要大于涨工资之前的工资
create or replace trigger addsal4p
  before update of sal on myemp
  for each row
begin
  if :old.sal >= :new.sal then
    raise_application_error(-20002, '涨前的工资不能大于涨后的工资');
  end if;
end;

调用
update myemp t set t.sal = t.sal - 100 where t.empno = 7369;

你可能感兴趣的:(数据库)