];
在事务内部,即可以回退,创建日志
Truncate table table_name
DDL语句,不能回退也不做日志,因此,数据不能恢复
delete from table_name
truncate table table_name
结果一样,表的数据全部删除
delete:在删除大量数据,速度慢
truncate:在删除大量数据,速度快
==============事务=============
必须完整地完成或取消的逻辑操作单位;没有可以接受的中间状态。
"begin"
1、update a set x=x+100 where y=111; 111=1000 ——> 1100
2、update b set x=x-100 where z=222; 222=2000 ——> 1900
3、commit;
"end"
事务的性质
原子性:事务不可分割
一致性:保持数据文件中的数据和日志中记录的数据一致
数据的修改前,需要在日志中记录
当提交(commit)了数据,则数据就变成永久的数据
当数据提交后,修改的数据,还是在内存中的,磁盘中的数据还是没有修改的。
而日志数据却一定会在磁盘中
日志记录的是:111=1100 222=1900
磁盘中的数据:111=1000 222=2000(修改的数据只是在内存中)
数据文件中的数据和日志中记录的数据不一致
如果两个语句在一个事务中则:
数据库在下次重启时,自动检查日志数据和数据文件数据
发现不一致,则按照日志中的数据,重新执行数据修改。
最终磁盘中数据:111=1100 222=1900
隔离性:不受其他事务的干扰
两个事务之间,在没有结束事务之前,数据的修改不会影响对方的数据操作
持续性:事务一旦被提交,数据的修改就是永久性的
提交:commit,Oracle中事务默认不是自动提交的(MS SQL Server是自动提交的)
回退:rollback,把没有提交的事务取消
保留点:savepoint:savepoint sp_name
在事务的内部添加标记,用于部分的回退相应的操作。
事务:
手工提交:
DML语句是事务语句,需要手工提交,才能将数据变成永久的
自动提交:
DDL、DCL语句,不需要提交,自动将数据变成永久。
=================select===============
===============基本查询================
1、单表执行Select:
select 列列表或表达式 from 表名或视图或其他 where 条件 order by 排序
关系型数据库中查询操作:
1、选择(where)
2、投影(列列表)
3、连接(join)
1、检索所有列:列列表 或 *
select * from emp
select deptno,dname,loc from dept
推荐使用列列表,不推荐使用*
因为*会降低程序的可读性,增加系统开销
2、理解行标识符:rowid
select rowid,ename from emp
Oracle中ROWID伪列是用于表示每一行的物理地址的
3、执行算术运算:+ - * /
select sal+comm from emp
对空值的处理:空值与任何表达式进行算术运算都是空
4、日期运算:
select hiredate+3 from emp ——日期
select to_date('12-2月-09')-hiredate from emp --天数
5、列运算(3和4属于列运算)
6、使用列别名:空格或AS,
需要双引号引起:特殊符号、大小写区分等
select ename "employee's name" from emps
7、串联操作符:“||”(SQL Server “+”)
select '姓名:'||ename||' 岗位:'||job from emp
8、理解空值:表示该列的值未知,判断使用is null或is not null,处理函数
select * from emp where comm is null
select sal+comm from emp;
select sal+nvl(comm,0) from emp;
9、禁止显示重复的行:distinct
distinct:消除重复:
select count(distinct job) from emp
--确定列基数
select count(distinct job)/count(*) from emp
--确定数据的重复几率
2、使用Where子句过滤数据
1、使用比较运算符
= > < <= >= <> != any all in
select * from emp where deptno=20;
select * from emp where deptno<>20;
select * from emp where sal>3000;
select * from emp where deptno in (10,20)
select * from emp where sal > ANY(2000,3000,4000) --大于最小的(小于最大的)
select * from emp where sal > ALL(2000,3000,4000) --大于最大的(小于最小的)
2、SQL操作符
like、in、between、is null
1、使用like操作符:字符串匹配,"_"匹配一个字符,"%"匹配任意多个字符
2、IN操作符:匹配值列表
3、Between操作符:匹配范围 == 大于等于小的 而且小于等于大的
3、逻辑操作符
and or not:优先级 not and or
4、使用Order by执行排序 ASC DESC
===========rownum=========
rownum:Oracle中的“伪列”在执行desc命令显示的列名中不存在的列
rownum为查询出的数据添加顺序的编号
可以基于编号的数据过滤
获得emp表的前10行(MS SQL Server “TOP”)
select rownum,ename from emp where rownum<=10
获得emp表的后4行
select rownum,ename from emp where rownum>10
以上语句不能得到相应的结果
rownum是在提取一行后就添加rownum,并且比较查询条件
如果提取第一行,rownum=1,rownum>10对于1来讲,是不符合条件的
第一行就是不满足条件的,如果再取一行,因为刚才的顺序编号1没有被使用
再次使用1,再次匹配条件也是不符合条件的,以此类推,就没有一行是,满足条件的了
1、select rownum id,ename from emp
create or replace view a
as
select rownum id,ename from emp
通过创建视图,实现先给要提取的每一行添加编号
2、select * from a where id>10
视图a是先被执行的,然后在执行以上语句
3、上面两步合并
select * from a where id>10 ===>
select * from (select rownum id,ename from emp) a where a.id>10;
其中a称为内嵌视图,SQL解析器,首先解释执行a对应的SQL语句
获得工资收入最高的前5个人
1、先对根据工资进行数据排序
create view b
as
select * from emp order by sal desc;
2、再对排序后的数据加上编号
create view c
as
select rownum id,b.* from b;
3、根据编号获得前5位
select * from c where id<=5;
4、合并成一条SQL语句
select * from (select rownum id,t.* from
(select * from emp order by sal desc) t) tt where tt.id<=5;
获得工资第五到第八位的人员信息
select * from (select rownum id,b.* from
(select * from emp order by sal desc) b) c where c.id between 5 and 8;
数据库分页程序需要用到Rownum伪列
==========聚合函数和分组查询==========
聚合函数:同时对一组数据进行操作,并每组返回一行输出,也称之为分组函数
聚合函数主要是数字处理函数(忽略空值)
avg(X) :平均值
count(X) :计数(返回查询的行数)
max(X) :最大值
min(X) :最小值
sum(X) :求和
variance(X) :方差(用于测算离散趋势)
stddev(X) :标准差(离均差,用于测算离散趋势,推荐使用)
1、avg()
列出员工的平均工资
select avg(sal) from emp
列出20部门的员工的平均工资
select avg(sal) from emp where deptno=20
2、count()
列出员工总数
select count(*) from emp
select count(empno) from emp
select count(rowid) from emp ——Oracle特定用法,效率较高——Rowid:行的物理地址(伪列)
列出所有员工中岗位的个数
select count(distinct job) from emp
列出有补助的员工数
select count(*) from emp where nvl(comm,0)>0
3、min、max
列出工龄最长的员工姓名和入职日期
select ename,hiredate from emp where (sysdate-hiredate)=(select max(sysdate-hiredate) from emp)
4、stddev、variance
查询部门员工收入差异最大的组
select stddev(sal) from emp group by deptno
使用Group by子句进行分组
1、Group by可以将行分组为具有相同列值的多个部分
查询emp表中存在的部门号
select deptno from emp group by deptno
2、在分组中使用多列
列出员工的岗位和部门号的不同情况
3、行分组使用聚集函数
聚合函数对每一组中的行进行计算机,并为每一组返回一个结果
查询各个部门的平均工资
select avg(sal),deptno from emp group by deptno
4、使用Order by子句对组进行排序
查询各个部门的平均工资,并以平均工资的从高至低的顺序排序
5、调用聚和函数的错误用法
获得各个岗位的总工资
select job,sum(sal) from emp group by job
select job,deptno,sum(sal) from emp group by job
sum(sal)要把获得每一个岗位的总工资,但是deptno,却要试图从每一行
获得部门号,这两个操作是不能同时完成的
因此:包含聚合函数的分组查询中,所选择的列,如果不出现在分组子句中是非法的
6、使用Having子句进行分组过滤
查询部门号为10和20的部门的平均工资
select avg(sal),deptno from emp
group by deptno
having deptno in(10,20);
7、组合使用where和Group by子句
查询部门号为10和20的部门的平均工资
select deptno,avg(sal) from emp where deptno in(10,20) group by deptno
8、组合使用Where、group by和Having
查询10、20的部门的平均工资中低于2500的记录
select deptno,avg(sal) from emp where deptno in(10,20) group by deptno
having avg(sal)<2500
where的作用:行的过滤(选择)
having:组过滤
分组查询中
在列列表中出现的列名,必须出现在分组条件中
聚集函数:min max avg count 忽略空值
=================连接查询====================
大部分数据库中都是有多个表的,这些表存储着企业的多方面的信息,
如何同时从多个表中获得所需要的信息——多表查询(连接,子查询)
1、入门
范例:
列出员工名和员工的部门名:
可以通过两步完成:
1、获得SCOTT所在的部门号
select deptno from emp where ename=员工名
2、通过部门号获得部门名
select dname from emp where deptno=部门号
如果需要在同一行中显示员工名和部门名,就需要将两个表连接起来,也就意味着
在同一个查询中同时指定两个表,然后指明两个表的关联列
select ename,dname from emp,dept where deptno=deptno
问题1:两个deptno到底哪一个是表(emp、dept)的,是没有办法搞明白的——限定词
select ename,dname from emp,dept where emp.deptno=dept.deptno
问题2:现在两个deptno可以区分开了,但是,如果两个表中都有dname,列列表
中的dname就没法区分了——列限定词
select dept.dname from emp,dept where emp.deptno=dept.deptno
问题3:每一次都输入表的全名,是一项冗余的徒劳的事情——表别名
select e.ename,d.dname from emp e,dept d where e.deptno=d.deptno
以上就是连接查询的基本知识
===============扩展==================
连接查询目前主要应用两种标准:SQL/86和SQL/92标准
SQL/86:使用where子句中表示连接条件
SQL/92:是用join关键字和“on 连接条件”建立连接
以上语句是使用的SQL/86标准,如果使用SQL/92标准,则写法如下:
select e.ename,d.dname from emp e join dept d using(deptno)
使用using子句的条件
1、查询必须是等值连接(连接条件表达式应该是用等号的)
2、等值连接中的列名必须是相同的9(d.deptno=e.deptno)
以下3条语句等价:
92:select e.ename,d.dname from emp e JOIN dept d ON e.deptno=d.deptno
92:select e.ename,d.dname from emp e JOIN dept d USING(deptno) --JOIN ... ON的特例
86:select e.ename,d.dname from emp e,dept d where e.deptno=d.deptno
2、其他
关于笛卡尔积:如果在查询中不指定连接条件就会产生笛卡尔积
SQL86:select ename,dname from emp,dept
在SQL92标准中,如果是取得笛卡尔积,需要使用交叉连接
SQL92:select ename,dname from emp cross join dept
关于多于两个表的连接
体现在连接条件中,连接条件的个数是表的个数减去1
--查询所有员工的员工名、部门名和工资等级
SQL86
select e.ename,d.dname,g.grade from emp e,dept d,salgrade g
where d.deptno=e.deptno and e.sal between g.losal and g.hisal;
SQL92
select e.ename,d.dname,g.grade from emp e join dept d
on
d.deptno=e.deptno
join salgrade g
on
e.sal between g.losal and g.hisal
3、连接条件和类型
表连接的连接表达式格式:
表中的列+操作符+另一个表中的列
e.deptno = d.deptno
针对连接表达式的连接操作符不同,连接可以分为:
1、等值连接(=)
2、不等值连接(<> > < >= <= ...)
除了根据连接操作符不同的分类外,连接本身还有3个类型:
1、内连接(inner join):完全满足连接条件的数据
2、外连接(outer join):对不满足连接条件的数据进行有选择的列出(即使连接中的列包含空值也返回)
3、自连接(self join):连接同一个表
4、不等值连接
在连接表达式中使用不是等号的其他操作符(<>、>、<、<=、>=、 like、 in、 between)
--查询所有员工的工资等级
SQL86:select e.ename,g.grade from emp e,salgrade g where e.sal between g.losal and g.hisal
SQL92:select e.ename,g.grade from emp e join salgrade g
on
e.sal between g.losal and g.hisal
5、外连接
准备数据:(添加一名员工,没有部门号)
insert into emp(empno,ename) values('1212','HYGJ');
列出部门名和员工名,包含没有部门的员工名
1、SQL86(内连接不能满足要求:因为内连接只能显示哪些满足连接表达式的行
而在此,null=10或者20或者30,都是不成立的)
select e.ename,d.dname,e.deptno,d.deptno
from emp e,dept d
where e.deptno=d.deptno
2、SQL92(内连接不能满足要求)
select e.ename,d.dname,e.deptno,d.deptno
from emp e join dept d
on e.deptno=d.deptno
3、SQL86(外链接,这里显示员工的部门号为空的,就要在相反的部门表的连接上加(+)
包含满足连接条件的所有组合,和哪些没有标识“+”的表中的取值为空的行)
select e.ename,d.dname,e.deptno,d.deptno
from emp e,dept d
where e.deptno=d.deptno(+)
4、SQL92(右外连接,显示空值的表在必须在join的右边
把Join左边的表中的连接列取值为空的行也显示出来)
select e.ename,d.dname,e.deptno,d.deptno
from emp e left outer join dept d
on e.deptno=d.deptno
列出所有的部门名和所有的员工名,包含没有部门的员工和没有员工的部门
5、SQL92(对两个表中所有的空值都列出来,用全外部连接)
select e.ename,d.dname,e.deptno,d.deptno
from emp e full outer join dept d
on e.deptno=d.deptno
6、自连接:对同一个表进行连接,解决表中行与行之间的关系
列出每一个员工的上司
1、SQL86
select e.ename 员工,m.ename
from emp e,emp m where e.mgr=m.empno
2、SQL92
select e.ename 员工,m.ename
from emp e join emp m on e.mgr=m.empno
=======================================================================================
子查询:
在select、insert、update或delete语句内部使用的select语句。这个select语句称为子查询。
子查询的类型:
1、单行子查询
不向外部SQL语句返回结果,或者只是返回一行
2、多行子查询
向外部的SQL语句返回一行或多行
3、多列子查询
向外部SQL语句返回多列数据
4、关联子查询
子查询引用了外部SQL语句中的一列或多列。
5、嵌套子查询
子查询位于另一个子查询中,最多可以嵌套255层
=================
1、单行子查询
不向外部SQL语句返回结果,或者只是返回一行
子查询可以放在Select语句中的Where、Having或From子句中
1、在Where子句中的子查询
--查询和SCOTT用户相同岗位的员工的姓名和岗位
select ename,job from emp where job=(select job from emp where ename='SCOTT')
select job from emp where ename='SCOTT':这条语句返回的只有一行数据
在上面的单行子查询操作中使用的是“=”,还可以使用的运算符是比较运算符
--查询高于平均工资的所有员工的姓名和工资
select ename,sal from emp where sal>(select avg(sal) from emp)
2、在having子句中使用子查询
--查询部门平均工资高于全部员工的总平均工资的部门号和其平均工资
select deptno,avg(sal) from emp group by deptno
having avg(sal)>(select avg(sal) from emp);
3、from子句中使用子查询(内联视图)
Oracle中的伪列Rownum的使用方法(前面已经讲了)
=================
2、多行子查询
子查询返回一行或多行数据,要处理多行记录可以使用IN、ANY或ALL操作符
1、使用IN操作符:IN检查值列表中是否包含指定的值
查询和SCOTT、SMITH同一岗位的所有员工的名字和岗位
select ename,job from emp
where job in(select job from emp where ename in('SCOTT','SMITH'))
2、使用ANY、ALL操作符,
Any用来对子查询返回值列表中的任意值进行比较
ALL用来对子查询返回值列表中的所有值进行比较
在ANY和ALL操作符之前必须使用比较运算符
查询工资低于salgrade表中低级工资的员工名和其工资
select ename,sal from emp where sal >any (2000,3000,4000)
查询工资低于salgrade表中所有的低级工资的员工名和其工资
select ename,sal from emp where sal 在一个例子
查询比各个部门平均工资都要低的员工名和工资
select ename,sal from emp where sal 查询低于任意部门平均工资的员工的名字和工资
select ename,sal from emp where sal 3、多列子查询:
列出每个部门中工资最低的员工的姓名和工资
select ename,sal from emp
where (sal,deptno) in
(select min(sal),deptno from emp group by deptno)
=======关联子查询=============
引用外部查询中的一列或多列的子查询,称之为关联子查询。
1、范例
查询高于自己部门平均工资的员工名和工资
select ename,sal from emp out
where sal>(
select avg(sal) from emp inner where inner.deptno=out.deptno
)
2、exists、not exists:检查行的存在性
查询可以管理其他员工的员工姓名
1、select ename from emp where exists(select 1 from emp inner where emp.empno=inner.mgr)
2、select ename from emp where empno in(select mgr from emp)
查询没有员工的部门号
1、select deptno from dept where not exists(select 1 from emp where dept.deptno=emp.deptno)
2、select deptno from dept where deptno not in(select deptno from emp)
关于exists(not exists)与in(not in)的比较
通常来讲,因为in(not in)需要检查实际值,而exists(not exists)只需要检查存在性,
所以exists(not exists)效率更高一些。
======嵌套子查询===========
在子查询内部嵌套其他子查询
知识点:rownum伪列:
在desc table_name 中不出现的列
此列用于对查询的结果添加顺序的编号
原理可以如下理解
通过查询语句在数据库中依次取出指定的行,然后根据
取出的先后顺序添加编号,编号从1开始
此时是指在没有排序和分组的前提下完成的。
--查询前5个员工序号和姓名
select rownum,ename from emp where rownum<=5
--查询第6到第10个员工的序号和名字
--查询工资最高的前5个员工的序号和名字
===========高级查询=============
集合操作:(SQL Server中只是支持Union和Union all)
Union 结果并集,去掉重复行
Union all 结果并集,保留重复行
Intersect 结果的交集
Minus 结果的差集
select * from emp where deptno=10 and job='CLERK';
交集:取两个查询公共的数据
select * from emp where deptno=10
intersect
select * from emp where job='CLERK'
差集:
把第一个查询结果中和第二个查询结果相同的行去掉,剩下的数据
两个查询的先后顺序就敏感了
select ename from emp where deptno=10 minus
select ename from emp where job='CLERK'
select ename from emp where job='CLERK' minus
select ename from emp where deptno=10;
2、使用case表达式:
case表达式可以在SQL中实现类似if...else语句,其功能和decode相似。
1、使用简单case表达式
case search_express
when express1 then result1
when express2 then result2
...
when expressn then resultn
else default_result
end
列出所有员工的名字和部门代码,代码对应如下:
10——A,20——B,30——C,其余的——X
select ename,
case deptno
when 10 then 'A'
when 20 then 'B'
when 30 then 'C'
else 'X'
end deptno
from emp
2、使用搜索case表达式
case
when condition1 then result1
when condition2 then result2
...
when conditionn then resultn
else default_result
end
1、列出所有员工的名字和部门代码,代码对应如下:
10——A,20——B,30——C,其余的——X
select ename,
case
when deptno=10 then 'A'
when deptno=20 then 'B'
when deptno=30 then 'C'
else 'X'、
end from emp;
2、有数据表如下:
create table a(
id int,
name varchar2(20),
subject varchar2(10),
score number(5,2));
insert into a values(1,'张三','物理',78);
insert into a values(2,'张三','化学',80);
insert into a values(3,'张三','英语',90);
insert into a values(1,'吴用','物理',60);
insert into a values(1,'吴用','化学',70);
insert into a values(1,'吴用','英语',98);
ID NAME SUBJECT SCORE
-- -------------------- ---------- ----------
1 张三 物理 78
2 张三 化学 80
3 张三 英语 90
1 吴用 物理 60
1 吴用 化学 70
1 吴用 英语 98
行列转换
姓名 物理 化学 英语
张三 78 80 90
吴用 60 70 98
select name 姓名,
sum(case subject when '物理' then score end) 物理,
sum(case subject when '化学' then score end) 化学,
sum(case subject when '英语' then score end) 英语
from a group by name
====================Oracle函数====================
分类:
多行函数:
也叫聚集函数、统计函数、分组函数:min max avg。。。
输入一组值,返回一个值
单行函数:
输入一个值,返回一个值
既可以在select表达式中使用也可以在PL/SQL程序中使用
表达式:
select 1+2
SQL Server合法的,求取表达式的值
Oracle中不合法的
在Oracle中Select是选择语句,后面一定会有一个from与之对应
From后跟表名,如果没有表,就用dual返回表达式的值
dual表实际上是不存在的虚表,数据库启动就动态建立,数据库关闭,dual表就没有了
单行函数分类:
1、字符函数
2、数字函数
3、日期函数
4、转换函数
字符函数:
ASCII(c):返回字符c的ASCII码
CHR(x):返回ASCII码为x的字符
LOWER(c):小写转换
UPPER(c):大写转换
INITCAP(c):把字符串中每一个单词的首字母大写,其他字母小写
CONCAT(x,y):将y附件加到x上,并返回所得到的字符串
LENGTH(c):返回字符串的字符个数
INSTR(字符串,要查找的字符串[,开始查找位置][,第几次出现])
LPAD(字符串,字符串长度[,追加的字符])
RPAD(字符串,字符串长度[,追加的字符])
TRIM([剔除的字符串 from] 要剔除的字符串):默认剔除两端的空格
LTRIM(字符串[,要剔除的字符串])
RTRIM(字符串[,要剔除的字符串])
SUBSTR(字符串,开始位置[,提取长度])
REPLACE(字符串,查找的字符串,要替代的字符串)
数学函数:
ABS(x):绝对值
CEIL(x):大于等于x的最小整数
FLOOR(x):小于等于x的最大整数
MOD(x,y):x除以y的余数
ROUND(x[,y]):从第y位小数点进行取整,默认是0,四舍五入
SIGN(x):取符号
TRUNC(x[,y]):从第y位小数点进行取整,默认是0,不四舍五入
日期函数
ADD_MONTHS(x,y):在X日期的基础上添加y个月后的日期
LAST_DAY(x):本月的最后一天是什么日期
MONTHS_BETWEEN(x,y):x和y之间间隔了几个月
NEXT_DAY(x,星期几):从x日开始,下一个星期几是什么日期
ROUND(x[,unit]):unit:指明所取的单元:日期的四舍五入,过半进1
TRUNC(x[,unit]):日期的截断
SYSDATE():获得当前服务器系统时间
CURRENT_DATE():获得当前客户端时间
SYSTIMESTAMP(p):获得当前服务器的时间戳(带有时区标记)
LOCALSYSTIMESTAMP(p):获得当前服务器的时间戳(时间自动转换为当前时区时间)
日期的四舍五入和截断:精度
取时间单位的一半
2009-5-20 15:07:21
按年:7月1号:2009年
按月:15号:6月
按日:12:00
转换函数:不同数据类型的数据之间的转换
SQL Server :
convert
cast
Oracle:
to_char :number转换到char,date转换到char
to_date :char转换到date
to_number :char转换到number
注意:在SQL Server或Oracle中都存在隐含数据类型转换
insert into emp(empno,hiredate,sal) values('1234','12-1月-2009','2000');
单引号界定符在数据库中一般是界定字符或字符串的,
只要是单引号引起来的就是字符或字符串
上面的语句是正确的,原因是:
Oracle自动数据类型的转换——隐含数据类型转换
隐含数据类型的转换是有条件的??
对字符串格式的要求和取值的要求
格式:日期——字符串(日期-月份(月)-年)
取值:数字——字符串(只能取可以作为数字用的字符)
隐含数据类型转换:
总是存在各种限制,
如果不能满足其格式的或取值的要求就会出错
显式数据类型转换,可以更加自由的定义格式和值
to_date(字符串,格式表达式):字符串==>日期
select to_date('2009-01-12','yyyy-mm-dd') from dual;
insert into emp(empno,hiredate,sal)
values('1234',to_date('2009-01-12','yyyy-mm-dd'),'2000');
to_char(日期,格式表达式):日期===>字符串
显示所有员工的雇用日期,其格式为2009-01-23
select to_char(hiredate,'yyyy--mm--dd') from emp
通过一个查询返回当前查询时刻,今天还剩多少秒?
格式表达式:
公元:
CC 两位的世纪
SCC 两位的世纪,公元前加-(负号)
年份:
Y YY YYY YYYY Year YEAR year
2009年
9 09 009 2009 Two Thousand Nine
DY 天的名称简写(英文星期几简称,中文中没有简称 )
DAY 天的名称全称(英文:星期几的英文全称,中文:=DY)
D 星期中的天(7)
DD 月中的天(31)
DDD 年中的天(365)
W 月中的周
WW 一年中的第几周(52)
MM 两位数表示月份
MON 月份缩写
MONTH 月份
HH24/HH12 24/12小时制时间
MI/SS 分钟/秒
SSSSS 一天中的秒
-/,.;: "text" 原样显示
to_char(数字,格式):数字===>字符串
显示员工的工资,需要工资的格式为¥2,300.00(RMB 2,300.00)
1、select to_char(sal,'L0,000,00') from emp
2、column sal format 'L0,000,00'
都能满足查询的要求
1、是查询语句,返回的数据字符串(已经不是原来的数据类型了)
2、是格式设置,返回的数据数字,只不过是显示进行格式化,数据还是原来的数据
只是显示形式变了
to_number(字符串,格式):字符串===>数字
select to_number('$1,900.00','$0,000.00') from dual;
格式表达式:
9:返回指定位置处的数字,如果该数字是负数,前面加负号
0:返回指定位置处的字符,没有数字的位置用0补齐
. :在指定位置返回小数点
, :在指定位置返还逗号
$:返回美元符号
C:返回ISO货币符号
D:本地小数点
G:本地分隔符
EEEE:科学计数法
L:本地货币符号
MI:正数返回数字后加空格,负数返回数字后加负号(减号)
PR:正数返回数字前后加空格,负数返回数字前后加尖括号(<>)
S:正数返回数字,并在指定位置添加号,负数在指定位置添加负号
杂项函数:
DECODE函数==case操作
非常类似于if ... else语句
if deptno=20 then
sal=sal+20;
elsif deptno=30 then
sal=sal+30;
elsif deptno=10 then
sal=sal+10;
else
sal=sal;
end if;
使用decode函数:
decode(deptno,
10,
sal+10,
20,
sal+20,
30,
sal+30,
sal
)
decode函数是一个无限参数函数,格式:
decode(表达式,值1,返回表达式1,值2,返回表达式2,....,返回表达式n)
1、根据以下规则,列出员工的新工资
10——+100元,20——+200元,30——+300元,其他部门不变
select sal,decode(
deptno,
10,sal+100,
20,sal+200,
30,sal+300,
sal) newsal from emp
2、有数据表如下:
create table a(
id int,
name varchar2(20),
subject varchar2(10),
score number(5,2));
insert into a values(1,'张三','物理',78);
insert into a values(2,'张三','化学',80);
insert into a values(3,'张三','英语',90);
insert into a values(1,'吴用','物理',60);
insert into a values(1,'吴用','化学',70);
insert into a values(1,'吴用','英语',98);
ID NAME SUBJECT SCORE
-- -------------------- ---------- ----------
1 张三 物理 78
2 张三 化学 80
3 张三 英语 90
1 吴用 物理 60
1 吴用 化学 70
1 吴用 英语 98
姓名 物理 化学 英语
张三 60 70 98
吴用 78 80 90
行列转换——在实际开发中,打印报表时非常普遍的使用
如何实现行列转换?(SQL Server CASE)
select name 姓名,
sum(decode(subject,'物理',score)) 物理,
sum(decode(subject,'化学',score)) 化学,
sum(decode(subject,'英语',score)) 英语
from a group by name
greatest:取最大值
least:取最小值
根据第一个参数的数据类型,自动转换其余的数据类型,使其一直,如转换失败
表达式报错。
nvl:空值处理函数
nvl2:空值处理函数
nvl(表达式,返回值)
select nvl(comm,0) from emp
员工的总收入(sal+comm)
select sal+nvl(comm,0) from emp
nvl2(表达式,返回值1,返回值2)
select nvl2(comm,comm,0) from emp
员工的总收入(sal+comm)
select nvl2(comm,comm+sal,sal) from emp
===============实现Oracle方案==============
Oracle方案——SQL Server 数据库
一般在实现一个软件工程项目时,用户习惯于把数据存储在一个集中的对象中。
比如:在SQL Server中,针对一个项目,我们一般创建一个数据库
但是在Oracle中一般只有一个数据库,那么在一个数据库中实现不同的项目就
非常不方便。Oracle在组织不同项目中的数据库对象时,使用方案进行区分。
也就是说,创建一个软件项目,在Oracle中不是针对这个项目,创建独立的数据库,
而是为此项目创建一个方案。
什么是方案:某用户拥有的所有的数据库对象的逻辑集合,就叫方案。
方案在名称上和用户名是相同的。
比如有一个Oracle“用户”叫scott,那么,我们一般称Scott用户所拥有的所有数据
库对象的集合叫“方案scott”
比如表emp,它的所有者是scott,我们引用的方法是:scott.emp
scott.emp:可以称之为:
scott方案中的emp表
也可以称之为
scott用户所拥有的emp表
以上两种说法是等价的,一般不进行区分。
比如数据库DB中有三个方案:a、b、c,每一个方案中都有一个表emp
格式:[[数据库名.]方案名.]对象名
db.a.emp
db.b.emp
db.c.emp
在SQL Server中引用方式:因为在SQL Server中有不同的数据库名a,b,c,
每一个数据库中都有emp表
格式:[[数据库名.]所有者的用户名.]对象名 == [[数据库名.]方案名.]对象名
a.dbo.emp
b.dbo.emp
c.dbo.emp
Oracle中创建方案的方法就是创建用户
Oracle中如何实现方案:
1、(可选)创建表空间
2、创建用户
3、给用户授予合适的权限
4、创建数据库对象(表、视图、索引等)
5、创建数据库应用程序(PL/SQL编程)
===============表空间===========
表空间是什么?
用于存储数据库对象的存储空间
SQL Server中数据存储在一个逻辑对象中——文件组
文件组是数据库中的逻辑对象,实现把不同的物理数据文件
组织在一个对象中,方便引用。
可以把整个表的数据的存储定义到不同的文件组
create table abc.dbo.test
(id int primary key)
on 'mygroup'
指定到文件组,实际上是定位到文件组所对应的多个物理文件
Oracle中,组织不同的物理文件的逻辑对象不叫文件组,叫表空间
表空间是用于在Oracle中组织不同的数据文件的逻辑数据库对象
表空间是有物理数据文件构成的。
创建具有一个数据文件的表空间
create tablespace tp_name
datafile
'路径和文件名' size 大小
1、Tp_name:表空间名字,遵循Oracle的命名约定(以后讲)
2、路径和文件名:
windows: c:\oradata\a.dbf
linux:/opt/oradata/a.dbf
3、size:初始化大小,指文件在创建时,分配的空间的大小
创建具有多个数据文件的表空间
create tablespace tp_name
detafile
'路径和文件名' size 大小,
'路径和文件名' size 大小,
'路径和文件名' size 大小,...
create tablespace test1
datafile
'c:\test1' size 5m,
'c:\test2.ora' size 5m
在实现项目方案时,表空间是可选的对象,如果用户不创建表空间
那么,系统会给新创建的用户分配系统默认表空间(system)
但是,为了保证项目数据能够高效存储、方便的备份,推荐创建表空间
如何使用表空间:
1、定义用户默认表空间(推荐用法)
2、创建对象时设置表空间(不推荐)
范例:创建一个表abc,存储在指定的表空间(a)中。
create table abc
(id int)
tablespace a;
===============方案(用户)===================
如果有可以使用的表空间了,那么就意味着,可以有空间存储用户数据了
用户:Oracle中的安全标识,用于标识一个安全主体。
方案:是一个逻辑对象,用于组织资源
下面创建用户:(创建方案)
语法:
create user 用户名 identified by 密码
[default tablespace 表空间名]
[quota 大小 on 表空间名]
[password expire]
1、default tablespace
指用户创建数据库对象时,对象默认保存在哪一个表空间中
如果[default tablespace 表空间名]不定义,就使用系统默认表空间(system)
2、quota 大小 on 表空间名:
在指定在"表空间名"所指定的表空间上,用户可以用多少空间
3、[password expire]:
用户在第一次登录时,可以自己更改密码
创建用户:hy密码是password,表空间默认
create user hy identified by password;
创建用户:hy密码是password,表空间为test
create user hy identified by password
default tablespace test
创建用户:hy密码是password,表空间为test
在test上可以用5M空间,密码立即过期
create user hy identified by password
default tablespace test
quota 5m on test
password expire
=================权限===========
Oracle中新创建的用户,是没有任何权限的,即使是登陆也不行
需要给Oracle用户明确授权才能使用
Oracle权限的分类:
系统权限:执行特定的SQL语句的权利
create procedure :用户可以创建任意名称的过程
create table
......
对象权限:访问其他方案对象的权利
select 表、视图 ... :对特定的表、视图等进行select操作
update 表、视图 ...
execute 过程、函数 ...
......
系统权限(any):(由特权用户管理dba)
create table
权限代表用户可以在自己的方案中创建表(create table)
create any table
权限代表用户可以在任意方案中创建表(create any table)
对象权限:(自己拥有的对象的对象权限不需要授权)(对象的所有者管理)
select :
对象的拥有者把对自己的对象的select权限授予其他用户
权限的授予和撤销(DCL,数据控制语言):
grant:授予权限
revoke:撤销权限
1、系统权限的授予和撤销:
GRANT 权限1,权限2,... TO
user | role | public [,user | role | public] ...
[with admin option]
user:
用户名,Oracle用户
public:
是一个Oracle组,代表所有的Oracle用户
(Windows中的everyone)不推荐使用
role:
角色,(类似于Windows中的组)
--1、授予用户在自己的方案中建立表、视图、过程和函数的权限
GRANT create table,create view,create procedure
TO hy
--2、授予用户scott、hy、hr在任意的方案中建立表、视图、过程和函数的权限
GRANT
create any table,
create any view,
create any procedure
TO
scott,
hy,
hr
with admin option:委派授权选项:
如果在授权时使用此选项,说明此用户可以管理此权限,
也就是此用户在自己得到此权限的同时,也可以把此权限授权其他用户
--3、授予用户在自己的方案中建立表、视图、过程和函数的权限,并且可以将
权限授予别的用户
GRANT create table,create view,create procedure
TO hy with admin option
grant create session to scott with admin option
用户scott获得create session权限,且scott也可以将此权限授予其他用户
如果授权者权限撤销了,那么不会影响其授与的其他用户的权限
A——>B——>C——>D
A权限撤销,B、C、D不会受到影响
系统权限不会级联撤销
2、对象权限的授予和撤销:
GRANT 对象权限1,...,对象权限n
ON 对象名1,...,对象名n
TO user | role | public [,user | role | public] ...
[with grant option]
对象权限1,...,对象权限n ON 对象名1,...,对象名n
在某对象上能够执行的相应权限授予用户
对象权限:根据对象类型不同权限名也不一样
grant select on emp to hy
grant select on proc_name to hy --错误的,因为过程是不能够select操作
grant execute on proc_name to hy --正确的,过程可以具有"执行"权限
给用户hr,在SCOTT方案中对emp表执行select的能力
grant select on scott.emp to hr;
范例:
1、让hr用户能够对scott的emp表执行select操作
grant select on emp to hr;
2、让hr用户可以对emp表执行delete、select操作
grant select,delete on emp to hr;
3、让hr用户可以修改scott的emp表中的员工工资
grant update on emp to hr;????
grant update(sal) on emp to hr;
4、授予hr用户对scott用户的emp表执行select、delete,以及
修改员工工资、补助和部门号。
grant select,delete,update(sal,comm,deptno)
on emp
to hr;
with grant option:对象权限的委派
委派后,权限是级联收回的
A——>B——>C
如果A的权限被撤销,则B、C都会被撤销
讨论:
授予hr、a、b、c、d、e用户对scott用户的emp表执行select、delete,
以及修改员工工资、补助和部门号。
grant select,delete,update(sal,comm,deptno)
on emp
to hr,a,b,c,d;
以上设置,特别是多用户和多权限的设置是非常麻烦的,
为了简化授权——>角色
Oracle角色:
——权限的集合就是角色
——类似于Windows的组(用户的集合)
一般情况,Oracle新的用户没有任何权限(系统、对象),
要给新用户授予系统权限、对象权限,且授权的个数不定
新建用户:
登录、创建表、视图、过程、序列、修改表、查询......
--创建新用户
create user test identified by password
default tablespace hy
quota 10m on hy
--授予登录系统
grant create session to test
--授予表、视图、索引、序列生成器等创建、修改、删除等权限
grant create table,create view,create sequence to test;
。。。
过多的权限设置,增加了新用户设置的复杂性
——使用角色:
——Oracle内置的3个角色:
1、connect:针对Oracle一般用户,诸如登录、创建表等基本权限
2、resource:针对Oracle开发用户,诸如编程、对表空间的无限制使用
3、dba:数据库管理员,对数据库具有最高的管理权限(sysdba)
一般一个新建立用户,只需要授予connect+resource角色即可
grant connect,resource to new_user
Oracle用户自定义角色:
1、公共角色:重点
create role role_name not identified;
用户被授予公共角色,用户自动获得此角色的权限
2、私有角色:可选的
create role role_name identified by password;
如果一个私有角色不是用户的默认角色,则用户获得此角色的权限
必须通过输入密码验证后获得。
角色的使用方法:
1、建立角色
create role myrole not identified;
2、给角色授权(向权限的集合中添加权限)
grant create session,create table to myrole;
3、通过角色给用户授权
grant myrole to hr;
hr用户就具有了create session,create table权限
1、建立角色
create role myrole not identified;
2、给用户授予角色
grant myrole to a,b,c,d,e,f,g
3、给角色授权
grant create session,create table to myrole;
用户a,b,c,d,e,f,g同时具有了create session,create table权限
——角色的主要作用:简化授权
角色的嵌套:
create role a not identified;
create role b not identified;
create role c not identified;
grant create session to a;
grant create table to b;
grant a,b to c;
grant c to scott;
用户scott具有create session,create table权限(connect、resource除外)
在Oracle中新建用户最简单的方法:
1、建立用户
create user test iednitifed by password;
2、授予角色(connect,resource)
grant connect,resource to test;
####################################
Oracle中如何实现方案:
1、(可选)创建表空间
2、创建用户
3、给用户授予合适的权限
4、创建数据库对象(表、视图、索引、同义词、序列等)
5、创建数据库应用程序(PL/SQL编程)
#####################################
Oracle安全管理,间接的实现了创建方案
但是,此方案中没有数据库对象,因此,下面学习如何实现方案对象:
方案对象:
表、视图、索引、同义词、序列、过程、函数、包、触发器、约束
实现表:
数据库中表的设计和实现是通过第三方软件实现,Visio,PD
1、建立表的语法:
CREATE TABLE [schema.]table_name
(column_name datatype [default express] [constraint constraint_name constraint_type],
…,
[constraint constrain_name] constraint_type (column,…)
) tablespace tp_name
--命名规则:
--数据类型:
--数据完整性:
实体完整:主键
参照完整性:外键
用户定义完整性:触发器
建立表时定义完整性的方法:
1、表级
2、列级
create table c
(c1 int primary key,--列级约束 c1 int [constraint PK_C] primary key,
c2 name not null,--列级约束
c3 number(6,2),
constraint chk_c3 check(c3>1000));--表级约束
not null:必须在列级定义
复合主键:必须在表级定义
--默认值:(不归类到约束,和SQL Server有区别)
--表空间:定义表的数据存储在什么表空间中,如果不定义,则
表的数据存储在用户的默认表空间中
临时表:(global temporary table):临时存储数据,后自动删除数据
根据数据的删除时机不同分为:
1、事务临时表
create global temporary table
(列定义)
[on commit delete rows]
oralce默认创建的临时表就是事务临时表
2、会话临时表
create global temporary table
(列定义)
on commit preserve rows --preserve:保留
添加列:新列位于表的末尾,
如果一个非空列位于表的末尾,可以为空的列位于非空列的前面
则此表会浪费数据库空间。
因为如果空值列位于表的末尾,则空值列不占用任何空间
否则,Oracle会为其分配1个字节
视图、索引、同义词、序列
视图:
查询的别名
select t.ename from (select rownum,emp.* from emp) t ——内嵌视图
内嵌视图是不能存储在数据库中。
普通视图是可以存储在数据库中的,而且,再次查询视图,不需要重新编译
查询语句。因为,普通视图在数据库中存储视图的定义和视图的编译结果。
——普通视图本身不存数据,只存储查询,当查询视图时,会再次执行查询
——不占用数据库的空间(数据段)
Create or replace view View_name(alias1,…,aliasn)
As
Select_statement
[with check option [constraint c_name]]
[with read only]
——or replace:用于修改视图,或者在创建视图时,如果存在同名视图,则覆盖其定义
——with check option:用于定义视图的chech约束
——with read only:定义视图不可更新(只读,只能select)
create or replace view a
as
select * from emp where deptno=20
with check option
——此视图只能操作deptno=20的数据
insert into a(empno,ename,deptno)
values(1234,'aaa',10);——非法的
create or replace view a
as
select * from emp where deptno=20
with read only
——此视图只能select
视图的作用:
1、基于安全的考虑,如果用户直接操作表,对用户的限制就没有办法完成
例如,不允许用户操作sal和comm列
解决:1、利用权限解决
revoke update(sal,comm) on emp from user_name
2、利用视图。
create or replace view b
as
select empno,ename,hiredate,mgr,job,deptno from emp;
grant all on b to user_name
2、简化查询(rownum伪列的范例)
select * from (select rownum id ,t.* from
(select * from emp order by sal)t)tmp
where tmp.id between 5 and 10
1、建立排序视图
create or replace view a
as
select * from emp order by sal;
2、建立编号视图
create or replace view b
as
select rownum id,a.* from a;
3、建立查询
select * from b where id between 5 and 10;
--简化多表查询
select e.ename 姓名,m.ename 上司,d.dname 部门名,g.grade 工资级别
from emp e,emp m,dept d,salgrade g
where
e.mgr=m.empno
and e.deptno=d.deptno
and e.sal between g.losal and g.hisal
1、两两相连,然后在写相对较简单的查询
2、之间建立视图
create or replace view a
as
select e.ename 姓名,m.ename 上司,d.dname 部门名,g.grade 工资级别
from emp e,emp m,dept d,salgrade g
where
e.mgr=m.empno
and e.deptno=d.deptno
and e.sal between g.losal and g.hisal
3、格式化数据的输出
数据库中直接取出的数据是不适合客户端直接显示的
转换成适合客户端显示,可以用高级语言处理,也可以用数据库处理
create or replace view a
as
select
initcap(ename) 姓名,
to_char(sal,'L0G000D00') 工资,
to_char(hiredate,'yyyy"年"mm"月"dd"日"') 入职日期
from emp;
视图属于数据库外模式的对象,是数据库用户(应用程序)操作的主要的
数据库对象。(某些基本表、视图、同义词、子程序)
视图从表面上看非常类似于表
视图不能等同于表使用的主要原因是本质不同,但是在视图实际使用时
他和表也有很多不同,主要表现为DML操作的限制
1、如果视图包含以下语法元素:
1、group by
2、分组函数
3、distinct
4、rownum
此视图不能进行任何DML操作
create or replace view a
as
select rownum id,emp.* from emp;
2、其他视图
视图定义中没有包含基表的具有not null约束的列——不可以执行insert操作
create or replace view a
as
select ename,sal,deptno from emp
具有连接的视图,不能执行Insert操作
create or replace view a
as
select e.empno,e.ename,d.deptno,d.dname from
emp e join dept d on e.deptno=d.deptno
序列:
Oracle中生成顺序编号(整数)的数据库对象,主键、流水号
CREATE SEQUENCE sequence_name
[START WITH n1] 第一个序号n1
[INCREMENT BY n2] 增量,默认为1
[{MAXVALUE n3 | NOMAXVALUE}] 最大序列号,>=n1 >n4
[{MINVALUE n4 | NOMINVALUE}] 最小序列号,<=n1 [{CACHE n5 | NOCACHE}] 高速缓存中预分配的序号个数
[{CYCLE | NOCYCLE}] 是否循环
[ORDER | NOORDER] 是否按照请求编号的顺序返回序号
1、创建序列发生器最简单的语法
create sequence a;
——从1开始——并不代表最小值是1,在最小值和最大值之间的一个数
序列发生器一旦创建,start with不可变的
——增量为1:每次取得数比上一次的数字之间的差
——最小值1,最大值10的27次方
如果有最大值和最小值——有界,否则就是无界
——缓存20个编号
指在内存中暂时存储从序列发生器取出的数字
——不循环
当到达最大值(最小值)后,再次从最小值(最大值)开始
——不排序
按照请求的先后顺序,依次分配编号——排序
2、如何从序列发生器中取得数字
使用伪列:
nextval:取下一个数字(在序列发生器第一次被使用时,必须使用nextval初始化)
currval:取当前的、刚刚取过的数字
3、自动编号的实现
1、利用伪列手工插入
create table x
(id int primary key,
name varchar2(10));
insert into x values(a.nextval,'aaa')
2、利用触发器自动插入
--建立表
create table y
(id int primary key,
name varchar2(10));
--建立序列发生器
create sequence seq_b;
insert into y values(seq_b.nextval,'aa');
--建立触发器
create or replace trigger seq_y
before insert on y
for each row
declare
v_num int;
begin
select seq_b.nextval into v_num from dual;
:new.id:=v_num;
end;
--测试
insert into y(name) values('aaa');
序列的自定义:
生成奇数:
create sequence a
increment by 2
生成偶数:
create sequence a
start with 2
increment by 2
生成0到9循环
create sequence a
start with 0
minvalue 0
maxvalue 9
cycle
nocache
同义词:——对象的别名
与视图相似,不占用实际的存储空间,只在数据字典中保存同义词的定义
1、分类:
公用同义词(public synonym)
所有者为特殊的用户组Public所拥有
不需要在引用时提供所有者名称(方案名)
所有用户都可以访问此同义词
方案同义词(schema synonym)
也称为私有同义词(private synonym)
属于用户自己的方案对象
其他用户只有获得授权才可以访问
2、语法:
公共同义词(特权用户:system)
Create [or replace] public synonym synonym_name
For [schema.]object
私有同义词(任意用户)
Create [or replace] synonym synonym_name
For [schema.]object
——同义词的创建不依赖于基础对象权限
但是,执行访问和操作还是依赖基础对象权限的
假设scott用户对hr用户的departments没有权限
创建针对departments表的视图:
create or replace view dp
as
select * from hr.departments
在scott方案中创建失败——视图是依赖基础对象权限的
创建针对departments表的同义词:
create or replace synonym dp
for hr.departments
在scott方案中创建成功——同义词是不依赖基础对象权限的
可以创建,但是,执行访问和操作还是依赖基础对象权限的
——公共同义词:
1、只有DBA用户才可以创建和维护,普通用户必须授权
2、可以有效的隐藏基础对象的名字和其所在的方案
create or replace public synonym mysy
for hr.departments
这时候任何用户都可以通过之间使用mysy的名字访问hr.departments
select * from mysy
单单通过对象名,其他用户是没有办法知道此对象所对应的基础对象的方案名
——同义词可以简化对象名:
假设有一个方案对象:aslkjcalksjckjasnckjasnkcnsakc
select * from aslkjcalksjckjasnckjasnkcnsakc;
create or replace synonym test for aslkjcalksjckjasnckjasnkcnsakc;
select * from test;
——通过同义词或视图的规划,可以使项目开发变得更加灵活
select * from department
如果department是表名,若表名发生变化,导致SQL语句无效
必须修改应用程序中的SQL语句
1、建立视图
create or replace view dp
as
select * from department;
如果表名发生变化,只需要修改视图的定义即可
select * from dp
2、建立同义词
create or replace synonym dp for department;
即使department表还没有实现,这时候,同义词的名字已经确定
select * from dp;
索引:
1、可选的方案对象
2、和视图、序列、同义词不同,是需要占用存储空间,索引段
3、索引有和没有,正确或不正确,对数据库操作都不会产生决定性的影响
4、主要作用:加快查询速度
索引的分类:
根据原理分类:
B树索引(默认的索引)
位图索引
应用的角度分类:
列值是否重复:惟一索引、非惟一索引(默认的索引)
定义的列的个数:单列索引和复合索引
根据索引表达式的不同:函数索引
create index a on emp(ename)
--B树的非唯一索引,还是单列索引
create unique index a on emp(ename)
--B树的唯一索引, 还是单列索引
create index a on emp(sal,nvl(comm,0))
-- B树的非唯一索引,多列的复合索引
create unique index a on emp(sal,nvl(comm,0))
-- B树的唯一索引,多列的复合索引
create bitmap index a on emp(job);
--位图的单列索引
create index a on emp(sal+nvl(comm,0));
--B树的函数索引
5、索引的使用方法:(略)
如何通过适当的SQL语句使用现有的索引
6、索引的原理:
1、B树索引的原理
数据的重复是敏感的,重复的越多效率越低
能够适应的查询:=、like、<、>
2、位图索引的原理
解决列基数较小的表的查询优化问题(列基数:列可能的取值的个数)
能够适应的查询:逻辑运算
位图内部自动进行逻辑运算
7、索引的数量
索引可以优化查询速度、但是也可以降低Insert、update和Delete的速度
因为修改表数据会引起索引数据的变化。
8、语法
CREATE [unique] | [bitmap] INDEX index_name
On
Schema.table_name([column1 [asc | desc],column2 [asc | desc],…] | [fun_express])
[Tablespace tablespace_name]
--unique:惟一索引
--bitmap:位图索引
--tablespace:定义索引数据存储的表空间
=======PL/SQL=========
绑定变量:
var 变量名 类型
exec :变量名:=值;
绑定变量在引用时在变量名前加":"
绑定变量可以为子程序提供实参(以后再讲)
替代变量:
&变量名
不需要声明,主要提供输入操作
输出:
serveroutput 默认关闭,需要开启
set serveroutput on ——打开输出缓冲区,显示输出缓冲区的内容
dbms_output.put_line('...')过程的输出通过serveroutput输出
dbms_output.put_line('...')把数据输出到输出缓冲区
PL/SQL程序的基本结构:
基本结构:PL/SQL块
构成:
头部信息 --可选的,如果没有头,就是匿名块
AS | IS --如果没有头,IS或AS,要用delcare代替
声明部分 --也是可选的
BEGIN --必须
执行部分 --必须
EXCEPTION --可选的,如果没有异常处理,exception就需要省略
异常处理 --可选的
END; --必须的
最简单的、不能再简化的
begin
null; -- 任何一个PL/SQL块,都至少有一条执行语句
end;
块的分类
匿名块(anonymous)
没有头和标签的块
begin
null;
end;
命名块(named)
在程序的开始,有标签的块
<>
begin
null;
end;
子程序(subprogram)
有头的块
create or replace procedure a
as
begin
null;
end;
触发器(trigger)
有头且声明是以declare开头的
create or replace trigger a
before insert on emp
for each row
declare
v_num int;
begin
select m.nextval into v_num from dual;
:new.empno:=v_num;
end;
语法:
1、PL/SQL程序可以使用的字符
算术运算符
+ - * / > < = **
字母
A-Z, a-z
数字
0-9
符号
~ ! @ # $ % * ( ) _ - + = | : ; " ' < > , . ? / ^
间空符号
制表符 空格 回车符号
2、如何使用这些字符,声明程序标识符,即命名规则
最长30字符
开始字符以字母开头
可以包含字母、数字和$、_、#符号
不可以包含任何空白字符
不区分大小写
声明变量(…)每行只能定义一个
默认不可以使用保留字
3、如何为声明的标识符对应的变量、常量等提供值
值的提供形式就是字面值
数字:
直接使用 123 '123'
字符文本:
单引号引起 'aaa'
字符串:
单引号引起,若包含单引号,则在其后再添加一个单引号 'aaaa'
日期:
单引号引起 '01-1月-09'
布尔型:
直接使用,不能用单引号:true、false
布尔值不可以打印
declare
a number(2);
b char(1);
c varchar2(20);
d date;
e boolean;
begin
a:=12;
b:='a';
c:='hello';
d:='12-2月-09';
e:=true;
dbms_output.put_line(a);
dbms_output.put_line(b);
dbms_output.put_line(c);
dbms_output.put_line(d);
--dbms_output.put_line(e);
if e=true then
dbms_output.put_line('true');
end if;
end;
4、注释和间空:
-- :单行注释
/*...*/ :段注释
间空 :提高程序的可读性
5、数据类型:基本和Oracle中表的数据类型相同
只是在Oracle中没有布尔型,PL/SQL中有布尔型
6、使用变量和常量
语法
Var_name [CONSTANT] TYPE [NOT NULL] [:=VALUE];
CONSTANT:常量
TYPE:数据类型
NOT NULL:非空约束
:=VALUE:设置初始值
TYPE:
已知数据类型(number\char\date\boolean)
锚定类型
%type :数据类型依赖于锚定表达式
declare
v_empno number(4); --已知数据类型
v_deptno emp.deptno%type;
--锚定类型,变量的数据类型和emp表的deptno列的类型一样
7、表达式:(略)
8、常见PL/SQL操作
赋值:
1、赋值操作符:
:= :直接变量、字面值或表达式赋值
2、通过查询赋值:
SELECT ... INTO … FROM :通过查询赋值
在PL/SQL语言中select是一个赋值语句,
不是查询语句,所以必须和INTO联合使用
select ename into v_ename from emp where empno=7788;
把select ename from emp where empno=7788,这个查询返回的
值,赋给v_ename变量。
串联:
|| :类似于SQL Server 2000的”+”
9、流程控制
1、顺序
2、选择(分支)
3、循环
选择结构
1、if语句
1、if 条件表达式 then ... end if;
2、if 条件表达式 then ... else ... end if;
3、if 条件表达式 then ... 条件表达式 then ... end if;
范例:向emp表插入新的数据, 诸如以下信息
:编号1234,姓名tom,部门20
declare
v_empno emp.empno%type;
v_ename emp.ename%type;
v_deptno emp.deptno%type;
begin
v_empno:=&empno;
v_ename:=&ename;
v_deptno:=&deptno;
insert into emp(empno,ename,deptno)
values(v_empno,v_ename,v_deptno);
commit;
end;
完善程序:判断是否有相同的员工编号
declare
v_empno emp.empno%type;
v_ename emp.ename%type;
v_deptno emp.deptno%type;
v_count int;
begin
v_empno:=&empno;
v_ename:=&ename;
v_deptno:=&deptno;
select count(rowid) into v_count from emp
where empno=v_empno;
if v_count=0 then
insert into emp(empno,ename,deptno)
values(v_empno,v_ename,v_deptno);
commit;
end if;
end;
完善程序对用户的友好性
declare
v_empno emp.empno%type;
v_ename emp.ename%type;
v_deptno emp.deptno%type;
v_count int;
begin
v_empno:=&empno;
v_ename:=&ename;
v_deptno:=&deptno;
select count(rowid) into v_count from emp
where empno=v_empno;
if v_count=0 then
insert into emp(empno,ename,deptno)
values(v_empno,v_ename,v_deptno);
commit;
dbms_output.put_line('新数据添加成功');
else
dbms_output.put_line('存在相同的员工编号');
end if;
end;
考虑部门号、员工号的问题
declare
v_count_e int;
v_count_d int;
v_empno emp.empno%type;
v_ename emp.ename%type;
v_deptno emp.deptno%type;
begin
v_empno:=&empno;
v_ename:=&ename;
v_deptno:=&deptno;
select count(*) into v_count_e from emp where empno=v_empno;
select count(*) into v_count_d from dept where deptno=v_deptno;
if v_count_e=0 and v_count_d<>0 then
insert into emp(empno,ename,deptno)
values(v_empno,v_ename,v_deptno);
commit;
dbms_output.put_line('新员工添加成功');
elsif v_count_e<>0 then
dbms_output.put_line('员工已经存在');
else
dbms_output.put_line('没有这个部门');
end if;
end;
范例:根据以下规则修改员工的工资:
部门号 增加幅度
10 20%
20 15%
30 18%
通过输入用户的编号,自动修改
declare
v_increment number(6,2);
v_deptno emp.deptno%type;
v_empno emp.empno%type;
v_sal emp.sal%type;
v_count int;
begin
v_empno:=&empno;
select count(*) into v_count from emp where empno=v_empno;
if v_count <>0 then
select deptno,sal into v_deptno,v_sal from emp where empno=v_empno;
if v_deptno=10 then
v_increment:=v_sal*0.2;
elsif v_deptno=20 then
v_increment:=v_sal*0.15;
elsif v_deptno=30 then
v_increment:=v_sal*0.18;
else
v_increment:=0;
end if;
update emp set sal=sal+v_increment where empno=v_empno;
commit;
dbms_output.put_line('数据操作成功');
else
dbms_output.put_line('员工编号不存在');
end if;
end;
2、case语句
1、
case 条件表达式
when 结果1 then
操作1;
...
when 结果n then
操作n;
esle
操作n+1;
end case;
2、
case
when 条件表达式1 then
操作1;
...
when 条件表达式1 then
操作n;
else
操作n+1;
end case;
范例:根据以下规则修改员工的工资:
部门号 增加幅度
10 20%
20 15%
30 18%
通过输入用户的编号,自动修改
declare
v_increment number(6,2);
v_deptno emp.deptno%type;
v_empno emp.empno%type;
v_sal emp.sal%type;
v_count int;
begin
v_empno:=&empno;
select count(*) into v_count from emp where empno=v_empno;
if v_count <>0 then
select deptno,sal into v_deptno,v_sal from emp where empno=v_empno;
case v_deptno
when 10 then
v_increment:=v_sal*0.2;
when 20 then
v_increment:=v_sal*0.15;
when 30 then
v_increment:=v_sal*0.18;
else
v_increment:=0;
end case;
update emp set sal=sal+v_increment where empno=v_empno;
commit;
dbms_output.put_line('数据操作成功');
else
dbms_output.put_line('员工编号不存在');
end if;
end;
--CASE的第二种形式
declare
v_increment number(6,2);
v_deptno emp.deptno%type;
v_empno emp.empno%type;
v_sal emp.sal%type;
v_count int;
begin
v_empno:=&empno;
select count(*) into v_count from emp where empno=v_empno;
if v_count <>0 then
select deptno,sal into v_deptno,v_sal from emp where empno=v_empno;
case
when v_deptno=10 then
v_increment:=v_sal*0.2;
when v_deptno=20 then
v_increment:=v_sal*0.15;
when v_deptno=30 then
v_increment:=v_sal*0.18;
else
v_increment:=0;
end case;
update emp set sal=sal+v_increment where empno=v_empno;
commit;
dbms_output.put_line('数据操作成功');
else
dbms_output.put_line('员工编号不存在');
end if;
end;
循环结构:
1、loop ... end loop
1、
loop
操作...
exit when 条件表达式
操作...
end loop;
2、
loop
操作...
if 条件表达式 then exit; end if;
操作...
end loop;
2、for ... loop
for 索引变量 in 起始值 .. 结束值 loop
操作
end loop;
3、while ... loop
while 条件表达式 loop
操作
end loop;
计算1到100的累积
declare
v_result number(4):=0;
v_increment number(3):=0;
begin
loop
v_result:=v_result+v_increment;
v_increment:=v_increment+1;
exit when v_increment>100;
end loop;
dbms_output.put_line(v_result);
end;
10、复合数据类型
1、record类型:多个成员变量,每一个成员变量的类型可以不同,类似于C中结构体
2、table类型:多个成员变量,类型相同,C中数组
3、游标类型及引用游标变量
record类型:保存表的一行的某些列的数据
1、声明类型
type type_name is record(...)
2、声明变量
var_name type_name;
declare
type mytype is record
(v_empno emp.empno%type,
v_ename emp.ename%type,
v_deptno emp.deptno%type);
emp_record mytype;
begin
select empno,ename,deptno into emp_record from emp
where empno=7788;
dbms_output.put_line(emp_record.v_empno);
dbms_output.put_line(emp_record.v_ename);
dbms_output.put_line(emp_record.v_deptn/o);
end;
RECORD特例:%rowtype——一行全部列的数据
declare
emp_row emp%rowtype;
begin
select * into emp_row from emp where empno=7788;
dbms_output.put_line(emp_row.empno);
dbms_output.put_line(emp_row.ename);
dbms_output.put_line(emp_row.deptno);
end;
Table类型:下标是从1开始的
declare
--1、类型的声明
type mytype is table of varchar2(20) index by binary_integer;
--2、变量的声明
ename_tab mytype;
begin
--ename_tab(1):='scott'
for i in 1 .. 14 loop
select ename into ename_tab(i) from
(select rownum id,emp.* from emp) t where t.id=i;
end loop;
for i in 1 .. 14 loop
dbms_output.put_line(ename_tab(i));
end loop;
end;
游标:
游标是一个指针,指向游标定义的SQL语句的结果集的内存上下文
游标本身不存储数据,只是定义一个指向内存区域的指针
游标数据默认是静态的,游标打开后,不能反映新的数据变化
游标对应的指针是一个自动处理的指针,对游标执行fetch后,游标会自动移动
游标分为:显式游标和隐式游标
显式游标:由用户自己声明和使用的游标,所有的操作必须用户参与
1、显式游标的基本操作:
1、声明游标
CURSOR 游标名 [(参数列表)]
[RETURN 类型]
IS
SQL语句
2、打开游标:open 游标名;
3、提取游标:fetch 游标名 into 变量列表或record类型;
4、关闭游标:close 游标名
范例:显示emp表的所有的员工名、工资和部门号
DECLARE
--计划需要的数据
CURSOR cur_1
IS
SELECT ename,sal,deptno FROM emp;
v_ename emp.ename%TYPE;
v_sal emp.sal%TYPE;
v_deptno emp.deptno%TYPE;
BEGIN
--执行相应定义中的SQL语句,得到这些数据
OPEN cur_1;--如果游标已经打开,再次打开就会出错
--提取游标指针指向的当前行
FOR i IN 1 .. 14 LOOP
FETCH cur_1 INTO v_ename,v_sal,v_deptno;
dbms_output.put_line('-------------------------');
dbms_output.put_line(v_ename);
dbms_output.put_line(v_sal);
dbms_output.put_line(v_deptno);
END LOOP;
--关闭游标
CLOSE cur_1;
--释放游标使用的内存
END;
范例:根据输入的部门号,显示部门中的员工姓名和工资
--范例:根据输入的部门号,显示部门中的员工姓名和工资
DECLARE
CURSOR cur(v_deptno emp.deptno%TYPE)
IS
SELECT * FROM emp WHERE deptno=v_deptno;
emp_row emp%ROWTYPE;
v_dno INT;
BEGIN
v_dno:=&deptno;
OPEN cur(v_dno);
FOR i IN 1 .. 14 LOOP
FETCH cur INTO emp_row;
dbms_output.put_line('---------------');
dbms_output.put_line('姓名:'||emp_row.ename);
dbms_output.put_line('工资:'||emp_row.sal);
END LOOP;
CLOSE cur;
END;
5、游标属性:
对象.属性名
游标名%属性名
%isopen:如果游标被打开了,取值true
%found:如果最近一次fetch返回了新的数据,取值true
%notfound:如果最近一次fetch返回的数据没有变化(没有提取到数据),取值true
%rowcount:返回最近一次提取的行的编号。游标每一行自动添加编号,编号从1开始
DECLARE
--计划需要的数据
CURSOR cur_1
IS
SELECT ename,sal,deptno FROM emp;
v_ename emp.ename%TYPE;
v_sal emp.sal%TYPE;
v_deptno emp.deptno%TYPE;
BEGIN
--得到这些数据
if not cur_1%isopen then
OPEN cur_1;
end if;
--提取游标指针指向的当前行
loop
FETCH cur_1 INTO v_ename,v_sal,v_deptno;
exit when cur_1%notfound;
dbms_output.put_line('------------'||cur_1%rowcount||'-------------');
dbms_output.put_line(v_ename);
dbms_output.put_line(v_sal);
dbms_output.put_line(v_deptno);
END LOOP;
--关闭游标
CLOSE cur_1;
END;
2、隐式游标:
Oracle中select、insert、delete,update语句,在执行时Oracle会为之自动产生一个游标
游标名为SQL,此游标是由Oralce自动声明、打开、提取和关闭,用户不需要任何操作。
SQL%isopen:永远都是false,因为自动操作此属性无意义。
SQL%found=true SQL%notfound=false
select:有返回的数据行
insert:是插入成功的
delete:删除成功的
update:更新成功的
SQL%rowcount=true
select:返回的数据行数
insert:是插入数据行数
delete:删除的数据行数
update:更新的数据行数
隐式游标广泛的应用于PL/SQL编程中,用于检查DML操作的状态
--通过输入岗位名称,工资的增量,修改相应岗位的员工的工资
declare
v_job emp.job%type;
v_increment emp.sal%type;
begin
v_job:=&job;
v_increment:=&increment;
update emp set sal=sal+v_increment where job=upper(v_job);
if SQL%found then
dbms_output.put_line('更新成功,共更新了'||SQL%rowcount||'行');
commit;
else
dbms_output.put_line('没有成功的更新任何数据');
end if;
end;
3、引用游标变量(ref cursor):类似于record和table类型。
1、声明类型
TYPE type_name is ref cursor;
2、声明变量
var_name type_name;
3、Oracle内部已经内置了一个类型:sys_refcursor 是ref cursor类型实例
var_name sys_refcursor; == 步骤1+步骤2
引用游标变量:
引用游标变量,在声明时,并没有指定SQL语句,必须在打开游标时,定义SQL语句,
再后面的操作和普通游标一致
范例:显示emp表的所有的员工名、工资和部门号
declare
mycur sys_refcursor; --没有定义SQL语句的
v_ename emp.ename%TYPE;
v_sal emp.sal%TYPE;
v_deptno emp.deptno%TYPE;
begin
open mycur for select ename,sal,deptno from emp; --利用相应的SQL语句确定了打开的结果集
loop
fetch mycur into v_ename,v_sal,v_deptno;
exit when mycur%notfound;
dbms_output.put_line('------------'||mycur%rowcount||'-------------');
dbms_output.put_line(v_ename);
dbms_output.put_line(v_sal);
dbms_output.put_line(v_deptno);
END LOOP;
close mycur;
end;
引用游标变量:主要应用于PL/SQL的子程序向调用环境提供多行多列的结果集。
应用程序和Oracle之间接口。
create or replace procedure get_inf_by_dno
(v_deptno emp.deptno%type,
v_res out sys_refcursor)
as
begin
open v_res for select * from emp where deptno=v_deptno;
--向调用环境提供多行多列的结果集时,需要游标有效,所以只需要打开即可
--不能提取和关闭
end;
11、异常处理:
程序的错误类型:
1、编译类型错误:
用户误输入,或者其他与用户编程能力相关的语法错误。
此类错误可以避免,提高编程能力、借助开发工具。
2、运行时错误:
程序本事在语法上没有错误,很多时候都是能够正常运行的
有些时候因为用户提供的数据的问题或者网络、硬件等问题,而使程序不能够正常运行的错误
这种错误依赖于用户的输入和环境配置,因此很难完全避免
在程序中可以根据各种类型的错误,做相应的处理——异常处理
语法:
Exception
When 异常名称1 [or 异常名称2…] then
语句段1
When 异常名称3 [or 异常名称4…] then
语句段2
When others then --除了1、2、3、4这4个异常以外的所有异常
语句段3
End;
Others:定义在异常处理部分没有处理的任何一个已知异常
异常名称:是一个名字,当此异常被引发,以异常名作为的表达式取true
异常的引发方式:
1、显式引发:raise 异常名;
2、错误引发:
这种形式,无需用户干预,只要是这个异常对应的错误发生了,此异常就自动引发
异常类型:
1、预定义异常==错误引发(由Oracle内置错误引发内置异常)
2、用户自定义异常:
1、显示引发
2、错误引发
1、由Oracle内置错误引发
2、由用户自定义错误引发
1、显式引发:
定义一个与错误无关的异常(显式引发)
1、声明:e_name exception;
2、引发:raise e_name;
declare
v_deptno emp.deptno%type;
e_dno exception;
v_sal number(6,2);
begin
v_deptno:=&deptno;
if v_deptno=20 then
raise e_dno;
else
select avg(sal) into v_sal from emp where deptno=v_deptno;
dbms_output.put_line(v_sal);
end if;
exception
when e_dno then
dbms_output.put_line('20号部门的工资不能查阅');
end;
--以上程序,异常处理没有什么实际的意义,主要是程序员出于调试目的而添加的
--或者是理解异常处理的流程原理
2、Oracle(预定义)内置异常:
随Oracle产品发布且已经定义好的异常,可以无需定义直接使用
dup_val_on_index == -1 :捕获违反了主键约束的错误
too_many_rows :捕获试图向一个标量类型的变量赋予多个值
no_data_found :捕获select into语句没有返回任何数据
1、dup_val_on_index == -1 :捕获违反了主键约束的错误
begin
insert into emp(empno,ename,deptno)
values(&empno,&ename,&deptno);
commit;
exception
when dup_val_on_index then
dbms_output.put_line('员工编号不能重复');
end;
2、too_many_rows —— -1422 :捕获试图向一个标量类型的变量赋予多个值
insert into emp(empno,ename,job,sal,deptno)
values(1445,'abc','HY',2300,20);
declare
v_sal emp.sal%type;
begin
select sal into v_sal from emp where job='&job';
exception
when too_many_rows then
dbms_output.put_line('此岗位有多名员工,无法获得其全部工资');
end;
3、no_data_found —— -1403:捕获select into语句没有返回任何数据
declare
v_sal emp.sal%type;
begin
select sal into v_sal from emp where empno=&empno;
exception
when no_data_found then
dbms_output.put_line('没有输入的员工编号');
end;
3、用户自定义异常:
1、自定义异常<—引发—内置错误关联
2、自定义异常<—引发—自定义错误关联
1、自定义异常<—引发—内置错误关联
语法:
1、声明
exception_name exception;
2、绑定
pragma exception_init(exception_name,error_code);
3、捕获
when ... then
declare
e_notnull exception;
pragma exception_init(e_notnull,-1400);
e_fk exception;
pragma exception_init(e_fk,-2291);
begin
insert into emp(empno,ename,deptno)
values(&empno,&ename,&deptno);
commit;
exception
when dup_val_on_index then
dbms_output.put_line('员工编号不能重复');
when e_notnull then
dbms_output.put_line('员工编号不能为空');
when e_fk then
dbms_output.put_line('提供了一个不存在的部门号');
end;
2、自定义异常<—引发—自定义错误关联
利用自定义错误和异常可以实现很多复杂的业务规则
1、定义错误
raise_application_error(error_code,error_message);
error_code:-20000 —— -20999,错误编码,Oracle预留的范围
error_message:错误消息,为用户提供与错误相关的说明(2048字节)
2、声明异常
exception_name exception;
3、绑定错误和异常
pragma exception_init(exception_name,error_code);
4、捕获异常
when exception_name then ...
范例:不允许操作20号部门的信息(DML:触发器)
declare
v_deptno emp.deptno%type;
v_increment emp.sal%type;
--e_dno exception;
--pragma exception_init(e_dno,-20001);
begin
v_deptno:=&deptno;
v_increment:=&increment;
if v_deptno=20 then
raise_application_error(-20001,'20号部门工资不能修改');
else
update emp set sal=sal+v_increment where deptno=v_deptno;
if SQL%found then
dbms_output.put_line('更新成功,共更新了'||SQL%rowcount||'行');
commit;
else
dbms_output.put_line('没有成功的更新任何数据');
end if;
end if;
--exception
--when e_dno then
--dbms_output.put_line('20号部门工资不能修改');
end;
范例:对以上程序,加入限制,如果不是周六和周日,就报错
declare
v_deptno emp.deptno%type;
v_increment emp.sal%type;
e_dno exception;
pragma exception_init(e_dno,-20001);
e_time exception;
pragma exception_init(e_time,-20002);
begin
v_deptno:=&deptno;
v_increment:=&increment;
if to_char(sysdate,'day') not in ('星期六','星期日') then
raise_application_error(-20002,'非法日期,操作失败');
else
if v_deptno=20 then
raise_application_error(-20001,'20号部门工资不能修改');
raise_application_error(-20001,'20号部门工资不能修改');
else
update emp set sal=sal+v_increment where deptno=v_deptno;
if SQL%found then
dbms_output.put_line('更新成功,共更新了'||SQL%rowcount||'行');
commit;
else
dbms_output.put_line('没有成功的更新任何数据');
end if;
end if;
end if;
/*
exception
when e_dno then
dbms_output.put_line(SQLCODE);
dbms_output.put_line(SQLERRM);
when e_time then
dbms_output.put_line(SQLCODE);
dbms_output.put_line(SQLERRM);
*/
exception
when others then
dbms_output.put_line(SQLCODE);
dbms_output.put_line(SQLERRM);
end;
获取错误栈信息:
1、错误代码:
SQLCODE:函数,返回错误的代码
2、错误消息:
SQLERRM:函数,返回错误消息文本
总结:
异常分类:
1、显式引发:
raise exception_name
显式引发的一般与错误无关,主要用于调试
2、错误引发
1、预定义异常
2、用户定义异常
1、与Oracle内置错误关联
2、与用户自定义错误关联
异常的传播规律:
在一个程序中可能发生错误的地方:
1、执行部分
如果在执行部分发生错误或者显式引发了异常,首先确定当前块是否有相应的
异常处理程序,如果有则异常处理程序处理此异常,如果没有,就把这个异常
传播到此块的父块,如果没有父块,则传播到调用环境,一旦传播到调用环境
如果调用环境没有处理,则导致程序非正常的终止运行。
2、声明部分
如果在声明部分发生错误或者显式引发了异常,则立即将此异常传播到父块,
即使本块有相应的处理程序,也不起作用。如果没有父块,则传播到调用环境
一旦传播到调用环境,如果调用环境没有处理,则导致程序非正常的终止运行。
3、异常处理
如果在异常处理部分发生错误或者显式引发了异常,则立即传播到块外,且一
次只能引发一次异常
===================子程序====================
Oracle子程序:
1、存储过程:一般性、经常性的数据库操纵(DML)
2、存储函数:经常性的运算、数据分析
3、程序包
1、为何要在Oracle中编写子程序:
1、子程序在应用程序开发中不是必须的。
2、优点1:
匿名块和SQL语句不能永久存储在数据库中,每一次执行,都需要重新编写或调用
源代码文本,不方便。子程序是数据库方案对象,可以存储在数据库中,且在数据
库中既存储子程序的源代码,又存储子程序的编译代码,调用、修改方便,执行效
率高。
3、优点2:
在高级预言中,通过嵌入SQL语句也能够完成子程序的任务,但是,很多任务,是
需要多条SQL语句,多个步骤完成的,所以,在高级语言中很多时候需要,多次向
数据库发送多条不同SQL语句。每一条都要通过网络以单独的TCP包发送,会占用
网络带宽,影响服务器性能。子程序可以把多个任务在一个程序中实现,且不需要
将SQL代码在网络上传输,节省网络资源。
4、缺点:过多的在数据库中实现子程序,会让应用程序过分依赖数据库类型,导致应
用程序迁移数据库平台变的困难。
2、如何编写子程序:
1、确定主要任务所对应的SQL语句。
2、根据确定的SQL语句,确定需要用户输入的数据和用户需要得到的数据
即确定子程序的形参(输入参数和输出参数(返回值))
3、完善程序的相关内容,验证、检查、异常等
3、如何利用已有的匿名块改写为子程序:
1、添加头部信息中的形式参数名、类型和模式
2、没有包含在形参中的变量,作为私有变量在as...begin之间声明
3、(可选)针对调用环境修改输出方式
create or replace procedure mod_sal
(
v_deptno IN emp.deptno%type,
v_increment IN emp.sal%type
)
as
e_dno exception;
pragma exception_init(e_dno,-20001);
e_time exception;
pragma exception_init(e_time,-20002);
begin
if to_char(sysdate,'day') not in ('星期六','星期日') then
raise_application_error(-20002,'非法日期,操作失败');
else
if v_deptno=20 then
raise_application_error(-20001,'20号部门工资不能修改');
raise_application_error(-20001,'20号部门工资不能修改');
else
update emp set sal=sal+v_increment where deptno=v_deptno;
if SQL%found then
dbms_output.put_line('更新成功,共更新了'||SQL%rowcount||'行');
commit;
else
dbms_output.put_line('没有成功的更新任何数据');
end if;
end if;
end if;
exception
when others then
dbms_output.put_line(SQLCODE);
dbms_output.put_line(SQLERRM);
end;
4、如何调用子程序:
1、call subprogram_name;(JAVA)
2、execute subprogram_name;(SQL*PLUS)
3、begin
subprogram_name;
end;
5、参数模式
1、IN:输入模式 :在程序中按常量处理
2、OUT:输出模式 :在程序中按变量处理
3、IN OUT :输入/输出模式 :在程序中按变量处理
create or replace procedure frm_name
(fname IN OUT varchar2,
lname IN OUT varchar2,
frm IN varchar2 default 'FL',
fullname OUT varchar2)
as
begin
fname:=initcap(fname);
lname:=initcap(lname);
if frm='FL' then
fullname:=fname||' '||lname;
else
fullname:=lname||' '||fname;
end if;
end;
6、如何给子程序传递参数
IN:实参可以是变量、常量、字面值;OUT和IN OUT :实参必须是变量
1、按位置传参
1、var fname varchar2(20)
exec :fname:='HY';
2、var lname varchar2(20)
exec :lname:='GJ';
3、var full varchar2(40)
4、exec frm_name(:fname,:lname,'FL',:full);--按照子程序的形式参数的位置,依次传递
等价写法:
begin
frm_name(:fname,:lname,'FL',:full);
end;
5、print full;
2、按名称传参
exec frm_name(lname=>:lname,frm=>'FL',fname=>:fname,fullname=>:full);
形式参数=>绑定变量,与位置无关
3、混合传参
exec frm_name(:fname,:lname,fullname=>:full,frm=>'LF');
7、子程序的形参数据类型不能设置精度。
以下程序的头中,形参的数据类型只能是varchar2,不能定义精度
如果希望输入的数据对精度有要求,一般使用锚定类型,由数据库底层
限制
create or replace procedure frm_name
(fname IN OUT varchar2,
lname IN OUT emp.ename%type,
frm IN varchar2 default 'FL',
fullname OUT varchar2) 。。。。
8、函数的调用:
函数是一个具有返回值的特殊过程,调用时需要考虑返回值的问题
create or replace function fm_name
(fname IN OUT varchar2,
lname IN OUT varchar2,
frm IN varchar2 default 'FL')
return varchar2
as
fullname varchar2(40);
begin
fname:=initcap(fname);
lname:=initcap(lname);
if frm='FL' then
fullname:=fname||' '||lname;
else
fullname:=lname||' '||fname;
end if;
return fullname;
end;
调用方法:
exec :full:=fm_name(:fname,:lname,'FL');
=============PL/SQL存储过程=============
存储过程:
Create [or replace] procedure proc_name
([arg1 in | out | in out]] arg_type1,…)
Is | as
声明部分
Begin
执行部分
Exception
异常处理部分
End [proc_name];
存储函数
Create [or replace] function fun_name
([arg1 [ in | out | in out ]] arg_type1,…)
Return return_type
Is | as
声明部分
Begin
执行部分
Exception
异常处理部分
End [fun_name];
范例:向emp表添加数据(empno、ename、deptno)
--外部程序可以访问的是形参列表中的变量
create or replace procedure add_emp
(v_empno emp.empno%type,
v_ename emp.ename%type,
v_deptno emp.deptno%type)
as
begin
insert into emp(empno,ename,deptno)
values(v_empno,v_ename,v_deptno);
commit;
end;
--使用内部局部变量实现,子程序内部的逻辑
create or replace procedure add_emp
(v_empno emp.empno%type,
v_ename emp.ename%type,
v_deptno emp.deptno%type)
as
v_count int; --子程序内部的局部变量,外部调用环境不能访问
begin
select count(*) into v_count from dept where deptno=v_deptno;
if v_count<>0 then
insert into emp(empno,ename,deptno)
values(v_empno,v_ename,v_deptno);
commit;
dbms_output.put_line('数据添加成功');
else
dbms_output.put_line('非法的部门号');
end if;
end;
--如何处理程序对调用环境的反馈,针对高级语言的调用,一般采用操作的状态代码表示
create or replace procedure add_emp
(v_empno emp.empno%type,
v_ename emp.ename%type,
v_deptno emp.deptno%type,
v_code out int)
as
v_count int; --子程序内部的局部变量,外部调用环境不能访问
begin
select count(*) into v_count from dept where deptno=v_deptno;
if v_count<>0 then
insert into emp(empno,ename,deptno)
values(v_empno,v_ename,v_deptno);
commit;
v_code:=1;--1代表操作成功
else
v_code:=0;--0代表操作失败
end if;
end;
--增强程序的健壮性,增加异常处理
create or replace procedure add_emp
(v_empno emp.empno%type,
v_ename emp.ename%type,
v_deptno emp.deptno%type,
v_code out int,
v_resultset out sys_refcursor)
as
e_notnull exception;
pragma exception_init(e_notnull,-1400);
e_fk exception;
pragma exception_init(e_fk,-2291);
begin
insert into emp(empno,ename,deptno)
values(v_empno,v_ename,v_deptno);
if SQL%found then
v_code:=1;
commit;
open v_resultset for select * from emp where empno=v_empno;
end if;
exception
when dup_val_on_index then
v_code:=-1;--不推荐直接使用数字
when e_fk then
v_code:=-2;
when e_notnull then
v_code:=-3;
when others then
v_code:=SQLCODE;
end;
存储函数:
在语法上讲,可以和过程相互替代,但是,函数更多的是用于计算机或返回
特定的数据
create or replace function insert_emp
(v_empno emp.empno%type,
v_ename emp.ename%type,
v_deptno emp.deptno%type,
v_resultset out sys_refcursor)
return int
as
v_code int;
e_notnull exception;
pragma exception_init(e_notnull,-1400);
e_fk exception;
pragma exception_init(e_fk,-2291);
begin
insert into emp(empno,ename,deptno)
values(v_empno,v_ename,v_deptno);
if SQL%found then
v_code:=1;
commit;
open v_resultset for select * from emp where empno=v_empno;
end if;
return v_code;
exception
when dup_val_on_index then
v_code:=-1;--不推荐直接使用数字
return v_code;
when e_fk then
v_code:=-2;
return v_code;
when e_notnull then
v_code:=-3;
return v_code;
when others then
v_code:=SQLCODE;
return v_code;
end;
函数一般作为单行函数使用是非常灵活的,所以以上的例子是很少见的
具有一个输入参数和一个返回值的函数(没有输出参数)
范例:通过用户的编号,获得用户年收入(月工资和补助求和乘以12)
create or replace function get_salary
(v_empno emp.empno%type)
return number
as
v_sal number(10,2);
begin
select (sal+nvl(comm,0))*12 into v_sal from emp where empno=v_empno;
return v_sal;
end;
单行函数的调用和过程不同:
1、在PL/SQL块中调用
begin
:sal:=get_salary(7788);
end;
2、表达式调用(SQL语句)
declare
v_sal number(10,2);
begin
v_sal:=get_salary(7788);
dbms_output.put_line(v_sal);
end;
select get_salary(empno) from emp(只有单行函数才可以使用的方式)
什么是单行函数:
不能在函数中修改数据库表
不可以包含事务控制、会话控制、系统控制和DDL语句
不能是局部函数
只能带有IN参数不可以包含OUT和IN OUT参数
只能返回标量数据类型
添加适当的异常处理,提高程序的健壮性
create or replace function get_salary
(v_empno emp.empno%type)
return number
as
v_sal number(10,2);
begin
select (sal+nvl(comm,0))*12 into v_sal from emp where empno=v_empno;
return v_sal;
end;
提高程序的通用性:
add_emp过程只能向emp表添加数据,一般添加数据一个表对应一个子程序。
如果我们想修改emp表的特定列
的数据,是不是需要针对一个列,写一个子程序呢?
动态SQL语句
Oralce不推荐使用动态SQL语句,因为性能问题
create or replace procedure mod_inf
(v_tablename varchar2,
v_colname varchar2,
v_newvalues varchar2,
v_condition varchar2,
v_resultset out sys_refcursor,
v_code out int)
as
SQLStr varchar2(2048);
begin
SQLStr:='UPDATE '||v_tablename||' SET '||v_colname||' = '||v_newvalues||' WHERE '||v_condition;
--dbms_output.put_line(SQLStr);
EXECUTE IMMEDIATE SQLStr;
IF SQL%FOUND THEN
v_code:=1;
COMMIT;
SQLStr:='SELECT * FROM '||v_tablename||' WHERE '||v_condition;
open v_resultset for SQLStr;
END IF;
end;
什么是局部子程序
在块的声明部分实现的子程序
可见范围、作用域是本块
在声明部分的尾部进行定义(前面定义类型,变量、常量等)
出现交叉引用子程序时,需要前向声明
局部子程序支持重载
create or replace function get_salary
(v_empno emp.empno%type)
return number
as
v_sal number(10,2);
function chk_empno
(v_empno emp.empno%type)
return boolean
as
v_num int;
begin
select count(*) into v_num from emp where empno=v_empno;
if v_num=0 then
return false;
else
return true;
end if;
end;
begin
if chk_empno(v_empno) then
select (sal+nvl(comm,0))*12 into v_sal from emp where empno=v_empno;
return v_sal;
else
return 0;
end if;
end;
===============程序包===============
由逻辑上相关的类型、变量、常量、子程序等集成在一起的命名的PL/SQL块
程序包的使用可以有效的隐藏信息,实现集成化的模块化的程序设计
作为完整的单元存储在数据库,以名称标识,具有面向对象的特性
把PL/SQL编程元素逻辑的组织在一起,实现代码重用、封装,保存,面向对象
结构(构成)
1、包标准:定义公共组件
在包标准部分定义的任何程序元素对外部调用环境都是可以使用的
2、包体:定义私有组件,实现公共组件
包体定义但在包标准中不存在的组件都是私有的,外部调用环境不能使用。
在包标准中定义了但没有实现的元素(子程序),需要在包体中实现其功能
建立一个包标准:
create or replace package test
as
--公共变量
v_code int;
v_msg varchar2(2000);
--公共常量
C_SUCCESS constant int:=1;
C_EX_PK constant int:=-1;
C_EX_FK constant int:=-2291;
c_EX_NOTNULL constant int:=-1400;
--公共异常
e_notnull exception;
pragma exception_init(e_notnull,-1400);
e_fk exception;
pragma exception_init(e_fk,-2291);
--以上的元素无需实现,在包体中可以使用,外部程序也可以使用
--子程序只提供声明,具体的实现代码需要在包体中完成
--公共函数
function get_salary
(v_empno emp.empno%type)
return number;
--公共过程
procedure add_emp
(v_empno emp.empno%type,
v_ename emp.ename%type,
v_deptno emp.deptno%type,
v_resultset out sys_refcursor);
end;
--包体的实现不是必须的,只有在包标准定义了子程序,而需要实现这些子程序时才需要包体
--包标准不依赖于包体,即使有定义的子程序。
--包体依赖于包标准
注意事项1:
在包体中定义和实现的子程序必须和包标准中的子程序同名,同参数列表
注意事项2:
充分考虑代码的复用:
1、常量、异常、变量的重用
2、私有子程序的重用
create or replace package body test
as
--实现包标准中声明的子程序和包体中声明的私有子程序
--这是一个私有函数,用于验证员工的存在性,因为此函数
--没有在包标准中声明,必然是私有函数,此函数只能在包内部使用,外部程序不可见
function chk_emp
(v_empno emp.empno%type)
return boolean
as
v_num int;
begin
select count(*) into v_num from emp where empno=v_empno;
if v_num=0 then
return false;
else
return true;
end if;
end;
--重载chk_emp函数,因为v_ename和v_empno两个形参的类型不同,可以实现重载
function chk_emp
(v_ename emp.ename%type)
return boolean
as
v_num int;
begin
select count(*) into v_num from emp where upper(ename)=upper(v_ename);
if v_num=0 then
return false;
else
return true;
end if;
end;
--下面的子程序是公共的,因为这些子程序在包标准部分已经声明了
function get_salary
(v_empno emp.empno%type)
return number
as
v_sal number(10,2);
begin
if chk_emp(v_empno) then
select (sal+nvl(comm,0))*12 into v_sal from emp where empno=v_empno;
return v_sal;
else
return 0;
end if;
end;
procedure add_emp
(v_empno emp.empno%type,
v_ename emp.ename%type,
v_deptno emp.deptno%type,
v_resultset out sys_refcursor)
as
--把程序中声明的局部异常,放到程序包标准部分,这些异常就可以被Oracle中所有程序使用
begin
insert into emp(empno,ename,deptno)
values(v_empno,v_ename,v_deptno);
if SQL%found then
v_code:=C_SUCCESS;
commit;
open v_resultset for select * from emp where empno=v_empno;
end if;
exception
when others then
v_code:=SQLCODE;
v_msg:=SQLERRM;
end;
begin
--初始化
v_code:=0;
v_msg:='这是变量的初始化';
end;
--测试程序
--公共变量、常量、异常等的使用方法
declare
v_resultset sys_refcursor;
v_row emp%rowtype;
begin
test.add_emp(&empno,&ename,&deptno,v_resultset);
if test.v_code=1 then
loop
fetch v_resultset into v_row;
exit when v_resultset%notfound;
dbms_output.put_line(v_row.ename);
end loop;
else
dbms_output.put_line(test.v_msg);
end if;
end;
==============数据库分页===============
单表分页:
create or replace procedure sep_page
(curr_page int,
page_size int,
v_result out sys_refcursor,
v_code out int)
as
v_start int;
v_end int;
v_count int;
v_pageCount int;
begin
select count(*) into v_count from emp;
v_pageCount:=ceil(v_count/page_size);
v_start:=(curr_page-1)*page_size+1;
v_end:=curr_page*page_size;
open v_result for
select * from (select rownum id,emp.* from emp) t where t.id between v_start and v_end;
if curr_page<=v_pageCount then
v_code:=1;
else
v_code:=0;
end if;
end;
通用分页:可以对任何查询分页
--创建公共变量,接受查询语句的行数
create or replace package test
as
v_code int;
end;
--实现分页过程
create or replace procedure sep_page
(curr_page int,--要求得到的当前页码
page_size int,--设置每一页显示多少行
v_sql varchar2,--设置对那些数据分页
v_result out sys_refcursor,--返回指定页的结果集
v_code out int)--返回是否操作成功
as
v_start int;
v_end int;
v_count int;
v_pageCount int;
SQLStr varchar2(2000);
s1 varchar2(2000);
begin
--构建一个匿名块的SQL字符串,通过公共变量test.v_code赋值,获得查询的总行数
s1:='begin select count(*) into test.v_code from ('||v_sql||'); end;';
execute immediate s1;
v_count:=test.v_code;
--计算机可以分多少页
v_pageCount:=ceil(v_count/page_size);
--计算机页的起始行号
v_start:=(curr_page-1)*page_size+1;
--计算页的结束行号
v_end:=curr_page*page_size;
SQLStr:='select * from (select rownum id,tmp.* from ('||v_sql||')tmp) t where t.id between '||v_start||' and '||v_end;
open v_result for
SQLStr;
if curr_page<=v_pageCount then
v_code:=1;
else
v_code:=0;
end if;
end;
========================触发器========================
特殊的子程序:
一般子程序是通过PL/SQL块调用(execute、begin...end、call等),才能够执行。
而触发器是不能够调用的,只能在满足了相应的条件后自动执行。
触发器的执行需要哪些条件?
触发事件:
什么样的动作,才有可能导致触发器触发
Insert、Delete、Update、Create、Alter、Drop、Grant ......
触发时机:
触发器是在某个触发事件前还是后执行
触发对象:
并不是触发事件中的语句对任何数据库对象操作,触发器就触发。
只有对特定的对象操作,触发器才有可能触发
触发类型:
语句级还是行级
语句级:触发事件执行一次就触发一次(只关心触发事件的类型)
行级:触发事件在具体执行时,每操作一行就触发一次(不但关系类型,还关系对行的影响)
触发条件:
触发器在满足以上所有条件以外,还要满足的额外条件
条件谓词:
触发器中的语法元素
Inserting、Updating、Deleting等
代表正在执行的操作是什么,比如:用户正在执行Insert操作,则Inserting返还true
触发器的语法规则:
--语句级触发器:
CREATE [OR REPLACE] TRIGGER trigger_name --触发器名
{BEFORE | AFTER} --触发时机
{insert | update | delete}
[OR {insert | update | delete]}…] --触发事件
ON {table_name|view_name} --触发对象
PL/SQL_block | call procedure_name; --触发动作
--行级触发器:
CREATE [OR REPLACE] TRIGGER trigger_name --触发器名
{BEFORE | AFTER} --触发时机
{insert | update | delete [of column1[,column2,…]]}
[Or insert | update | delete [of column3[,column4,…]]}…] --触发事件
ON {table_name|view_name} --触发对象
FOR EACH ROW --行级触发(如果没有for each row就是语句级)
[WHEN condition] --触发条件
PL/SQL_block | call procedure_name; --触发动作
语句级触发器的使用:
语句级触发器只关心语句本事是否是被执行了,不对此语句所影响的数据进行考察,
所以语句级触发器只能完成一些比较简单的功能。
===============语句级触发器范例:=================
1、安全性控制
是特指应用程序级的安全性控制,与Oracle的权限、用户和角色等安全性无关。
例题:
用户在非工作时间不能修改EMP表
1、如何阻止用户修改表
通过自定义错误,并触发这个错误
2、如何描述时间是合法还是非法
9-17 周一——周五
3、如何确定触发器的各个设置:
前触发
Insert、update、delete
emp
当用户对emp表,执行Insert或者Update或者Delete操作之前,
检查现在的时间是不是在9-17 或者 周一到周五,如果不是,则
通过raise_application_error产生一个错误。
create or replace trigger chk_emp
before insert or update or delete
on emp
begin
if to_char(sysdate,'HH24') not between 9 and 17 or
to_char(sysdate,'day') in ('星期六','星期日') then
raise_application_error(-20001,'error time');
end if;
end;
2、安全性审核
要求对emp表的任何操作,都记录在Inf表中,记录的信息包含:
操作者的用户名、操作的时间、操作类型。
当用户在emp表上执行Insert或者Update或者Delete操作之后,
记录用户名、时间和操作类型
create or replace trigger audit_emp
after insert or update or delete
on emp
declare --和子程序不同,此处用declare,不能用is或as
v_type varchar2(10);
begin
case
when updating then
v_type:='UPDATE';
when Deleting then
v_type:='DELETE';
else
v_type:='INSERT';
end case;
insert into inf values(USER,SYSDATE,v_type);
--commit;此处COMMIT语句不能添加。因为触发器中不能出现任何事务控制语句
end;
触发器所执行的DML语句和触发这个触发器的DML语句是在一个事务中的。
触发器中不能出现任何事务控制语句
===============行级级触发器范例:=================
触发器中包含For each row则说明此触发器是行级的。
行级触发器就可以访问DML语句所操作的数据
触发语句
insert into emp(empno,ename) values(1200,'aaa');
导致以下语句的执行
insert into inf values(USER,SYSDATE,v_type);
如果希望访问触发语句中的值,则使用限定词访问
:old :可以访问触发语句中修改前的值(即将删除的数据)
:new :可以访问触发语句中修改后的值(即将插入的数据)
例如:想访问1200,则可以通过表达式:new.empno访问
update emp set sal=3000 where sal=2000;
访问2000:——:old.sal
访问3000:——:new.sal
Insert语句:只有:new
Delete语句:只有:old
Update语句::OLD和:NEW都可以访问
范例1:自动编号
1、创建序列发生器:
create sequence a;
2、建立一个测试表
create table test(
id int primary key,
name varchar2(20));
3、创建触发器,实现ID列的自动唯一编号
create or replace trigger seq_id
before insert
on test
for each row
declare
v_num int;
begin
select a.nextval into v_num from dual;
:new.id:=v_num;
end;
练习题:
1、创建序列发生器b:
create sequence b;
2、建立一个测试表
create table test1(
id varchar2(10) primary key,
name varchar2(20));
3、创建触发器实现自动生成主键,主键的格式是HYGJ0001
范例2:检测时间的合法性:
要求用户对hiredate列的数据修改时,此日期不能大于当前系统时间
--利用Check约束实现:
create table emps as select * from emp;
alter table emps add constraint chk_hiredate check(hiredate<=sysdate);
因为Oracle中check表达式不能包含系统函数,以上语句是非法的
所以约束不能完成要求,下面就用触发器解决:
create or replace trigger chk_hiredate
before update of hiredate or insert
on emps
for each row
begin
if :new.hiredate>sysdate then
raise_application_error(-20001,'非法的时间,时间必须小于或等于当前时间');
end if;
end;
--如果update后不加of hiredate,则任何针对emps表的Update操作都会触发此触发器
--加上update of hiredate,则只有在更新hiredate列时,触发器才会被触发
范例3:数据备份
此处的数据备份不同于Oracle的备份,是指应用程序中逻辑备份。
在很多网站或系统中,需要跟踪用户的爱好、兴趣或者用户的操作历史等信息,信息量很大
且系统一般分析用的数据集中在近期,对于较晚的信息,可能临时不需要。但是,有些时候
这些较晚的数据也是偶尔使用的,因此,这些数据不能物理删除,只能逻辑删除。
逻辑删除:数据是被放在一个备份表中,对于日常一般应用,此表的数据是不会被使用的,
只有在特定情况下才使用此表,那么,把数据从日常表中备份到备份表中就是逻辑删除,
因为在日常操作中,哪些备份的数据好像不存在一样,用户认为这些数据被“删除”了
题目:
用户删除emp表的记录时,将这些删除的行,自动备份到bak表
create table bak as select * from emp;
delete from bak;
写触发器,实现自动备份
思路:当用户删除表emp中的数据时,在删除之前,把即将删除的数据插入到BAK表中。
1、为何是行级触发器?
关键要看,是否在触发器中使用触发语句所操作的数据,如何使用则是行级,不使用就是语句级
触发语句是:delete from emp ...
触发器中使用:insert into bak ...,Insert语句使用的数据就是被删除的数据
2、触发时机是Before还是After?
很多时候Before还是After,对触发器不重要。
当前例子中,使用Before或After,是没有关系的,都可以。
但是有些时候,Before和After就不能相互替代:自动编号
create or replace trigger bak_emp_del
before [after] delete
on emp
for each row
begin
insert into bak
values(:old.EMPNO,:old.ENAME,:old.JOB,:old.MGR,
:old.HIREDATE,:old.SAL,:old.COMM,:old.DEPTNO);
end;
范例4:级联删除和级联更新
如果主表中的数据被删除,则从表中与之相关的数据一起被删除:级联删除
如果主表中的数据被修改,则从表中与之相关的数据一起被修改:级联更新
如果把DEPT表中20号部门删除,此操作能不能成功呢?
此操作不能完成,因为如果20部门不存在了,则emp表原20部门的记录就违反了
外键约束(所属20部门,在DEPT中不存在了)
为了能够删除dept表中的行,则创建触发器:
1、思路:当用户删除dept表中的行,则在删除前,将EMP表中与之相关的行的部门号
设置为空或者将对应行删除。
--1:级联更新,设置为空
create or replace trigger cascade_delete
before delete
on dept
for each row
begin
update emp set deptno=null where deptno=:old.deptno;
end;
--2:级联删除相关行
create or replace trigger cascade_delete
before delete
on dept
for each row
begin
delete from emp where deptno=:old.deptno;
end;
用户修改了部门号,则从表EMP中相对应的部门号也随之修改
update dept set deptno=90 where deptno=20
--3:数据级联的更新
create or replace trigger cascade_update
before update of deptno
on dept
for each row
begin
update emp set deptno=:new.deptno where deptno=:old.deptno;
end;
问题:如何将多个触发器合并成一个触发器
对2和3的对比,触发对象、触发时机和类型都是相同的,则两个触发器可以合并成一个
create or replace trigger cascade_update
before update of deptno or delete
on dept
for each row
begin
case
when updating('deptno') then
update emp set deptno=:new.deptno where deptno=:old.deptno;
else
delete from emp where deptno=:old.deptno;
end case;
end;
范例5:生成流水号(参考范例1的练习题)
生成连续流水号:要求编号不能在删除后,不再使用
(一个序号在删除后可以在下一次增加新数据时再次使用)
例如:编号19,删除后19就不能出现在表中,现在需要19再次被使用
思路:在删除相应编号的行时,自动把删除的行对应的编号存储在一个表中,当用户再次
添加新数据时,首先检查表中是否有被删除的编号,如果有就优先使用,如果没有则通过
序列发生器生成新的。
1、创建存储删除的编号的表TMP
create table tmp(id int);
2、创建生成编号的序列
create sequence c;
3、创建测试数据表test
create table test
(id int primary key,
name varchar2(20));
4、通过触发器实现:连续编号
1、当添加新行则生成编号
2、当删除行则备份编号
create or replace trigger seq_id
before insert or delete
on test
for each row
declare
v_num int;
v_count int;
begin
case
when Inserting then
--判断TMP表中是否有已删除的编号
select count(*) into v_count from tmp;
if v_count=0 then
--如果没有已删除的编号则从序列生成
select c.nextval into v_num from dual;
else
--如果有则取最小值
select min(id) into v_num from tmp;
--取出最小值,并把最小值删除
delete from tmp where id=v_num;
end if;
:new.id:=v_num;
else
insert into tmp values(:old.id);
end case;
end;
练习:生成流水号(连续的)
--当向test表插入数据时,先检查表中有没有已经删除的行,如果有
就优先使用已经删除的编号,且从小到大依次使用,如果没有,就利用
序列发生器生成新的值。
要求流水号格式:HYGJ2009010120000006
前缀:固定的,HYGJ
20090121:入职日期
20:入职时的部门号,如果没有部门则是00
000006:编号
创建测试表:
create table test
(id varchar2(20),
name varchar2(20),
deptno int references dept(deptno));
创建序列
create sequence b;
创建备份表
create table del_id
(id int);
创建触发器
5、替代触发器:
--替代触发器只应用于视图
--针对默认不能执行特定的DML语句的视图,创建的触发器,
目的:使不能更新的视图可以执行相应的DML语句
替代触发器的触发时机只有:instead of
替代触发器一定是“行级触发器”
用于替代特定的DML语句
create or replace trigger trigger_name
instead of
insert | update | delete | insert or update | insert or delete | update or delete
| insert or update or delete | [update of column_name,...]
on view_name
for each row
PL/SQL Block
--创建视图(连接视图)
create or replace view emps
as
select empno,ename,deptno,dname from emp join dept using(deptno)
insert into emps values(1234,'aaa',90,'HR');
连接视图不能执行Insert操作
通过替代触发器,把不能执行的Insert语句替代成可以执行的insert
思路:当用户向emps视图插入数据时,把这条Insert语句替代成分别向emp和dept插入数据
create or replace trigger instead_insert_emps
instead of insert
on emps
for each row
declare
v_count int;
begin
--检查部门号是否存在
select count(rowid) into v_count from dept where deptno=:new.deptno;
--如果不存在,则添加新的部门信息,如果存在则什么都不做
if v_count=0 then
insert into dept(deptno,dname) values(:new.deptno,:new.dname);
end if;
--向员工表插入数据
insert into emp(empno,ename) values(:new.empno,:new.ename);
end;
Oracle数据库技术总结:
1、数据查询和基本操作:
1、Select语句(连接操作、子查询、Oracle函数)
2、Insert、Update、Delete操作
2、配置Oracle客户端
1、本地网络服务名(主机、端口、协议、SID)
2、SQL*Plus或其他客户端的配置及连接
3、实现数据库:
1、数据存储:Tablespace
2、Oracle用户
3、权限和角色(系统、对象权限,默认Oracle角色)
4、实现方案对象(表、视图、约束、序列、同义词、索引)
5、编写PL/SQL子程序
4、PL/SQL编程:
1、实现存储过程和函数
2、实现程序包
3、实现触发器
在工程实践中,如何实现应用程序的数据库(Oracle)
1、应用项目的需求分析
2、绘制数据库模型图(软件技术中需要UML图)
3、实现基本的表及视图(由模型图生成)
4、创建表空间(可选)
5、创建Oracle用户,且授予适当的权限
6、执行由模型图生成的SQL脚本(start)
7、由项目需要编写数据库端的应用程序(子程序和触发器)
1、创建序列
2、创建触发器实现自动编号
3、根据相对应的业务需求,编写触发器(为了降低高级语言的编程难度)
4、根据业务需求,编写过程、函数、包
在工程实践中,如何实现应用程序的数据库(MS SQL Server)
1、应用项目的需求分析
2、绘制数据库模型图(软件技术中需要UML图)
3、实现基本的表及视图(由模型图生成)
4、创建数据库
5、创建SQL Server用户,且授予适当的权限(可选)
6、执行由模型图生成的SQL脚本
7、由项目需要编写数据库端的应用程序(子程序和触发器)
1、根据相对应的业务需求,编写触发器(为了降低高级语言的编程难度)
2、根据业务需求,编写过程、函数