1 Oracle基础介绍
1.1 基本概念
1.1.1 实例
往往是数据库里面一些相关的进程启动起来,调入到内存当中,共同起到一个协调的作用;在Oracle中,表、视图、触发器、存储过程等等均称之为“数据对象”
1.1.2 用户管理
选择口令管理,主要观察以下几个用户:
Ø 普通用户: scott/tiger
Ø 普通管理员:system/manager
Ø 超级管理员:sys/change_on_install
1.1.3 服务项管理
除以下两个服务外,其他的服务“自动”设置为“手动”(以Oracle10g作说明)
Ø OracleOraDb10g_home1TNSListener:表示监听服务,如果客户端想连接到数据库,此服务必须打开,在日后的程序开发中此服务起作用
Ø OracleServiceMLDN:表示数据库的主服务,命名规则为:OracleService数据库名称。此服务必须启动,否则Oracle根本就无法运行
1.2 SQL简单介绍
SQL(Structured Query Language,结构查询语言)是一个功能强大的数据库语言,有如下三种简单划分:
Ø DML(Data Manipulation Language,数据操作语言)——用于检索或者修改数据
Ø DDL(Data Definition Language,数据定义语言)——用于定义数据的结构,如创 建、修改或者删除数据库对象
Ø DCL(Data Control Language,数据控制语言)——用于定义数据库用户的权限
1.3 SQL常用命令
1)set linesize 长度:设置每行显示的长度
2)set pagesize 行数:设置每页显示记录的行数
3)show user:显示当前连接的用户是哪一个
4)select * from tab:得到当前用户下一个数据库中全部表的名称
5)desc 表名称:查看一个表的完整结构
6)ed及@指令
Ø SQL>ed 打开一个类似文本的编辑器
Ø SQL>@c:\a.txt 假设a.txt文本中的内容是SELECT * FROM emp;
7)连接:conn 用户名/密码 AS SYSDBA
2 简单查询语句
2.1 SELECT语句
SELECT { DISTINCT } *|具体的列 别名
FROM 表名
{ WHERE 条件}
{ GROUP BY 表达式 { HAVING 分组条件 } }
{ ORDER BY 排序的字段1,排序的字段2 ASC|DESC }
简单说明:
1)DISTINCT:消除重复的内容
2)没有内容表示NULL,不为空的话:IS NOT NULL
3)BETWEEN 最小值 AND 最大值
4)字段 IN (值1,值2,...,值n),如果要求查询的内容不在此范围中,则用NOT IN
5)使用LIKE语句:注意通配符的问题,有两种通配符:
Ø “%”:可以匹配任一长度的内容
Ø “_”:可以匹配一个长度的内容
6)在操作条件中,还可以使用:< <= = > >= 不等于符号:有两种形式: <> !=
7)对结果进行排序:ORDER BY
2.2 单行函数
语法:function_name(column|expression,[arag1,arg2,...])
2.2.1单行函数分类
共有5种:
Ø 字符:专门处理字符的,比如大小写变化、求出字符的长度
Ø 数值
Ø 日期
Ø 转换
Ø 通用(NVL、DECODE)
2.2.2 字符函数
UPPER(STRING):转换成大写
LOWER(STRING):转换成小写
INITCAP(STRING):将字母首字母大写
CONCAT(STR1,STR2):连接字符串
substr(String,int,int):第二个int可以取负值,倒过来截取字符串
length(String):返回当前字符串的长度
replace('hello','l','x'):替换字符串,当前返回值是hexxo
2.2.3 数值函数
四舍五入:ROUND(number[,int])
截断小数位:TRUNC(number[,int]):不会四舍五入,可以取负值
取余(取模):MOD(number,number)
2.2.4 日期函数
MONTHS_BETWEEN(sysdate):求出给定日期范围的月数
ADD_MONTHS(sysdate,4):在指定日期上加上指定的月数,求出之后的日期
NEXT_DAY(sysdate,'星期一'):下一个的今天是哪一个日期
LAST_DAY(sysdate):求出给定日期的最后一天日期
2.2.5 转换函数
TO_CHAR():数字9表示一位数字 to_char(number,'99,999')
TO_NUMBER():转换成数字
TO_DATE():转换成日期
2.2.6 通用函数
NVL(STR1,STR2):如果STR1的值为null,则返回STR2的值
DECODE(column|expression search1,result1 [,search2,result2,...][default]):如果column|expression的值与searchi相比较,如果相同的话,则返回resulti,否则返回default的值
3 多表查询
3.1左、右连接
select * from emp,dept where emp.deptno(+)=d.deptno;
(+)左: =左边表示右连接
(+)右: =右边表示左连接
3.2 SQL:1999语法对SQL的支持
3.2.1 交叉连接(CROSS JOIN)
产生笛卡尔积
emp,dept 等价于 emp cross join dept
3.2.2 自然连接(NATURAL JOIN)
自动进行关联字段的匹配
3.2.3 USING子句
直接关联的操作列
select * from emp e join dept d using(deptno) where deptno=30;
3.2.4 ON子句
用户自己编写连接的条件
select * from emp e join dept d on(e.deptno=d.deptno) where deptno=30;
3.2.5 左|右(外)连接
LEFT|RIGHT OUTER JOIN
4 组函数 分组统计
分组:例如把男生分为一组,女生分一组
4.1 常用的分组函数
COUNT():求出全部的记录数
MAX():求出一组中的最大值,一般用于针对数字的应用上
MIN():求出最小值
AVG():求出平均数
SUM():求和
4.2 分组统计
语法:GROUP BY 分组条件
范例:select deptno,count(empno) from emp报错:ORA-00937:不是单组分组函数 范例:select deptno,empno,count(empno) from emp group by deptno报错:ORA-00979:(empno) 不是GROUP BY 表达式 |
原因:
1)如果程序中使用了分组函数,则有两种可以使用的情况:
Ø 程序中存在了GROUP BY,并指定了分组条件,可以将分组条件一起查询出来
Ø 如果不使用分组的话,则只能单独的使用分组函数
2)在使用分组函数的时候,不能出现分组函数和分组条件之外的字段
范例:select demptno,AVG(sal) from emp where AVG(sal)>2000 GROUP BY deptno报错: |
此处不允许使用分组函数
3)分组函数只能在分组中使用,不允许在WHERE语句之中出现,可通过HAVING分组条件指令完成相应功能
范例:select demptno,AVG(sal) from emp GROUP BY deptno HAVING AVG(sal)>2000 |
4.3 分组的简单原则
1)只要一列上存在重复的内容才有可能考虑到分组
2)分组函数可以嵌套使用,但是在组函数嵌套使用的时候不能再出现分组条件的查询语句
5 子查询
在一个查询的内部还包含一个查询。所有的子查询必须在“()”中编写,分为三类:
Ø 单列子查询:返回的结果是一列的一个内容,出现的几率最高
Ø 单行子查询:返回多个列,有可能是一条完整的记录
Ø 多行子查询:返回多条记录
在子查询中,存在以下三种查询的操作符号:
5.1 IN
指定一个查询的范围
5.2 ANY
Ø =ANY:与IN的操作符功能完全一样
Ø >ANY:比里面最小的值大
Ø <ANY:比里面最大的值小
5.2 ALL
Ø >ALL:比里面最大的值大
Ø <ALL:比里面最小的值要小
6 数据库更新操作
6.1查询操作
SELECT
6.2 更新操作
INSERT UPDATE DELETE
6.3 表的复制
执行以下语句后,会将表结构及数据完整的复制出来
CREATE TABLE myemp AS SELECT * FROM EMP;
7 事务处理
7.1 定义
所谓事务处理,就是保证数据操作的完整性,所有的操作要么同时成功,要么同时失败
7.2 本质
1)在Oralce中对于每一个连接到数据库的窗口(sqlplus、plsql developer)连接之后,实际上都会与数据库的连接建立一个session,即:每一个连接到数据库上的用户都表示创建了一个session。
2)一个session对数据库所做的修改,不会立刻反映到数据库的真实数据之上,是允许回滚的,当一个session提交所有的操作之后,数据库才真正的做出修改。
7.3 事务处理
在数据库的操作中提供了以下的两个主要命令完成事务的处理:
Ø 提交事务:commit
Ø 回滚事务:rollback
注意:如果数据已经提交了,则肯定无法回滚
7.4 死锁
在Oracle中关于事务的处理上也会存在一种死锁的概念(即,一种等待的状态),换句话说,一个session如果更新了数据库中的记录,其他session事无法立刻更新的,要等待对方提交之后才允许更新。
8 表的管理
8.1 建表
语法:CREATE TABLE 表名 AS 子查询
说明:
1)如果现在子查询写的是:SELECT * FROM emp,表示将表结构和表内容一起复制
2)如果现在子查询写的是:SELECT * FROM emp where 1=2,加入了一个永远不可能成立的条件,则此时表示的是只复制表结构,但是不复制表内容
8.2 表的修改
8.2.1 增加列
语法:ALTER TABLE 表名称 ADD (列的名称 列的类型 DEFAULT 默认值, 列的名称 列的类型 DEFAULT 默认值,... ) |
8.2.2 修改列
语法:ALTER TABLE 表名称 MODIFY (列的名称 列的类型 DEFAULT 默认值)
8.3 为表重命名
语法:RENAME 旧的表名称 TO 新的表名称 (只能在Oralce中使用)
8.4 截断表
如果将某表中的一条数据使用DELETE语句删除了,则可以通过rollback进行回滚,如果现在假设要想清空一张表的数据,但是同时又不需要回滚,可以立刻释放资源,就需要使用截断表的语法,如下所示:
TRUNCATE TABLE 表名称;
9 约束
9.1 约束的分类
在实际中,约束只要分为以下五种约束:
Ø 主键约束 PRIMARY KEY:主键表示一个唯一的标识,本身不能为空
|- 例如:身份证编号是唯一的标识,本身不能为空
Ø 唯一约束 UNIQUE:在一个表中只允许建立一个主键约束,而其他列如果不希望出现重复值的话,则就可以使用唯一约束
Ø 检查约束 CHECK:检查一个列的内容是否合法
|- 例如:年龄,只能在0~150 CHECK CHECK(age BETWEEN 0 AND 150)
|- 例如:性别,只能是男、女、中性 CHECK(sex IN ('男','女'))
Ø 非空约束 NOT NULL:姓名这样的字段里面的内容就不能为空
Ø 外键约束 FOREIGN KEY:在两张表中进行约束操作
9.2 修改表的约束
----单表约束----
ALTER TABLE 表名称 ADD CONSTRAINT 约束名称 约束类型(约束字段)
----主外键约束,两张表----
ALTER TABLE 表名称 ADD CONSTRAINT 约束名称 约束类型(约束字段) REFERENCE 父表名(参考字段) ON DELETE CASCADE |
9.3 删除约束
ALTER TABLE 表名称 DROP CONSTRAINT 约束名称
9.4 综合练习
CREATE TABLE person( pid varchar2(18), name varchar2(20) NOT NULL, age number(3) NOT NULL, birthday DATE, sex varchar2(4) DEFAULT '男', CONSTRAINT person_pid_pk PRIMARY KEY(pid), CONSTRAINT person_name_uk UNIQUE(name), CONSTRAINT person_age_ck CHECK(age BETWEEN 0 AND 150), CONSTRAINT person_sex_ck CHECK(sex IN ('男','女','中')) );
CREATE TABLE book( bid number PRIMARY KEY NOT NULL, bname varchar2(80), bprice number(5,2), pid varchar2(18), CONSTRAINT person_book_pid_fk FOREIGN KEY(pid) REFERENCES person(pid) ON DELETE CASCADE ); |
案例
--约束:primary key,not null, check, unique, foreign key create table t_goods( goodId char(8) primary key,--主键 goodName varchar2(30), unitprice number(10,2) check (unitprice>0), catagory varchar2(8), provider varchar2(30) );
create table t_customer( customerId char(8) primary key, customerName varchar2(50) not null, address varchar2(50), email varchar2(50) unique, sex char(4) default '男' check (sex in('男','女')), cardId char(18) );
create table t_purchase( customerId char(8) references t_customer(customerId), goodId char(8) references t_goods(goodId), nums number(10) check (nums between 1 and 30) );
--alter table为表增加约束 --增加not null约束,需要使用modify选项 --增加其他四种约束使用add选项 --例1:增加not null约束 alter table t_goods modify goodName not null; --例2:增加unique约束 alter table t_customer add constraint cardunique unique(cardId); --例3:增加check约束 alter table t_customer add constraint addresscheck check(address in ('南京','扬州')); --例4:增加外键约束 alter table t_purchase add constraint goodIdpk foreign key(goodId) references t_goods(goodId); --删除约束 alter table 表名 drop constraint 约束名称;
--列级定义、表级定义
--设置查询所需时间的输出 set timing on; |
10 ROWNUM
1)表示行号,实际上这是一个列,但是这个列是一个伪列,此列可以在每张表中出现
2)在程序开发中用于分页,ROWNUM不支持BETWEEN...AND...的操作,只能使用逻辑运算符进行比较
3)范例
select * from (select rownum rid ,id,title,content from forum where rownum<=10) forum3 where forum3.rid>5; |
11 集合操作
11.1 集合操作
1)在Oralce中提供了是三种类型集合操作:并(UNION)、交(INTERSECT)、差(MINUS)
Ø UNION:将多个查询的结果组合到一个查询结果之中,没有重复内容
Ø UNION ALL:也是将多个查询结果组合到一个查询之中,但是包含重复值
Ø INTERSECT:返回多个查询结果中相同的部分
Ø MINUS:返回两个查询结果的差集
2)集合操作
语法:SELECT语句1 UNION|UNION ALL|INTERSECT|MINUS SELECT语句2
12 视图
12.1 介绍
视图是一个虚拟表,其内容由查询定义。同真实的表一样,视图包含一系列带有名称的列和行数据。但是,视图并不在数据库中以存储的数据值集形式存在,一个视图实际上就是封装了一条复杂的查询语句。行和列数据来自由定义视图的查询所引用的表,并且在引用视图时动态生成
12.2视图与表的区别
1)表需要占用磁盘空间,视图不需要
2)视图不能添加索引
3)使用视图可以简化复杂查询,比如:学生选课系统
4)视图有利于提高安全性,比如:不同用户查看不同视图
12.3 创建视图
语法:CREATE OR REPLACE VIEW 视图名称 AS 子查询 {WITH CHECK OPTION|WITH READ ONLY}
说明:
1)WITH CHECK OPTION:不能更新视图的创建条件
2)WITH READ ONLY:创建的视图只读,不能执行更新任意列等操作
12.4 删除视图
语法:DROP VIEW 视图名称
12.5 案例
--创建视图,把emp表的sal<1000的雇员映射到该视图(view) create view my_view as select * from emp where sal<1000;
--为简化操作,用一个视图解决显示雇员编号,姓名和部门名称 create view my_view2 as select emp.empno,emp.ename,dept.dname from emp,dept where emp.deptno=dept.deptno; |
13 同义词 序列 索引
13.1 序列
13.1.1 创建序列
语法:CREATE SEQUENCE 序列名 [INCREMENT BY n] [START WITH n] [{MAXVALUE/ MINVALUE n|NOMAXVALUE}] [{CYCLE|NOCYCLE}] [{CACHE n|NOCACHE}]; |
13.1.2删除序列
语法:DROP SEQUENCE 序列名; |
13.1.3查询下一个序列
语法:select SEQ_表名.nextval from dual; 范例:select seq_test.nextval from dual; |
13.2 同义词
相当于“别名”的意思,其好处是可以越过用户权限访问数据库,如DUAL是sys用户下的表,但是在scott用户下也可以访问,就是因为建立了同义词
语法:create synonym 同义词名 for 用户名.数据库名; |
13.3 索引
用于加速数据存取的数据对象
语法:create index 索引名 on 表名(列名) 范例:create index nameIndex on t_customer(customerName); |
13.4 综合练习
--建表,以作测试之用 SQL> create table forum(id number(10),title varchar2(30),content varchar(30)); --建立主键 SQL>alter table forum add constraint f_pk primary key (id); --创建sequence SQL> create sequence myseq increment by 1 start with 1; --创建synonym SQL> create synonym forum2 for apps.forum; --重复输入以下数据8次,以作测试数据 SQL> insert into table forum(myseq.nextval,'半月谈','政治敏感话题'); --查询结果 SQL> select * from forum2; SQL> select * from forum; --结果显示 ID TITLE CONTENT ----------- ------------------------------ ------------------------------ 1 半月谈 政治敏感话题 2 半月谈 政治敏感话题 3 半月谈 政治敏感话题 4 半月谈 政治敏感话题 5 半月谈 政治敏感话题 6 半月谈 政治敏感话题 7 半月谈 政治敏感话题 8 半月谈 政治敏感话题 8 rows selected |
14 用户管理
14.1 创建用户
在oracle中可以对用户进行建立以及授权的操作,只有在超级管理员登录的情况下才可以创建用户
创建用户的语法:CREATE USER 用户名 IDENTIFIED BY 密码 范例:CREATE USER test IDENTIFIED BY test123 |
14.2 授权
1)然后授权,使用语法如下:
授权语法:GRANT 权限1,权限2,... TO 用户 范例:GRANT CREATE SESSION TO test |
2)实际上,一个新的用户所有的权限都要分别赋予,如果现在假设要想把多个权限一次性赋予一个用户,则可以将这些权限定义成一组角色。在Oracle中提供了两个主要角色:CONNECT 、RESOURCE,可以直接把这两个角色赋予test
范例:GRANT CONNECT,RESOURCE TO test; |
3)如果需要访问其它用户的表,则需要授予此张表相应的权限
范例:GRANT SELECT,DELETE ON scott.emp TO test; |
14.3 修改用户密码
1)超级管理员可以修改一般用户的登录密码:
语法:ALTER USER 用户名 IDENTIFIED BY 密码 |
2)手工让用户的登录密码失效:
语法:ALTER USER 用户名 PASSWORD EXPIRE |
14.4 用户锁定设置
可以使用命令锁住一个用户:
加锁语法:ALTER USER 用户名 ACCOUNT LOCK 解锁语法:ALTER USER 用户名 ACCOUNT UNLOCK |
14.5 回收权限
语法:REVOKE 权限 ON 用户.表名称 FROM 用户 |
15数据库的备份与恢复
15.1 备份与回复
Ø 数据库备份:exp
Ø 数据库恢复:imp
15.2 操作步骤
在d盘建立一个data的文件夹,在此文件夹之中保存所有的备份文件,如果要备份,则需要使用命令行方式,进入到d:\data文件夹之中;恢复备份数据也是一样的步骤
16 嵌套表
16.1 定义
所谓嵌套表,是指一个表中还含有子表
16.2 范例
但是,如果想完成一个嵌套表的制作,则首先要保证一点:因为数据库在创建数据表的时候都要指定字段的类型,所以嵌套表本身也需要同样指定类型,那么这种类型就需要单独定义
create type project_ty as OBJECT( proid number(4), proname varchar2(50), prodate date ); |
类型创建成功之后,并不意味着类型可以直接使用,因为此类型是一个完整的类型,所以要为此类型指定一个名称
create type project_nt as table of project_ty; |
以上的操作表示以后可以直接使用project_nt表示project_ty类型,就类似于varchar2表示字符串是一样的。此时可以使用此类型创建department表
create table department( depno number(2) primary key not null, dname varchar2(50) not null, projects project_nt )nested table projects store as project_nt_tab_temp; |
添加测试数据
insert into department(depno,dname,projects) values(1,'技术部', project_nt( project_ty(1001,'ERP',sysdate), project_ty(1002,'CRM',sysdate), project_ty(1003,'HFM',sysdate) ) ); |
查询
SQL> select * from department;
DEPNO DNAME PROJECTS ----- -------------------------------------------------- -------- 1 技术部 <Object>
SQL> select * from table 2 (select projects from department where depno=1);
PROID PRONAME PRODATE ----- -------------------------------------------------- ----------- 1001 ERP 1/18/2011 1 1002 CRM 1/18/2011 1 1003 HFM 1/18/2011 1 |
更新
update table (select projects from department where depno=1) pro set value (pro)=project_ty('1001','测试',to_date('2011-01-01','yyyy-mm-dd')) where pro.proid=1001; |
17 可变数组
17.1 定义
属于嵌套表的升级版,在可变数组中,实际上就是将内部的嵌套表的内容的长度进行了限制。
17.2 范例(待补充)
eg:一个部门有多个工人
--步骤一 create type worker_info as object( id number, name varchar2(50), sex varchar2(6) );
--步骤二 create type worker_info_list as varray(10) of worker_info;
drop table department; create table department( deptno number(2) primary key not null, dname varchar2(50) not null, workers worker_info_list );
--步骤三 insert into department(deptno,dname,workers) values (20,'后勤部', worker_info_list( worker_info(1,'张三','男'), worker_info(2,'李四','女'), worker_info(3,'王五','男') ) );
--步骤四 select * from table (select workers from department where deptno=20); |
18 数据库设计范式
第一范式(1NF)
数据库表中的字段都是单一属性的,不可再分。这个单一属性由基本类型构成,包括整型、实数、字符数、字符型、逻辑型、日期型等。
create table person( pidint primary key not null , pname varchar(50) , pinfo varchar(200) ) |
输入以下的测试数据:
insert into person(pid,pname,pinfo) values(1111,'张三','1983年11月23日出生,现在的住址:北京是西城区'); |
实际上,对于人员的信息来看,由以下基本分组成:
- 生日:1983年11月23日
- 省市:背景
- 地区:西城区
- 详细的信息:…
每个字段不可再分,所以,以上的数据库创建脚本修改如下:
create table person( pid int primary key not null , pname varchar(50) , birthday datetime , area varchar(200) , subareavarchar(200) , addressvarchar(200) ) |
当然,以上的划分也要有一些注意点:
- 现在假如有如下一种设计,将姓名分成姓和名两个列
create table person( pid int primary key not null , 姓 varchar(50) , 名 varchar(50) , birthday datetime , area varchar(200) , subareavarchar(200) , addressvarchar(200) ) |
所以,要保证数据表中每一个字段都不可再分。
例如,如下的数据库表实符合第一范式的:
子段1 | 子段2 | 子段3 | 子段4 |
而这样的数据库表是不符合第一范式的:
子段1 | 子段2 | 子段3 | 子段4 |
子段3.1 子段3.2 |
第一范式(2NF)
数据库表中不存在非关键字段对任一候选关键字段的部分函数依赖(部分函数依赖指的是存在组合关键字中的某些子段决定非关键字段的情况),也即所有非关键字段都完全依赖于任意一组候选关键字。
假定选课关系表为SelectCourse(学号,姓名,年龄,课程名称,成绩,学分),关键字为组合关键字(学号、课程名称),因为存在如下决定关系:
(学号,课程名称)→(姓名,年龄,成绩,学分)
这个数据库表不满足第二范式,因为存在如下决定关系:
- (课程名称)→(学分)
- (学号)→(姓名,年龄)
即,存在组合关键字中的字段决定非关键字的情况。
第一范式的要求非常简单,就是要保证每个列的有意义。但是如果所有的操作都是用第一范式,也会存在问题:
现在建立一张学生选课表:学号、姓名、年龄、课程名称、成绩、学分
create table selectcourse( stuid int , stuname varchar(50) , stuage int , cname varchar(50) , grade int , credit int , ); |
以上的数据库脚本,符合第一范式的要求,但是如果现在按照第一范式设计的话,则也会存在问题:
insert into selectcourse values('s001','张三',20,'JAVA',88,3); insert into selectcourse values('s002','李四',20,'JAVA',80,3); insert into selectcourse values('s003','王五',20,'JAVA',85,3); |
从以上的数据库创建脚本可以发现,所有的课程信息冗余了,而且还存在一下问题:
- 如果一门课程没有一个学生选择,则此课程就从学校彻底消失了
- 课程中本身也应该包含一个课程的编号;但是如果按照以上的设计,则课程编号肯定重复
- 如果要更改课程信息,则需要更改许多条记录
使用第二范式修改数据库脚本:
- 学生应该是一个实体表的信息
create table student( stuid int primary key not null , stuname varchar(50) , stuage int , ); |
- 课程也应该是一个实体表的信息
create table course( cid int primary key not null , cname varchar(50) , credit int , ); |
- 学生选课:一个学生可能选多门课程,一门课程会有多个学生参加,每隔学生的每个课程成绩不一样,应该建立一张关系表,表示出以上的概念
create table selectcourse( stuid int , cid int , grade int , 加入外键关联,因为学生没了,成绩就没了,因为课程没了,成绩也就没了 ); | |
插上数据:
以上设计解决了一下问题:
- 学生不选课的时候,课程信息不会消失
- 更新课程的时候直接更新课程表即可
- 所有的关联关系在关系表现中体现
也就是说现在完成了一个多-多的关系。
第三范式(3NF)
假定学生关系表为Student(学号,姓名,年龄,所在学院,学院地点,学院电话),关键字为单一关键字“学号”,因为存在如下决定关系:
(学号)→(姓名,年龄,所在学院,学院地点,学院电话)
这个数据库是符合2NF的,但是不符合3NF,因为存在如下决定关系:
(学号)→(所在学院)→(学院地点,学院电话)
即,存在非关键字段“学院地点”、“学院电话”对关键字段“学号”的传递函数依赖。也会存在数据冗余、更新异常、插入异常和删除异常的情况。
在实际开发中,第三范式使用的频率是最多的。
例如:现在要求设计一张学生表,包含学号、姓名、年龄、所在院校、学院地址、学院电话,此时肯定不能使用第一范式,但是现在如果使用是第二范式呢?
按照这种设计,一个学生可以同时在多个学生可以在多个学院同时上课,多个学院会同时有同一个学生。此时,最好的做法是:一个学生包含多个学生,一个学生属于一个学院。实际上,此设计就完全类似于部门和雇员表的设计结构。
create table student( stuid int , stuname varchar(50) , stuage int , );
create table college( cid int , cname varchar(50) , caddress varchar(50) , ctel varchar(50) , );
create table studentcollege( stuid int , cid int , 设置主外键关系 ); |
是一个很明确的一对多的关系设计。
以上的三个范式只能算是参考,如果真的按照此种方式设计数据库,则有够累,数据库唯一原则:
- 数据库表的关联查询越少越好,SQL语句的复杂度越低越好
修改设计:
把学生关系表分为如下两个表:
- 学生:(学号,姓名,年龄,所在学院);
- 学院:(学院,地点,电话)
这样的数据库表是符合第三范式的,消除了数据冗余、更新异常、插入异常和删除异常。
19 PLSQL
19.1 pl/sql初步介绍
1、 期望目标
1) 掌握oracle的pl/sql概念
2) 掌握pl/sql编程技术(包括编写过程、函数、触发器…)
2、 pl/sql的介绍-pl/sql是什么?
1) pl/sql(procedural language/sql)是oracle在标准的sql语言上的扩展。pl/sql不仅允许嵌入sql语言,还可以定义变量和常量,允许使用条件语句和循环语句,允许使用例外处理各种错误,这样使得它的功能变得更加强大。
2) 了解过程,函数,触发器
- 过程、函数、触发器是pl/sql编写,相当于存储在oracle数据库中的对象
- 过程、函数、触发器是在oracle中
- pl/sql是非常强大的数据库过程语言,使用pl/sql能够写变量,做判断;可以简化复杂度;减少网络的传销,增加程序的模块化编程,提高运行性能,使速度更加的快捷
- 过程、函数可以在Java程序中调用
3、 pl/sql的介绍-为什么学?
1) 学习必要性
- 提高应用程序的运行性能
- 模块化的设计思想【分页的过程、订单的过程、转账的过程】
- 减少网络传输量
- 提高安全性
2) 弊端
- 移植性不好,例如从oracle数据库移植到DB2,则全部要重写
3) pl/sql的介绍-用什么编写pl/sql
- sqlplus开发工具
- pl/sql developer开发工具
4) 案例
- 编写一个存储过程,该过程可以向某表中添加记录
-- 1.创建一个简单的表
create table gdsc (id int,name varchar2(20),passwd varchar(20));
-- 2.创建过程
create or replace procedure sp_gdsc_01 is
begin
--执行部分
insert into gdsc values(1,'韩顺平','shunping');
end;
-- replace:表示如果有sp_gdsc_01,就替换
-- 3.如何查看错误信息
show error;
-- 如何调用该过程
-- 1.exec 过程名(参数值1,参数值2...);
-- 2.call 过程名(参数值1,参数值2...);
19.2 基础
1、 介绍
开发人员使用pl/sql编写应用模块时,不仅需要掌握sql语句的编写方法,还要掌握pl/sql语句及语法规则。pl/sql编程可以使用变量和逻辑控制语句,从而可以编写非常有用的功能模块。比如:分页存储过程模块、订单处理存储过程模块、转账存储过程模块等。而且如果使用pl/sql编程,我们可以轻松的完成非常复杂的查询要求。
2、 简单分类
|----------过程(存储过程)
|
|----------函数
块(编程)---------|
|----------触发器
|
|----------包
3、 编写规范
1) 注释
- 单行注释: --
- 多行注释: /* …… */
2) 标识符号的命名规范
- 当定义变量时,建议用v_作为前缀,如v_sal
- 当定义常量时,将以用c_作为前缀,如c_rate
- 当定义游标时,建议用_cursor作为后缀,如emp_cursor
- 当定义例外时,建议用e_作为前缀,如e_error
4、 pl/sql块介绍
1) 介绍
块(block)是pl/sql的基本程序单元,编写pl/sql程序实际上就是编写pl/sql块。要完成相对简单的应用功能,可能只需要编写一个pl/sql块;但是,如果要想实现复杂的功能,可能需要在一个pl/sql块中嵌套其他的pl/sql块。
2) 块结构示意图
- pl/sql块由三个部分构成:定义部分、执行部分、例外处理部分。如下所示:
declare
/* 定义部分:定义常量、变量、游标、例外、复杂数据类型,该部分是可选的 */
begin
/* 执行部分:要执行的pl/sql语句和sql语句,该部分是必须的 */
exception
/* 例外处理部分:处理运行的各种错误,该部分是可选的 */
end
/* 和java程序进行比较 */
3) pl/sql块的实例:实例1 - 只包含执行部分的pl/sql块
set serveroutput on --打开输出选项
begin
dbms_output.put_line('hello,world');
end;
相关说明:dbms_output是oracle所提供(类似java的开发包),该包包含一些过程,put_line就是dbms_output包的一个过程。
运行结果:
4) pl/sql块的实例:实例2 – 包含定义部分和执行部分的pl/sql块
declare
v_ename varchar2(5); --定义字符串变量
begin
select ename into v_ename from emp where empno=&no;
dbms_output.put_line('雇员名:'||v_ename);
end;
相关说明:&,取地址符表示要接收从控制台输入的变量
5) pl/sql块的实例:实例3 – 包含定义部分、执行部分和例外处理部分的pl/sql块
为了避免pl/sql程序的运行错误,提高pl/sql的健壮性,应该对可能的错误进行处理,这个很有必要:
- 比如在实例2中,如果输入了不存在的雇员号,应当做例外处理
- 有时出现异常,希望用另外的逻辑处理
declare
--定义字符串变量
v_ename varchar2(5);
v_sal number(7,2);
begin
--执行部分
select ename,sal into v_ename,v_sal from emp where empno=&no;
dbms_output.put_line('雇员名:'||v_ename||' 工资:'||v_sal);
--异常处理
exception
when no_data_found then
dbms_output.put_line('You enter the error employee number!');
end;
相关说明:Oracle事先预定了一些例外,no_data_found就是找不到数据的例外
5、过程
过程用于执行特定的操作。当建立过程时,既可以指定输入参数(in),也可以指定输出参数(out)。通过在过程中使用输入参数,可以将数据传递到执行部分;通过使用输出参数,可以将执行部分的数据传递到应用环境。在sqlplus中可以使用create procedure命令来建立过程。
实例如下:
1) 请考虑编写一个过程,可以输入雇员名,新工资,然后可以根据修改雇员的工资
create procedure sp_pro3(spName varchar2,newSal number) is
begin
--执行部分,根据用户名去修改工资
update emp set sal=newSal where ename=spName;
end;
2) 如何调用过程有两种方法:exec… call…
exec sp_pro3('SCOTT',4678);
3) 如何在java程序中调用一个存储过程
import java.sql.*;
public class TestOraclePro {
public static void main(String[] args) {
try {
// 1.加载驱动
Class.forName("oracle.jdbc.driver.OracleDriver");
// 2.得到连接
Connection ct = DriverManager.getConnection("jdbc:oracle:thin:@192.168.68.130:1521:orcl", "scott","tiger");
// 3.创建CallableStatement
CallableStatement cs = ct.prepareCall("{call sp_pro3(?,?)}");
// 4.给?赋值
cs.setString(1, "SMITH");
cs.setInt(2, 10);
// 5.执行
cs.execute();
// 6.关闭资源
cs.close();
ct.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
5、 函数
1) 函数用于返回特定的数据,当建立函数时,在函数头部必须包含return字句,而在函数体内必须包含return语句返回的数据。我们可以使用create function来建立函数,实际案例:
--函数案例
--输入雇员的姓名,返回该雇员的年薪
create function sp_fun2(spName varchar2) return number is yearSal number(7,2);
begin
--执行部分
select sal*12+nvl(comm,0)*12 into yearSal from emp where ename=spName;
return yearSal;
end;
2) 在sqlplus中调用该函数
3) 同样我们可以在java程序中调用该函数
select sp_fun2(‘SCOTT’) from emp;
6、 包
包用于在逻辑上组合过程和函数,它由包规范和包体两部分组成。
1) 我们可以使用create package命令来创建包,实例如下:
--创建包
--创建一个包sp_package
--声明该包有一个过程update_sal
--声明该包有一个函数annual_income
create package sp_package is
procedure update_sal(name varchar2,newSal number);
function annual_income(name varchar2) return number;
end;
2) 包的规范只包含了过程和函数的说明,但是没有过程和函数的实现代码。包体用于实现包规范中的过程和函数。建立包体可以使用create package body命令:
--给包sp_package实现包体
create package body sp_package is
procedure update_sal(name varchar2,newSal number)is
begin
update emp set sal=newSal where ename=name;
end;
function annual_income(name varchar2) return number is annual_salary number ;
begin
select sal*12+nvl(comm,0) into annual_salary from emp
where ename=name;
return annual_salary;
end;
end;
3) 如何调用包的过程或是函数?当调用包的过程或是函数时,在过程和函数前需要带有包名,如果要访问其它方案的包,还需要在包名前加方案名。如案例:
7、 触发器
1)触发器是指隐含的执行的存储过程。当定义触发器时,必须要指定触发的事件和触发的操作,常用的触发事件包括insert,update,select语句,而触发操作实际就是一个pl/sql块。可以使用create trigger来建立触发器。
特别说明:触发器可维护数据库的安全和一致性。
3) 什么是隐含执行?
一般我们不会主动调用它。当用户登陆的时候触发一件事情;或者当用户动一个表的时候,就可以修改另一张表,比如把一个外键删除了就把一个主键删除了,它可以连动触发。
19.3 定义并使用变量
1、 介绍
在编写pl/sql程序时,可以定义变量和常量;在pl/sql程序中包括有(主要是前三种):
1)标量类型(scalar)
2)复合类型(composite)
3)参照类型(reference)
4)lob(large Object)
2、 标量(scalar)-常用类型
1)在编写pl/sql块时,如果要使用变量,需在定义部分定义变量。pl/sql中定义变量和厂里的语法如下:
Identifier [constant] datatype [not null] [:=|default expr] |
identifier:名称
constant:指定常量。需要指定它的初始值,且其值是不能改变的
datatype:数据类型
not null:指定变量值不能为null
:= 给变量或是常量指定初始值,相当于java语言中的赋值运算符=
default:用于指定初始值
expr:指定初始值的pl/sql表达式,可是文本值、其他变量、函数等。
2)标量定义的案例
- 定义一个变长字符串
v_ename varchar2(10);
- 定义一个小数,范围-9999.99~9999.99
v_sal number(6,2);
- 定义一个小数并给一个初始值为5.4 :=是pl/sql的赋值号
v_sal number(6,2):=5.4;
- 定义一个日期类型的数据
v_hiredate date;
- 定义一个布尔变量,不能为空,初始值为false
v_valid boolean not null default false;
3)标量(scalar)- 使用标量
在定义好变量后,就可以使用这些变量。这里需要说明的是pl/sql块为变量赋值不同于其他的编程语言,需要在等号前加冒号(:=)
下面以输入员工号,显示雇员姓名、工资、个人所得税(税率为0.03)为例。说明变量的使用,看看如何编写:
declare
c_tax_rate number(3,2):=0.03;
--用户名
v_ename varchar2(5);
v_sal number(7,2);
v_tax_sal number(7,2);
begin
--执行
select ename,sal into v_ename,v_sal from emp where empno=&no;
--计算所得税
v_tax_sal:=v_sal*c_tax_rate;
--输出
dbms_output.put_line('姓名是:'||v_ename||' 工资:'||v_sal||' 交税:'||v_tax_sal);
dbms_output.put_line('hello,world');
end;
4)标量(scalar)- 使用%type类型
对于上面的pl/sql块有一个问题:就是如果员工的姓名超过了5个字符的话,就会有错误,为了降低pl/sql程序的维护工作量,可以使用%type属性定义变量,这样它会按照数据库列来确定你定义的变量的类型和长度。
使用格式:
标识符名 表名.列明%type
举例如下:
--声明的变量的类型跟表emp中的ename、sal的类型一致
v_ename emp.ename%type;
v_sal emp.sal%type;
3、 复合变量(composite)- 介绍
1)用于存放多个值的变量,主要包括这几种:
- pl/sql记录
- pl/sql表
- nested table(嵌套表)
- varray(变长数组)
2)复合类型-pl/sql记录
类似高级语言中的结构体,需要注意的是,当引用pl/sql记录成员时,必须要加记录变量作为前缀(记录变量.记录成员),如下:
--pl/sql纪录实例
declare
--定义一个pl/sql记录类型emp_record_type,类型包含三个数据:name,salary,title
type emp_record_type is record (name emp.ename%type,salary emp.sal%type,title emp.job%type);
--定义了一个sp_record变量,这个变量的类型是emp_record_type
sp_record emp_record_type;
begin
select ename,sal,job into sp_record from emp where empno=7788;
dbms_output.put_line('员工名:'||sp_record.name);
end;
3)复合类型-pl/sql表
相当于高级语言中的数组,但是需要注意的是在高级语言中数组的下标不能为负数,而pl/sql是可以为负数的,并且表元素的下标没有限制,实例如下:
--pl/sql表实例
declare
--定义了一个pl/sql表类型 sp_table_type,该类型是用于存放emp.ename%type
--index by binary_integer表示下标是整数
type sp_table_type is table of emp.ename%type index by binary_integer;
--定义了一个sp_table变量,这个变量的类型是sp_table_type
sp_table sp_table_type;
begin
select ename into sp_table(0) from emp where empno=7788;
dbms_output.put_line('员工名:'||sp_table(0));
end;
说明:sp_table_type 是pl/sql表类型
emp.ename%type 指定了表的元素的类型和长度
sp_table 为pl/sql表变量
sp_table(0) 则表示下标为0的元素
4、 参照变量
1) 介绍
参照变量是指用于存放数组指针的变量。通过使用参照变量,可以使得应用程序共享相同对象,从而降低占用的空间。在编写pl/sql程序时,可以使用游标变量(ref cursor)和对象类型变量(ref obj_type)两种参照变量类型
2) 参照变量-ref cursor游标变量
使用游标时,当定义游标时不需要指定相应的select语句,但是当使用游标时(open)需要指定selelct语句,这样一个游标就与一个select语句结合了。实例如下:
- 使用pl/sql编写一个块,可以输入部门号,并显示该部门所有员工姓名和他的工资
- 在上题的基础上,如果某个员工的工资低于200元,就增加100元
declare
--定义一个游标类型
type sp_emp_cursor is ref cursor;
--定义一个游标变量
test_cursor sp_emp_cursor;
--定义变量
v_ename emp.ename%type;
v_sal emp.sal%type;
begin
--把test_cursor和一个select结合
open test_cursor for select ename,sal from emp where deptno=&no;
--循环取出
loop
fetch test_cursor into v_ename,v_sal;
--判断是否test_cursor为空
exit when test_cursor%notfound;
dbms_output.put_line('名字:'||v_ename||' 工资:'||v_sal);
end loop;
end;
19.4 pl/sql的进阶
1、期望目标
1)掌握pl/sql的高级用法(能编写分页过程模块,下订单过程模块…)
2)会处理oracle常见的例外
3)会编写oracle各种触发器
4)理解视图的概念并能灵活使用视图
2、pl/sql的进阶-控制结构
1)介绍
在任何计算机语言(C、java、Pascal)都有各种控制语句(条件语句,循环结构,顺序控制结构),在pl/sql中也存在这样的控制结构
2)条件分支语句
pl/sql中提供了三种条件分支语句:
if-then
if-then-else
if-then-elsif-else
2.1 简单的条件判断 if-then
案例:编写一个过程,可以输入一个雇员名,如果该雇员的工资低于2000,就给该雇员工资增加10%
create or replace procedure sp_pro6(spName varchar2) is
--定义
v_sal emp.sal%type;
begin
--执行
select sal into v_sal from emp where ename=spName;
--判断
if v_sal<2000 then
update emp set sal=sal*1.1 where ename=spName;
end if;
end;
--调用
exec sp_pro6('ALLEN');
2.2 二重条件分支 if-then-else
案例:编写一个过程,可以输入一个雇员名,如果该雇员的补助不是0,就在原来的基础上增加100,;如果补助为0,就把补助设为200
create or replace procedure sp_pro7(spName varchar2) is
--定义
v_comm emp.comm%type;
begin
--执行
select comm into v_comm from emp where ename=spName;
--判断
if v_comm<>0 then
update emp set comm=comm+100 where ename=spName;
else
update emp set comm=comm+200 where ename=spName;
end if;
end;
2.3 多重条件分支 if-then-elsif-else
案例:编写一个过程,可以输入一个雇员编号,如果该雇员的职位是PRESIDENT,就给他的工资增加1000,如果该雇员的职位是MANAGER就给他的工资增加500,其他职位的雇员工资增加200
--if-then-elsif-else
create or replace procedure sp_pro8(spNo number) is
--定义
v_job emp.job%type;
begin
--执行
select job into v_job from emp where empno=spNo;
--判断
if v_job='PRESIDENT' then
update emp set sal=sal+1000 where empno=spNo;
elsif v_job='MANAGER' then
update emp set sal=sal+500 where empno=spNo;
else
update emp set sal=sal+200 where empno=spNo;
end if;
end;
3、循环语句 loop
是pl/sql中最简单的循环语句,这种循环语句以loop开头,以end loop结尾,这种循环至少会被执行一次;
案例:现有一张表users,表结构如下users(userID,userName),请编写一个过程,可输入用户名,并循环添加10个用户到users表中,用户编号从1开始增加。
--建表
create table users(userID number,userName varchar2(40));
--loop
create or replace procedure sp_pro9(spName varchar2) is
--定义
v_num number:=1;
begin
loop
insert into users values(v_num,spName);
--判断是否要退出循环
exit when v_num=10;
--自增
v_num:=v_num+1;
end loop;
end;
4、循环语句 while循环
基本循环至少要执行循环体一次,而对于while循环来说,只有条件为true时,才会执行循环体语句,while循环以while…loop开始,以end loop结束;
案例:现有一张users表,请编写一个过程,可输入用户名,并循环添加10个用户到users表中,用户编号从11号开始增加
create or replace procedure sp_pro10(spName varchar2) is
--定义
v_num number:=11;
begin
while v_num<=20 loop
--执行
insert into users values(v_num,spName);
--自增
v_num:=v_num+1;
end loop;
end;
5、循环语句 for循环
基本for循环的基本结构如下
begin
for i in reverse 1..10 loop
insert into users values(i,'shunping');
end loop;
end;
我们可以看到控制变量i,在隐含中就在不停的增加
6、顺序控制语句 goto、null
6.1 goto语句
goto语句用于跳转到特定标号去执行语句。注意由于使用goto语句会增加程序的复杂性,并使得应用程序可读性变差,所以在做一般应用开发时,建议大家不要使用goto语句。
基本语法如下:
goto label,其中label是已经定义好的标号名。
declare
i int :=1;
begin
loop
dbms_output.put_line('输出i='||i);
if i=10 then
goto end_loop;
end if;
i:=i+1;
end loop;
<<end_loop>>
dbms_output.put_line('循环结束');
end;
--打开输出语句的开关
SQL> set serveroutput on;
6.2 null语句
null语句不会执行任何操作,并且会直接将控制传递到下一条语句,使用null语句的主要好处是可以提高pl/sql的可读性
--null
declare
v_sal emp.sal%type;
v_ename emp.ename%type;
begin
select ename,sal into v_ename,v_sal from emp where empno=&no;
if v_sal<3000 then
update emp set comm=sal*0.1 where ename=v_ename;
else
null;
end if;
end;
19.5 pl/sql的进阶-编写分页过程
1、介绍
分页是任何一个网站(bbs、网上商城、blog)都会使用到的技术,因此学习pl/sql编程开发就一定要掌握该技术
2、无返回值的存储过程
首先,掌握最简单的存储过程,无返回值的存储过程:
案例:现有一张表book,表结构book(bookId,bookName,publishHouse),请编写一个过程,可以向book表中添加书,需要通过java程序调用。
--分页
create table book (bookId number,bookName varchar2(50),publishHouse varchar2(50));
--编写存储过程
--in:表示该变量是存储过程的输入参数,默认则为in
--out:表示一个输出参数
create or replace procedure sp_pro11(spBookId in number,spBookName in varchar2,spPublishHouse in varchar2) is
begin
insert into book values(spBookId,spBookName,spPublishHouse);
end;
--在java中调用
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
//调用一个无返回值的过程
public class TestPro11 {
public static void main(String[] args) {
try {
// 1.加载驱动
Class.forName("oracle.jdbc.driver.OracleDriver");
// 2.得到连接
Connection ct = DriverManager.getConnection(
"jdbc:oracle:thin:@192.168.68.130:1521:orcl", "scott","tiger");
// 3.创建CallableStatement
CallableStatement cs = ct.prepareCall("{call sp_pro11(?,?,?)}");
// 4.给?赋值
cs.setInt(1, 1);
cs.setString(2, "JAVA Swing");
cs.setString(3, "Public");
// 5.执行
cs.execute();
// 6.关闭资源
cs.close();
ct.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
3、有返回值的存储过程(非列表)
案例:编写一个过程,可以输入雇员的编号,返回该雇员的姓名。
--有输入和输出的存储过程
create or replace procedure sp_pro12(spno in number,spName out varchar2)is
begin
select ename into spName from emp where empno=spno;
end;
--在java中调用
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
//调用一个无返回值的过程
public class TestPro12 {
public static void main(String[] args) {
try {
// 1.加载驱动
Class.forName("oracle.jdbc.driver.OracleDriver");
// 2.得到连接
Connection ct = DriverManager.getConnection(
"jdbc:oracle:thin:@192.168.68.130:1521:orcl", "scott","tiger");
// 看看如何得到有返回值的存储过程
// 3.创建CallableStatement
CallableStatement cs = ct.prepareCall("{call sp_pro12(?,?)}");
// 4.给第1个?赋值
cs.setInt(1, 7788);
// 4.给第2个?赋值
cs.registerOutParameter(2, oracle.jdbc.OracleTypes.VARCHAR);
// 5.执行
cs.execute();
// 取出返回值,要注意问号的顺序
String name = cs.getString(2);
System.out.println("7788的名字:" + name);
// 6.关闭资源
cs.close();
ct.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
4、有返回值的存储过程(列表[结果集])
案例:编写一个过程,输入部门号,返回该部门所有雇员信息。对该题分析如下:
由于oracle存储过程没有返回值,它的所有返回值都是通过out参数来代替的,列表同样也不例外,但由于是集合,所以不能用一般的参数,必须要用package。所以要分为两部分;
1)建一个包,如下:
create or replace package testpackage as
type test_cursor is ref cursor;
end testpackage;
2)建立存储过程,如下:
create or replace procedure sp_pro13(spNo in number,sp_cursor out testpackage.test_cursor) is
begin
open sp_cursor for select * from emp where deptno=spNo;
end;
3)在java中调用
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
//调用一个无返回值的过程
public class TestPro13 {
public static void main(String[] args) {
try {
// 1.加载驱动
Class.forName("oracle.jdbc.driver.OracleDriver");
// 2.得到连接
Connection ct = DriverManager.getConnection(
"jdbc:oracle:thin:@192.168.68.130:1521:orcl", "scott","tiger");
// 3.创建CallableStatement
CallableStatement cs = ct.prepareCall("{call sp_pro13(?,?)}");
// 4.给第1个?赋值
cs.setInt(1, 10);
// 4.给第2个?赋值
cs.registerOutParameter(2, oracle.jdbc.OracleTypes.CURSOR);
// 5.执行
cs.execute();
// 得到结果集
ResultSet rs = (ResultSet) cs.getObject(2);
while (rs.next()) {
System.out.println(rs.getInt(1) + rs.getString(2));
}
// 6.关闭资源
cs.close();
ct.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
5、编写分页基础
要求:请编写一个存储过程,要求可以输入表名、每页显示记录数、当前页;返回总记录数,总页数,和返回的结果集,且返回的结果集按工资从低到高的顺序排序
--1.开发一个包
--使用:testpackage.test_cursor
--2.开始编写分页的过程
create or replace procedure fenye
(tableName in varchar2,
myPageSize in number,--页显示记录数
myPageNow in number,
myRowCount out number,--总记录数
myPageCount out number,--总页数
p_cursor out testpackage.test_cursor--返回的记录集
)is
--定义部分
--定义sql语句 字符串
v_sql varchar2(1000);
--定义两个整数
v_begin number:=(myPageNow-1)*myPageSize+1;
v_end number:=myPageNow*myPageSize;
begin
--执行部分
v_sql:='select * from (select t1.*,rownum rn from (select * from '|| tableName
||' order by sal) t1 where rownum<='|| v_end ||') where rn>='||v_begin;
--把游标和sql关联
open p_cursor for v_sql;
--计算myRowCount、myPageCount
--组织一个sql
v_sql:='select count(*) from '|| tableName;
--执行sql,并把返回的值,赋给myRowCount
execute immediate v_sql into myRowCount;
--计算myPageCount
if mod(myRowCount,myPageSize)=0 then
myPageCount:=myRowCount/myPageSize;
else
myPageCount:=myRowCount/myPageSize+1;
end if;
--关闭游标
--close p_cursor;
end;
--使用java测试
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
//调用一个无返回值的过程
public class TestProFenYe {
public static void main(String[] args) {
try {
// 1.加载驱动
Class.forName("oracle.jdbc.driver.OracleDriver");
// 2.得到连接
Connection ct = DriverManager.getConnection(
"jdbc:oracle:thin:@192.168.68.130:1521:orcl", "scott","tiger");
// 3.创建CallableStatement
CallableStatement cs = ct.prepareCall("{call fenye(?,?,?,?,?,?)}");
// 4.给第1个?赋值
cs.setString(1, "emp");
cs.setInt(2, 5);
cs.setInt(3, 2);
// 注册记录数
cs.registerOutParameter(4, oracle.jdbc.OracleTypes.INTEGER);
// 注册总页数
cs.registerOutParameter(5, oracle.jdbc.OracleTypes.INTEGER);
// 注册返回的结果集
cs.registerOutParameter(6, oracle.jdbc.OracleTypes.CURSOR);
// 5.执行
cs.execute();
// 取出总记录数
// getInt(4)中的4,是由该参数的位置决定的
int rowNum = cs.getInt(4);
int pageCount = cs.getInt(5);
System.out.println("rowNum=" + rowNum);
System.out.println("pageCount=" + pageCount);
ResultSet rs = (ResultSet) cs.getObject(6);
while (rs.next()) {
System.out.println("编号:" + rs.getInt(1) + " 姓名:"
+ rs.getString(2) + "\t薪水" + rs.getFloat(6));
}
// 6.关闭资源
cs.close();
ct.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
19.6 pl/sql的进阶-例外处理
1、例外的分类
Oracle将例外分为预定义例外,非预定义例外和自定义例外三种;
预定义例外用于处理常见的oracle错误;
非预定义例外用于处理预定义例外不能处理的例外;
自定义例外用于处理与oracle错误无关的其他情况;
2、例外传递
案例:编写一个过程,可接收雇员的编号,并显示该雇员的姓名。问题是,如果输入的雇员编号不存在,怎样去处理呢?
declare
--定义
v_ename emp.ename%type;
begin
select ename into v_ename from emp where empno=&gno;
dbms_output.put_line('名字:'||v_ename);
exception
when no_data_found then
dbms_output.put_line('编号没有');
end;
3、预定义例外
预定义例外是由pl/sql所提供的系统例外。当pl/sql应用程序违反了oracle规定的限制时,则会隐含的触发一个内部例外。
pl/sql为开发人员提供了二十多个预定义例外。我们给大家介绍常用的例外。
1)预定义例外 case_not_found
在开发pl/sql块中编写case语句时,如果在when字句中没有包含必须的条件分支,就会触发case_not_found的例外:
create or replace procedure sp_pro6(spno number) is v_sal emp.sal%type; begin select sal into v_sal from emp where empno=spno; case when v_sal<1000 then update emp set sal=sal+100 where empno=spno; when v_sal<2000 then update emp set sal=sal+200 where empno=spno; end case; exception when case_not_found then dbms_output.put_line('case语句没有与'||v_sal||'相匹配的条件'); end; |
2)预定义例外 cursor_alreadly_open
当重新打开已经打开的游标是,会隐含的触发例外cursor_alreadly_open
declare cursor emp_cursor is select ename,sal from emp; begin open emp_cursor for emp_record1 in emp_cursor loop dbms_output.put_line(emp_record1.ename); end loop; exception when cursor_already_open then dbms_output.put_line('游标已经打开'); end; |
3)预定义例外 dup_val_on_index
在唯一索引所对应的列上插入重复的值时,会隐含的触发例外dup_val_on_index
begin insert into dept values(10,'公关部','北京'); exception when dup_val_on_index then dbms_output.put_line('在deptno列上不能出现重复值'); end; |
4)预定义例外 invalid_cursor
当试图在不合法的游标上执行操作时,会触发该例外
例如:试图从没有打开的游标提取数据,或是关闭没有打开的游标,则会触发该例外。
declare cursor emp_cursor is select ename,sal from emp; emp_record emp_cursor%rowtype; begin --open emp_cursor; --打开游标 fetch emp_cursor into emp_record; dbms_output.put_line(emp_record.ename); close emp_cursor; exception when invalid_cursor then dbms_output.put_line('请检测游标是否打开'); end; |
5)预定义例外 invalid_number
当输入的数据有误时,会触发该例外。比如,数字100写成了1oo,就会触发该例外
begin update emp set sal=sal+'1oo'; exception when invalid_number then dbms_output.put_line('输入的数字不正确'); end; |
6)预定义例外 no_data_found
下面是一个pl/sql块,当执行select into没有返回行,就会触发该例外
declare v_sal emp.sal%type; begin select sal into v_sal from emp where ename='&name'; exception when no_data_found then dbms_output.put_line('不存在该员工'); end; |
6)预定义例外 too_many_rows
当执行select into语句时,如果返回超过了一行,就会触发该例外。
declare v_ename emp.ename%type; begin select ename into v_ename from emp; exception when two_many_rows then dbms_output.put_line('返回了多行'); end; |
7)预定义例外 zero_divide
当执行2/0语句时,则会触发该例外。
8)预定义例外 value_error
当在执行赋值操作时,如果变量的长度不足以容纳实际数据,则会触发该例外,如:
declare v_ename varchar2(5); begin select ename into v_ename from emp where empno=&no1; dbms_output.put_line(v_ename); exception when value_error then dbms_output.put_line('变量尺寸不足'); end; |
9)其他预定义例外
9.1 login_denide
当用户非法登录时,会触发该例外
9.2 not_logged_on
如果用户没有登录就执行dml操作,就会触发该例外
9.3 storage_error
如果超出了内存空间或是内存被损坏,就出发该例外
9.4 timeout_on_resource
如果oracle在等待资源时,出现了超时就触发该例外
4、非预定义例外
非预定义例外用于处理与预定义例外无关的oracle错误,使用预定义例外只能处理21个oracle错误,而当使用pl/sql开发应用程序时,可能会遇到其他的一些oracle错误。比如在pl/sql块中执行dml语句时,违反了约束规定等等,在这样的情况下,也可以处理oracle的各种例外,因为非预定义例外用的不多,这里就不多举例了。
5、自定义例外
预定义例外和自定义例外都是与oracle错误相关的,并且出现的oracle错误会隐含的触发相应的例外;而自定义例外与oracle错误没有任何关联,它是由开发人员为特定情况所定义的例外。
案例:请编写一个pl/sql块,接收一个雇员的编号,并给该雇员工资增加1000元,如果该雇员不存,请提示。
--自定义例外
create or replace procedure ex_test(spNo number) is
--定义一个例外
myex exception;
begin
--更新用户sal
update emp set sal=sal+1000 where empno=spNo;
--sql%notfound:表示没有更新成功
if sql%notfound then
--raise myex:触发myex这个例外
raise myex;
end if;
exception
when myex then
dbms_output.put_line('没有更新任何用户');
end;