Oracle入门学习详解

个人博客原文链接

断断续续花了半个月多月的时间才较为详细的过了一遍Oracle的知识点,以下为学习过程中我所记录的知识点和练习的例题。Oracle整个学下来,我感觉相比较SQLServer和MYSQL根本不是同一层次的东西,在Oracle中有许多概念是其它数据库所没有的,特别是PLSQL这个设计让我特别惊讶,没想到还可以在sql语言中处理数据流程,就像在写Java代码一样,大大提高了开发效率。

环境:Windows 7 64位,Oracle 11g,SQLPlus

注意事项

1.oracle没有Scott用户的问题

  • a.首先连接管理员账户conn sys/pwd as sysdba;

  • b.执行scott.sql文件
    找到scott.sql文件放在D盘根目录
    @D:\scott.sql

  • c.连接scott conn scott/TIGER;

  • d.查看当前用户 show user;

  • e.若要修改scott用户密码先登录管理员账户

  • g.更改scott用户密码 alter user scott identified by lousen;

2.SQL中的null值

  • 包含null的表达式都为null
    解决办法:a不为null就返回a,a为null则返回b NVL(a,b)

  • null永远!=null (不能写=null)
    解决办法:select * from emp where comm is null

  • 空值是无效的,未指定的,未知的或不可预计的值,空值不是0或者空格

3.日期和字符串都用单引号表示''

4.SQL语句和SQLPlus命令的区别
SQL:一种语言,ANSI标准,关键字不能缩写,使用语句控制数据库中的表的定义信息和表中的数据。
SQLPlus:一种环境,Oracle的特性之一,关键字可以缩写,命令不能改变数据库中的数据的值集中运行。

5.Oracle字符大小写敏感,日期格式敏感
Oracle严格区分字符串的大小写
默认日期格式DD-MON-RR(日-月-年)
查看默认日期格式
select * from v$nls_parameters;

6.更改日期格式

  • a.更改当前会话的日期格式
    alter session set NLS_DATE_FORMAT='yyyy-mm-dd';

  • b.更改所有数据库的日期格式(需sys权限)
    alter system set NLS_DATE_FORMAT='yyyy-mm-dd';

7.where解析的顺序
从右到左,故在使用例如or的逻辑表达式时可以优化,正确的写在右边就可以不用执行左边的了,提高效率

基本操作

1.查看当前用户的表
select * from tab;

2.查看指定表结构
desc emp;

3.设置行宽
先查看当前行宽
show linesize
修改行宽
set linesize 200

4.设置列宽(format=for)
设置ename列的宽为8个字符
col ename format a8
设置sal列的宽为4个数字的长度
col sal for 9999

5.设置页面分页显示的行数
set pagesize 20

6.修改上一句sql语句

  • a.进入上一句sql语句修改 ed

  • b.执行上一句sql语句 /

7.清屏
host cls

8.录屏
把操作记录在d盘根目录下的笔记.txt中
开始:spool d:\笔记.txt
结束:spool off

9.SQL执行时间的开关
- set timing on
- set timing off

10.执行外部sql文件
例:执行d盘根目录下的test.sql语句
@d:\test.sql

11.查看执行计划
explain plan for select * from ....
select * from table(dbms_xplan.display);
从右往左,从下往上看执行计划

12.登录
普通用户:conn username/password[@ip:1521/orcl];
超级管理员:conn username/password[@ip:1521/orcl] as sysdba;

13.查看当前连接数据库的用户
show user;

14.查看当前用户下的表
select * from tab;
注:超级管理员的权限很大,可以使用select * from [用户名].[表名] 来查看某用户下的表的数据

15.打开输出开关
set serveroutput on;

Oracle的体系结构

  • 数据库
    Oracle数据库是数据的物理存储。这就包括(数据文件ORA或者DBF、控制文件、联机日志、参数文件)。其实Oracle数据库的概念和其它数据库不一样,这里的数据库是一个操作系统只有一个库。可以看作是Oracle就只有一个大数据库。

  • 实例
    一个Oracle实例(Oracle Instance)有一系列的后台进程(Backguound Processes)和内存结构(Memory Structures)组成。一个数据库可以有n个实例。

  • 用户
    用户是在实例下建立的。不同实例可以建相同名字的用户。

  • 表空间
    表空间是Oracle对物理数据库上相关数据文件(ORA或者DBF文件)的逻辑映射。一个数据库在逻辑上被划分成一到若干个表空间,每个表空间包含了在逻辑上相关联的一组结构。每个数据库至少有一个表空间(称之为system表空间)。

    每个表空间由同一磁盘上的一个或多个文件组成,这些文件叫数据文件(datafile)。一个数据文件只能属于一个表空间。

  • 数据文件
    数据文件是数据库的物理存储单位。数据库的数据是存储在表空间中的,真正是在某一个或者多个数据文件中。而一个表空间可以由一个或多个数据文件组成,一个数据文件只能属于一个表空间。一旦数据文件被加入到某个表空间后,就不能删除这个文件,如果要删除某个数据文件,只能删除其所属于的表空间才行。

  • 创建表空间
    一个数据库下可以建立多个表空间,一个表空间可以建立多个用户、一个用户下可以建立多个表。

    基本语法

    create tablespace test  --表空间名称
    datafile 'd:\Oracle\Space\test.dbf' --表空间对应的数据文件
    size 50m    --定义的表空间初始大小
    autoextend on   --自动增长,当表空间存储占满时,自动增长
    next 10m;   --指定一次增长的大小
    

用户

数据库与其它数据库产品的区别在于,表和其它的数据库对象都是存储在用户下的。

  1. 创建用户
    例:创建一个账号为tom,密码为123,默认表空间为test的用户

    create user tom 
    identified by 123
    default tablespace test
  2. 用户赋权
    新创建的用户没有任何权限,登录后会有提示
    Oracle中已存在三个重要的角色:connect角色,resource角色,dba角色。

    • connect角色:是授予最终用户的典型权利,最基本的
    • resource角色:是授予开发人员的
    • dba角色:拥有全部特权,是系统最高权限

    例:授予tom角色resource权限
    grant resource to tom;

    注:新用户必须在sys用户下给其赋予权限,否则无法正常登录

基本查询操作

1.给列起别名有三种方式

  • a.select name as "姓名" from user;

  • b.select name "姓名" from user;(推荐使用)

  • c.select name 姓名 from user;

2.查询表中所有数据

  • a.select * from user;(需要解析*

  • b.select name,age,sex,password from user;(sql优化推荐使用)

3.去掉查询结果中重复的数据(distnict作用于后面的所有列)
select distinct job,dept from emp;

4.连接符||
select 'Hello' || ' World' "字符串" from dual;
ps:dual表即伪表无实际含义,仅为符合语法(from后必须跟表)

查询员工信息:xxx的薪水是xxx
select ename||'的薪水是'||sal "信息" from emp;

5.在。。。之间(between and)
从员工表查询工资在[1000,2000]的数据
select * from emp where sal between 1000 and 2000;`
注:a.含有边界 b.小值在前,大值在后

6.在。。。中(in)
从员工表查询部门号是10和20的员工
select * from emp where deptno in(10,20);

从员工表查询部门号不是10和20的员工
select * from emp where deptno not in (10,20);

注:如果集合中有null,不能使用not in,但可以使用in

7.模糊查询
查询名字是4个字的员工
select * from emp where ename like '____'

查询名字中含有下划线的员工
select * from emp where ename like '%\_%' escape '\'

8.order by (order by作用于后面的所有列,desc只作用于离它最近的列)

  • a.order by后面可以 + 列、表达式、别名、序号(列的序号)

    查询员工id,姓名,月薪,年薪并按年薪降序排列(表达式)
    select empno,ename,sal,sal*12 from emp order by sal*12 desc

    查询员工id,姓名,月薪,年薪并按年薪降序排列(别名)
    select empno,ename,sal,sal*12 "年薪" from emp order by "年薪" desc

    查询员工id,姓名,月薪,年薪并按年薪降序排列(序号)
    select empno,ename,sal,sal*12 "年薪" from emp order by 4 desc

  • b.多个列排序
    从员工表查询数据,先根据员工编号升序排列,编号相同的话就根据月薪升序排列
    select * from emp order by deptno,sal;

    从员工表查询数据,先根据员工编号升序排列,编号相同的话就根据月薪降序排列
    select * from emp order by deptno,sal desc;

    从员工表查询数据,先根据员工编号降序排列,编号相同的话就根据月薪降序排列
    select * from emp order by deptno desc,sal desc;

  • c.null的排序 (注:在排序时null值最大)
    查询员工的信息,按照奖金升序排序,null在下边
    select * from emp order by comm;

    查询员工的信息,按照奖金降序排序,null在上边
    select * from emp order by comm desc;

    如何实现降序排列的null值在上边呢?
    select * from emp order by comm desc nulls last;

单行函数

一.字符函数

1.大小写控制函数

  • a.lower(转小写)

  • b.upper(转大写)

  • c.initcap(首字母大写)
    select lower('Hello WOrld') 转小写,upper('Hello WOrld') 转大写,initcap('hello wOrld') 首字母大写 from dual;

2.字符控制函数

  • a.concat(连接字符串)
    select concat('hello',' world') from dual;

  • b.substr(截取字符串)
    select substr('Hello World',1,5) from dual;

  • c.length(字符数),legthb(字节数)
    select length('Hello World') 字符数,lengthb('Hello World') 字节数 from dual;
    select length('上海') 字符数,lengthb('上海') 字节数 from dual;

  • d.instr(查询自子符串的位置,从1开始数)
    select instr('hello world','ll') 位置 from dual;

  • e.lpad(左填充) rpad(右填充)
    select lpad('abcd',10,'*') 左,rpad('abcd',10,'*') 右 from dual;

  • f.trim(去掉前后指定的字符)
    select trim('H' from 'Hello WorldH') from dual;

  • g.replace(替换指定的字符)
    select replace('Hello World','l','*') from dual;

二.数字函数

  • round(四舍五入)
    select round(45.926,2) 一,round(45.926,1) 二,round(45.926,0) 三,round(45.926,-1) 四,round(45.926,-2) 五 from dual;

  • trunc(截断)
    select trunc(45.926,2) 一,trunc(45.926,1) 二,trunc(45.926,0) 三,trunc(45.926,-1) 四,trunc(45.926,-2) 五 from dual;

  • mod(求余)
    select mod(1600,300) 余数 from dual;

三.日期函数
Oracle中的日期型数据实际含有两个值:日期和时间。
默认日期格式为:DD-MON-RR

  1. 当前日期
    select sysdate from dual;

  2. 格式化时间
    select to_char(sysdate,'yyyy-mm-dd hh24:mi:ss') from dual;

  3. 日期的数学运算
    a.在日期上加上或减去一个数字(天数)结果还是日期
    b.两个日期相减返回日期之差的天数
    c.可以用数字除24向日期中加上或减去小时

    select ename,hiredate,(sysdate-hiredate) 天,(sysdate-hiredate)/7 星期,(sysdate-hiredate)/30 月,(sysdate-hiredate)/365 年 from emp;

    注:不允许日期+日期

  4. months_between(相差的月数)
    select ename,hiredate,(sysdate-hiredate)/30 一,months_between(sysdate,hiredate) 二 from emp;

  5. add_months(向指定日期添加指定月数)
    select add_months(sysdate,53) from dual;

  6. last_day(本月的最后一天)
    select last_day(sysdate) from dual;

  7. next_day(指定日期的下一个星期,’1’表示下个星期日,或直接用’sunday’表示)
    select next_day(sysdate,'1') from dual;

  8. 在日期中插入字符串
    select to_char(sysdate,'yyyy-mm-dd hh24:mi:ss"今天是"day') from dual;

  9. round(日期的四舍五入)
    同数字

  10. trunc(日期的截取)
    同数字

四.数据类型转换
1. 隐式
varchar2 or char -> number
varchar2 or char -> date
number -> varchar2
date -> varchar2

  1. 显式

    • to_char(日期格式转换)
      to_char(date,'yyyy-mm-dd hh24:mi:ss')
      注:’YYYY’=年,’MM’=月,’DD’=日,’DAY’=星期

    • to_char(数字格式转换)
      to_char(number,'L9,999.99')
      注:’9’=数字,’0’=零,’$’=美元,’L’=本地货币符号,’.’=小数点,’,’=千位符

    • to_number(字符转数字)
      to_number('$123.33','$999.99')

    • to_date(字符转日期)
      to_date('2018-05-26 12:38:49','yyyy-mm-dd hh24:mi:ss')

五.通用函数(适用于任何数据类型,包括null)
1. nvl (a,b)
当a为null时,返回b,否则返回a

  1. nvl(a,b,c)
    当a为null时,返回c,否则返回b

  2. nullif(a,b)
    当a=b时,返回null,否则返回a

  3. coalesce(a,b,c…)
    从左到右找到第一个不为null的值并返回

六.条件表达式
1. case表达式
实现董事长工资+1000,经理+800,其他+400
select empno,ename,job,sal 涨前,case job when 'PRESIDENT' then sal+1000 when 'MANAGER' then sal+800 else sal+400 end 涨后 from emp;

  1. decode表达式(Oracle独有)
    实现董事长工资+1000,经理+800,其他+400
    select empno,ename,job,sal 涨前,decode(job,'PRESIDENT',sal+1000,'MANAGER',sal+800,sal+400) 涨后 from emp;

多行函数(分组函数)

一.什么是分组函数
分组函数作用于一组数据,并对一组数据返回一个值。

二.常见的组函数
1. AVG
计算所有员工的平均工资
select avg(sal) "平均工资" from emp;

  1. COUNT
    计算所有员工的数量
    select count(*) "总数" from emp;

  2. MAX
    查找最高工资
    select max(sal) "最高工资" from emp;

  3. MIN
    查找最低工资
    select min(sal) "最低工资" from emp;

  4. SUM
    计算工资之和
    select sum(sal) "总和" from emp;

三.组函数与空值
组函数忽略空值。

例:计算平均值时,如果存在空值,不同的方式得到的平均值结果也不同
1. 用总奖金数除以总人数(总人数不会有null,但有人的奖金为null)
select sum(comm)/count(*) from emp;

  1. 用总奖金数除以总获得奖金的人数(并不会把null加入运算)
    select sum(comm)/count(comm) from emp;

  2. 直接用avg组函数(并不会把null加入运算)
    select avg(comm) from emp;

故:需根据具体情况来看使用哪种平均数。以上三种情况1的值和2,3的不同,2和3的值相同。

在组函数中使用NVL函数,使分组函数无法忽略空值。
select avg(nvl(comm,0)) from emp;

四.多个列的分组(GROUP BY)
使用group by子句将表中的数据分成若干组。
1. group by子句(单列)
注1:在select列表中所有未包含在组函数中的列,都应该包含在group by子句中 。
例:按部门计算每个部门的平均工资
select deptno,avg(sal) from emp group by deptno;

注2:然而,包含在group by子句中的列不必包含在select列表中。
    `select avg(sal) from emp group by deptno;`
  1. group by子句(多列)
    先按第一个列分组,第一个列相同在按第二列分组,以此类推。
    select deptno,job,sum(sal) from emp group by deptno,job order by 1;

五.过滤分组(HAVING)
使用having过滤分组的前提条件。
1. 行已经被分组了
2. 使用了组函数
3. 满足having子句中条件的分组将被显示
例:按部门查找每个部门的最高工资并显示大于3000的数据
select deptno,max(sal) from emp group by deptno having max(sal)>3000;

where和having的区别:
where后面不可以使用多行函数。
select deptno,avg(sal) from emp where deptno=10 group by deptno;
等价于
select deptno,avg(sal) from emp group by deptno having deptno=10;

注:sql优化原则,尽量使用where,先过滤再分组,降低负荷。

六.group by子句的增强
select deptno,job,sum(sal) from emp group by rollup(deptno,job);
等价于
select deptno,job,sum(sal) from emp group by deptno,job;
+
select deptno,sum(sal) from emp group by deptno;
+
select sum(sal) from emp;

可按照报表的格式输出数据。

例:根据部门和职位计算工资总和的报表
1. 取消deptno的重复列显示,每行空两格。
break on deptno skip 2
2. 执行group by子句语句的增强
select deptno,job,sum(sal) from emp group by rollup(deptno,job);
3. 取消报表格式
break on null;

多表查询

一.笛卡尔集
1. 产生条件
省略连接条件
连接条件无效
所有表中的所有行互相连接

  1. 为避免笛卡尔集,可以在where中加入有效的连接条件
    例:有n张表,则需要至少(n-1)个有效的连接条件

  2. 在实际运行环境中,应避免使用笛卡尔全集
    例:有两张表,则产生的表(笛卡尔全集)的数据为两张表的列相加,两张表的行相乘

二.等值连接
例:从员工表和部门表查询员工信息:员工号 姓名 月薪 部门名称
select e.empno,e.ename,e.sal,d.dname from emp e,dept d where e.deptno=d.deptno;

三.不等值连接
例:从员工表和工资级别表查询工资级别在最低级别和最高级别之间的员工的信息:员工号 姓名 月薪 工资级别
select e.empno,e.ename,e.sal,s.grade from emp e,salgrade s where e.sal between s.losal and s.hisal;

四.外连接
对于某些不成立的记录,仍然希望包含在最后的结果中。
例:where e.deptno=d.deptno
当该部门不存在员工时,e.deptno为null,这个结果将不会被显示,若想要显示需使用外连接

  1. 左外连接
    当where e.deptno=d.deptno不成立的时候,等号左边的表仍然被包含在最后的结果中
    写法:where e.deptno=d.deptno(+)
    select d.deptno 部门号,d.dname 部门名称,count(e.empno) 人数 from emp e,dept d where e.deptno=d.deptno(+) group by d.deptno,d.dname;

  2. 右外连接
    当where e.deptno=d.deptno不成立的时候,等号右边的表仍然被包含在最后的结果中
    写法:where e.deptno(+)=d.deptno
    select d.deptno 部门号,d.dname 部门名称,count(e.empno) 人数 from emp e,dept d where e.deptno(+)=d.deptno group by d.deptno,d.dname;

五.自连接
通过表的别名,将同一张表视为多张表
例:从员工表中查询员工信息: 员工姓名 老板姓名
select e.ename 员工姓名,b.ename 老板姓名 from emp e,emp b where e.mgr=b.empno;

注:自连接不适合大表操作

六.层次查询
代替自连接查询,只对一张表进行操作,适合大表操作
原理:树形结构
例:从员工表中查询员工信息: 员工姓名 老板姓名
分析:当前员工的mgr即为上一层(prior)的员工,(mgr is null表示根节点)
select level,empno,ename,mgr from emp connect by prior empno=mgr start with mgr is null;

注:level是伪列

子查询

在一个查询的内部还包含另一个查询,则此查询称为子查询
子查询所要解决的问题:不能一步求解

  1. 常用格式
    例:查询工资比SCOTT高的员工信息
    select * from emp where sal > (select sal from emp where ename='SCOTT');

  2. 需要注意的问题

    • 合理的书写风格

    • 括号

    • 可以在主查询的where、select、having、from后面使用子查询

      • where
        select * from emp where sal > (select sal from emp where ename='SCOTT');

      • select
        select empno,ename,sal,(select job from emp where empno=7839) from emp;

      • having
        select deptno,sum(sal) from emp group by deptno having deptno=(select deptno from emp where ename='SCOTT');

      • from
        select * from (select empno,ename,sal from emp );

    • 强调from后面的子查询
      select * from (select empno,ename,sal,sal*12 annsal from emp);

    • 主查询和子查询可以不是同一张表,只要子查询返回的结果主查询可用即可
      select * from emp where deptno=(select deptno from dept where dname='SALES');

    • 一般不在子查询中排序;但top-n分析问题中,必须对子查询排序
      top-n问题:查询工资排名前三的员工的信息
      select rownum,empno,ename,sal from (select * from emp order by sal desc) where rownum<=3;

      注:rownum是伪列,永远按照默认的顺序生成,rownum只能使用<和<=,不能使用>和>=
      注:rownum永远从1开始,要分页需要使用特殊的手段
      select * from (select rownum r,e1.* from (select * from emp order by sal) e1 where rownum <=8 ) where r >=5;

    • 一般先执行子查询,再执行主查询;但相关子查询例外
      相关子查询:将主查询中的值 作为参数传递给子查询
      例:查询工资大于平均工资的员工的信息
      select empno,ename,sal,(select avg(sal) from emp where deptno=e.deptno) avgsal from emp e where sal > (select avg(sal) from emp where deptno=e.deptno);

    • 单行子查询只能使用单行操作符,多行子查询只能使用多行操作符

    • 子查询中的null

  3. SQL优化
    尽量使用多表查询代替子查询

  4. 多行子查询

    • in(在集合中)
      例:查询部门名称是SALES和ACCOUNTING的员工
      select * from emp where deptno in (select deptno from dept where dname='SALES' or dname='ACCOUNTING');

    • any(和集合中任意一个值比较)
      例:查询工资比30号部门任意一个员工高的员工信息
      select * from emp where sal > any (select sal from emp where deptno=30);

    • all(和集合中的所有值比较)
      例:查询工资比30号部门所有员工高的员工信息
      select * from emp where sal > all (select sal from emp where deptno=30);

  5. 多行子查询中的null

    • in
      in后面的集合中可以包含null

    • not in
      not in后面的集合不可以包含null
      解决办法:select * from emp where empno not in (select mgr from emp where mgr is not null);

集合运算

  1. UNION/UNION ALL(并集)

    • UNION
      返回两个集合的所有记录,重复部分只计算一次
      select deptno,job,sum(sal) from emp group by deptno,job
      union
      select deptno,to_char(null),sum(sal) from emp group by deptno
      union
      select to_number(null),to_char(null),sum(sal) from emp;

    相当于
    select deptno,job,sum(sal) from emp group by rollup(deptno,job);

    • UNION ALL
      返回两个集合的所有记录,重复部分计算两次
      SQL优化:两者都可以时,尽量使用union all
  2. INTERSECT(交集)
    返回同时属于两个集合的记录
    例:显示薪水同时位于700~1300和1201~1400的员工的信息
    select ename,sal from emp where sal between 700 and 1300 intersect select ename,sal from emp where sal between 1201 and 1400;

  3. MINUS(差集)
    返回属于第一个集合,但不属于第二个集合的记录
    例:显示薪水位于700~1300但不位于1201~1400的员工的信息
    select ename,sal from emp where sal between 700 and 1300 minus select ename,sal from emp where sal between 1201 and 1400;

  4. 集合运算注意事项

    • 参与运算的各个集合必须列数相同 且类型一致
    • 采用第一个集合作为最后的表头(故别名应在第一个集合)
    • order by永远写在最后(故order by应在最后一个集合)
    • 可使用括号来改变集合执行顺序
  5. SQL优化原则
    尽量不要使用集合运算,集合越多效率越低

处理数据

  • SQL的类型

    1. DML(data manipulation language 数据操作语言):insert update delete select
    2. DDL(data definition language 数据定义语言): create table,alter table,drop table,truncate table,create/drop view,sequence,index,synonym(同义词)
    3. DCL(data control language 数据控制语言):grant(授权) revoke(撤销权限)
  • DML语句

    1. insert
      基本语法
      insert into emp(empno,ename,sal,deptno) values(1002,'Mary',2000,10);

      一次性插入多条数据
      例:一次性将emp中所有10号部门的员工插入到emp10中
      insert into emp10 select * from emp where deptno=10;
      注:不必书写values子句
      注:子查询中的值的列表应与insert子句中的列名对应(*代表结构完全一致)

      注:海量插入数据

      1. 数据泵(PLSQL程序:dbms_datapump)
      2. SQL*Loader工具
      3. 外部表
    2. delete
      基本语法
      delete from emp where deptno=10;

      delete和truncate的区别:

      1. delete逐条删除;truncate先摧毁表 再重建
      2. (*)delete是DML(可以回滚) truncate是DDL(不可以回滚)
      3. delete不会释放空间 truncate会
      4. delete可以闪回(flashback) truncate不可以
      5. delete会产生碎片 truncate不会

      注:在ORACLE中delete比truncate速度快
      原因:undo数据(还原数据)

    3. update
      基本语法
      update emp set deptno=20 where deptno=10;

  • 事务
    ORACLE中事务的标志

    1. 起始标志
      事务中第一条DML语句

    2. 结束标志

      • 提交
        显式:commit
        隐式:正常退出exit,DDL,DCL

      • 回滚
        显示:rollback
        隐式:非正常退出,断电,宕机

    回滚到保留点

    • 使用savepoint语句在当前事务中创建保存点
    • 使用roll back to savepoint语句回滚到指定的保存点
      savepoint a;
      insert...
      delete...
      ...
      roll back to savepoint a;

    数据库的隔离级别
    Oracle 支持的 2 种事务隔离级别:READCOMMITED, SERIALIZABLE。Oracle 默认的事务隔离级别为: READ COMMITED

创建和管理表

  • 创建表
    基本语法

    create table emp2(
        ename varchar2(10),
        age number
    );

    在创建表的同时插入数据

    create table empinfo
    as
    select e.empno,e.ename,e.sal,e.sal*12 annsal,d.dname
    from emp e, dept d
    where e.deptno=d.deptno;

    创建表时只复制表结构,不插入数据

    create table emp3
    as
    select * from emp
    where 1=2;

    注:where的条件永为假,故只复制结构,不复制数据

  • 修改表

    1. 追加列
      alter table emp2 add photo blob;

    2. 修改列
      alter table emp2 modify ename varchar2(40);

    3. 删除列
      alter table emp2 drop column photo;

    4. 重命名列
      alter table emp2 rename column ename to username;

    5. 重命名表
      rename emp3 to test1;

    6. 删除表
      drop table test1;

    7. Oracle的回收站

      • 查看回收站
        show recyclebin;

      • 清空回收站
        purge recyclebin;

      • 闪回
        flashback table test1 to before drop;
        若已存在同名的表
        flashback table test1 to before drop rename to test2;

      • 查询回收站的表
        表名加上”“即可

      • 回收站有两个同名的表
        根据删除时间分辨
        闪回优先恢复最近删除的表

      • 管理员用户没有回收站
        使用管理员用户进行删除操作时需十分谨慎

  • 约束
    例:创建一个带约束的表

    create table student
    (
        sid number constraint student_pk primary key,
        sname varchar2(20) constraint student_name_notnull not null,
        gender varchar2(2) constraint student_gender check (gender in ('男','女')),
        email varchar2(40) constraint student_email_unique unique
        constraint student_email_notnull not null,
        deptno number constraint student_fk references dept(deptno) on delete set null
    );

其他数据库对象

  • 视图(view)
    视图的概念

    • 视图是一种虚表
    • 视图建立在已有表的基础上,视图依赖建立的表称为基表
    • 向视图提供数据内容的语句为select语句,可以将视图理解为存储起来的select语句
    • 视图向基表提供表数据的另一种形式

    视图的优点

    • 限制数据访问
    • 简化复杂查询
    • 提供数据的独立性
    • 同样的数据,可以有不同的显示方式

    注:视图并不能提高性能
    注:最好不要通过视图对表进行修改,通常加上with read only;

    基本语法

    create  or replace  view empinfoview
    as
    select e.empno,e.ename,e.sal,e.sal*12 annsal,d.dname
    from emp e, dept d
    where e.deptno=d.deptno
    with read only;
  • 序列(sequence)

    • 序列的概念

      • 自动提供唯一的数值
      • 共享对象
      • 主要用于提供主键值
      • 将序列值装入内存可以提高访问效率
        注:最好一个表使用一个序列,不要多个表共用一个,否则会产生裂缝
    • 序列的语法

      create sequence dept_seq
      increment by n 【设置步长,默认为1】
      start with n   【设置起始数,默认为1】
      maxvalue n|nomaxvalue   【设置最大值,默认为nomaxvalue】
      minvalue n|nominvalue   【设置最小值,默认为nominvalue】
      cycle|nocycle   【设置是否循环,默认为不循环】
      cache n|nocache 【设置缓存长度,默认为20】
      

    故,一般情况下只需要使用create sequence dept_seq;即可,其他为默认值。

    • 如何使用序列
      配合nextval和currval伪列

      • nextval
        返回序列下一个有效值,任何用户都可以使用

      • currval
        返回序列当前的值

      • nextval必须在currval之前指定,两者必须同时有效

      一般在插入数据时使用
      insert into test2 values(dept_seq.nextval,'Mike');

    • 序列裂缝

      • 回滚
      • 系统异常
      • 多个表共用同一序列
    • 删除序列
      删除之后,序列不可再次被引用
      drop sequence dept_seq

  • 索引(index)
    索引的概念
    - 相当于目录
    - 一种独立于表的模式对象,可以存储在与表不同的磁盘或表空间中
    - 通过指针加快oracle服务器的查询速度
    - 索引被破坏不会对表产生影响,影响的只是查询速度
    - 索引一旦创建,oracle会自动进行维护

    创建索引

    • 自动创建
      定义primary key和unique时,oracle自动创建索引

    • 手动创建
      例:在表test2的name列上创建索引
      create index test2_name_index on test2(name);

    什么时候创建索引

    • 列中值分布范围广泛
    • 列经常出现在where子句或连接条件中
    • 表经常被访问且数据量很大,访问的数据大概占数据总量的2%到4%

    删除索引
    drop index test2_name_index;

  • 同义词
    同义词概念

    • 使用同义词访问相同的对象
    • 方便访问其他用户的对象
    • 缩短对象名字的长度
    • 所有的数据对象都可以起别名

    基本语法
    create synonym hremp for hr.employess;

PL/SQL

PLSQL是Oracle对sql语言的过程化扩展,指在SQL命令语言中增加了过程处理语句(如分支、循环等),使SQL语言具有过程处理能力。

运行环境:Windows 7 64位,plsqldevelop 10

  1. PL/SQL程序结构

    declare
        说明部分(变量说明,光标申请,例外声明)
    begin
        语句序列
    exception
        例外处理语句
    End;
    
  2. 变量和常量说明

    • 说明变量
      var1 char(10); –oracle建表时字段的变量
      married boolean:=true; –赋初值
      psal number(10,2); –保留两位小数
      my_name emp.ename%type; –引用类型变量,即my_name的类型与emp.ename一致
      emp_rec emp%rowtype; –记录型变量,代表一行的变量

    • 记录型变量分量的引用
      emp_rec.ename = ‘Mike’;

    • 定义常量
      married constant boolean:=true;

    注意: := 赋值符号等价于java中的=号;
    注意: = 逻辑等,判断两个值是否相等,等价于java中的==号?;

    例:查询员工号为7369的员工的姓名,薪水并打印

    declare
        pename emp.ename%type;
        psal emp.sal%type;
    begin
        select ename,sal into pename,psal from emp where empno=7369;
        dbms_output.put_line(pename||'的工资是'||psal);
    end;
  3. if分支

    • 语法1
      IF 条件 THEN
      语句1;
      语句2;
      END IF;

    • 语法2
      IF 条件 THEN
      语句1;
      ELSE
      语句2;
      END IF;

    • 语法3
      IF 条件 THEN
      ELSIF 条件 THEN
      ELSIF 条件 THEN
      ELSIF 条件 THEN

      ELSE 语句
      END IF;

    • 例:判断不同年龄段的人群

      accept num prompt ‘请输入一个年龄’;
      declare
          mynum number := &num;
      begin
          if mynum < 18 then
              dbms_output.put_line('未成年人');
          elsif mynum >= 18 and mynum < 40 then
              dbms_output.put_line('中年人');
          elsif mynum >= 40 then
              dbms_output.put_line('老年人');
          end if;
      end;
      
  4. Loop循环

    • 语法1
      WHILE 条件 LOOP

      END LOOP;

    • 语法2(推荐)
      Loop
      EXIT when 条件;

      End loop;

    • 语法3
      FOR I IN 1 … 3 LOOP
      语句序列 ;
      END LOOP ;

    • 例:输出1到10的数字

      declare
          mynum number := 1;
      begin
          Loop
          EXIT  when   mynum > 10;
          dbms_output.put_line(mynum);
          mynum := mynum+1;
          End loop;
      end;
      
  5. 游标(光标)
    游标相当于java中集合的概念,可以用来存储查询返回的多条数据。

    光标的属性:

    1. %isopen(游标打开状态)
    2. %rowcount(影响的行数)
    3. %found(游标中还有值)
    4. %notfound(游标中没有值)

    游标的使用步骤:

    1. 现在declare中定义游标:cursor c1 is select job from emp;
    2. 在begin打开游标:open c1;
    3. 取一行游标的值到所定义的变量中:fetch c1 into pjob;
    4. 游标退出循环的条件:exit when c1%notfound
    5. 结束游标:close c1;

    注:pjob变量必须和emp表中的job列表的类型一致

    例1:使用游标输出emp表的员工姓名和工资

    declare
        --定义光标和变量
        cursor cemp is select ename,sal from emp;
        pename emp.ename%type;
        psal emp.sal%type;
    
    begin
        --打开光标
        open cemp;
    
        loop
            --把光标中存储的值赋给变量
            fetch cemp into pename,psal;
            --当光标中没有值时退出循环
            exit when cemp%notfound;
            dbms_output.put_line(pename||'的薪水是'||psal);
    
        end loop;
        --关闭光标
        close cemp;
    
    end;

    例2:查询某部门的员工姓名

    declare
      cursor cemp(dno emp.deptno%type) is select ename from emp where deptno=dno;
      pename emp.ename%type;
    begin
      open cemp(20);
      loop
        fetch cemp into pename;
        exit when cemp%notfound;
        dbms_output.put_line(pename);
      end loop;
      close cemp;
    end;
  6. 异常
    PLSQL的异常处理机制和Java十分相似,都是用来增强程序的健壮性和容错性的

    系统定义异常

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

    例:捕获除零异常

    declare 
        mynum number;
    begin
        mynum := 1/0;
    exception
        when zero_divide then
            dbms_output.put_line('除零异常');
        when value_error then
            dbms_output.put_line('转换异常');
        when no_data_found then
            dbms_output.put_line('空值异常');
        when others then
            dbms_output.put_line('其他异常');
    end;
    

    自定义异常
    在声明中定义异常,用raise抛出异常,再捕获异常

    例:查询部门编号是50的员工

    declare
        no_found exception;  --自定义异常
        cursor cemp is select ename from emp where deptno=50;
        pename emp.ename%type;
    
    begin
        open cemp;
            fetch cemp into pename;
            if cemp%notfound then
                raise no_found;  --抛出异常
            end if;
        close cemp;
    
        exception --捕获异常
            when no_found then
                dbms_output.put_line('没有找到该员工');
            when others then
                dbms_output.put_line('其他异常');
    end;
    
  7. 综合练习
    例1:打印每个年份(80,81,82,87)入职员工的人数

    详细设计:
    sql语句
    select to_char(hiredate,’yyyy’) from emp;
    –>游标–>循环判断–>退出循环–>打印结果

    变量
    count80 number := 0;
    count81 number := 0;
    count82 number := 0;
    count87 number := 0;

    编码:

    declare
      --入职年份
      cursor cemp is select to_char(hiredate,'yyyy') from emp;
      phiredate varchar2(4);
      --入职人数
      count80 number := 0;
      count81 number := 0;
      count82 number := 0;
      count87 number := 0;
     begin
        --打开光标
       open cemp;
            --循环判断,累计叠加
            loop
               --获取年份
              fetch cemp into phiredate;
              exit when cemp%notfound;
    
              if phiredate = '1980' then
                count80 := count80+1;
               elsif phiredate = '1981' then
                count81 := count81+1;
               elsif phiredate = '1982' then
                count82 := count82+1; 
                else
                  count87 := count87+1;
               end if;
    
            end loop;
        --关闭光标
       close cemp;
       --打印结果
       dbms_output.put_line('总人数:'||(count80+count81+count82+count87));
       dbms_output.put_line('1980:'||count80);
       dbms_output.put_line('1981:'||count81);
       dbms_output.put_line('1982:'||count82);
       dbms_output.put_line('1987:'||count87);
     end;
    

    例2:为员工涨工资。从最低工资调起每人涨10%,但工资总额不能超过5万元,请计算涨工资的人数和涨工资后的工资总额,并输出涨工资人数及工资总额。

    详细设计:
    sql语句
    select empno,sal from emp order by sal;
    –>游标–>循环–>退出条件:1.总金额>5w 2.加上最后涨工资的员工总金额>5w 3.not found

    变量:1. 初始值 2.最终得到
    涨工资的人数: countEmp number := 0;
    涨后的工资总额:salTotal number;
    1.涨前总金额:select sum(sal) into salTotal from emp;
    2.涨后=涨前+sal*0.1

    编码:

    declare 
        cursor cemp is select empno,sal from emp order by sal;
        pempno emp.empno%type;
        psal emp.sal%type;
        --涨工资人数
        countEmp number := 0;
        --涨后总工资
        salTotal number;
    begin
        open cemp;
        --涨后总工资的初始值为涨前总工资
        select sum(sal) into salTotal from emp;
        loop
            --涨后总工资大于5w时
            exit when salTotal > 50000;
                fetch cemp into pempno,psal;
            --所有人涨完工资了
            exit when cemp%notfound;
            --加上最后涨工资的员工总金额>5w 
            exit when (salTotal + 0.1*psal) > 50000;
                update emp set sal = psal*1.1 where empno=pempno;
                countEmp := countEmp+1;
                salTotal := salTotal + 0.1*psal;
        end loop;
        close cemp;
        dbms_output.put_line('涨工资人数为:'||countEmp);
        dbms_output.put_line('涨后总工资:'||salTotal);
    end;
    

    例3: 实现按部门分段(6000 以上、(6000 ,3000) 、3000 元以下)统计各工资段的职工人数、以及各部门的工资总额( 工资总额中不包括奖金)。

    详细设计:
    SQL语句
    外循环
    部门:select deptno from dept;
    内循环
    查部门中的员工薪水: select sal from emp where deptno=dno;

    变量:1. 初始值 2.最终得到
    部门号
    pdep emp.deptno%type;
    工资
    psal emp.sal%type;
    每个段的人数
    count1 number; count2 number; count3 number;
    部门的工资总额
    salTotal number;

    1. select sum(sal) into salTotal from emp where deptno=pdep;
    2. 累加

    编码:
    先创建一张表msg来存储数据

    create table msg(
        deptno number,
        count1 number,
        count2 number,
        count3 number,
        salTotal number
    );
    

    用plsql处理数据,将所得数据插入msg表

    declare
        cursor cdept is select deptno from dept;
        --声明部门号
        pdep dept.deptno%type;
        cursor cemp(dno emp.deptno%type) is select sal from emp where deptno = dno;
        --声明薪水
        psal emp.sal%type;
        --声明每个部门每个工资段的人数
        count1 number;
        count2 number;
        count3 number;
        --声明部门工资总额
        salTotal number;
    begin
        --打开部门游标,循环部门
        open cdept;
        --进入外循环
        loop
        fetch cdept into pdep;
        exit when cdept%notfound;
            --初始化count
            count1 := 0;
            count2 := 0;
            count3 := 0;
            --获取每个部门员工总工资
            select sum(sal) into salTotal from emp where deptno=pdep;
            --打开工资游标,循环判断工资,累计叠加人数
            open cemp(pdep);
    
            --进入内循环
            loop
            fetch cemp into psal;
            exit when cemp%notfound;
                if psal < 3000 then
                    count1 := count1+1;
                elsif psal >= 3000 and psal < 6000 then
                    count2 := count2+1;
                else
                    count3 := count3+1;
                end if;
            end loop;
            close cemp;
            --把结果插入到msg表中
            insert into msg values(pdep,count1,count2,count3,nvl(salTotal,0));
        end loop;
        close cdept;
        dbms_output.put_line('完成');
    end;
    
  8. 存储过程和存储函数

    • 存储过程
      指存储在数据库中供所有用户程序调用的子程序叫存储过程。

      例:给指定员工涨100工资,打印涨前和涨后工资
      先创建存储过程raiseSalary

      --创建存储过程raiseSalary,eno为形参,in表示输入,number表示实参类型
      create or replace procedure raiseSalary(eno in number)
      as
          --定义变量保存前的薪水
          psal emp.sal%type;
      begin
          --得到涨后薪水
          update emp set sal = sal + 100 where empno=eno;
          dbms_output.put_line('涨前:'||psal||'  涨后:'||(psal+100));
      end;
      

      再调用存储过程raiseSalary

      begin
        --给形参赋值
        raisesalary(eno => 7369);
        commit;
      end;
      
    • 存储函数
      函数为一个命名的存储程序,可带参数,并返回一个计算值。使用方式和存储过程类似,但必须要有一个return子句,用于返回函数值。

      例:查询某个员工的年收入

      先创建存储函数queryEmpIncome

      --创建存储函数queryEmpIncome,形参为eno,in表示输入,形参类型为number
      create or replace function queryEmpIncome(eno in number)
      --返回的值的类型
      return number
      as
          --定义变量
          psal emp.sal%type;
          pcomm emp.comm%type;
      begin
          select sal,comm into psal,pcomm from emp where empno=eno;
          --返回年收入
          return psal*12+nvl(pcomm,0);
      end;
      

      再调用存储函数

      begin
        :result := queryempincome(eno => 7369);
      end;
      
    • 过程和函数中的in/out
      一般来讲,存储过程和存储函数的区别在于函数可以有一个返回值;而过程没有返回值。

      但是,存储过程和存储函数都可以通过out来指定一个或多个输出参数。我们可以利用out参数,在存储过程和存储函数返回多个值。

      故,在一般原则下,只返回一个值就用存储函数,返回多个值就用存储过程。存储函数是旧版本的,为了兼容才保留下来的。

      例:查询某个员工的姓名,月薪,职位。
      先创建存储过程queryEmpInfo

      create or replace procedure queryEmpInfo(eno in number,pename out varchar2,psal out number,pjob out varchar2)
      as
      begin
          select ename,sal,job into pename,psal,pjob from emp where empno = eno;
      end;
      

      再调用queryEmpInfo

      begin
        queryempinfo(eno => 7369,
                     pename => :pename,
                     psal => :psal,
                     pjob => :pjob);
      end;
      
    • 在out参数中使用游标(适用于有很多输出out时)

      例:查询某个部门中所有员工的信息

      先申明包结构包头

      --包头
      CREATE OR REPLACE PACKAGE MYPACKAGE AS 
      
        type empcursor is ref cursor;
        procedure queryEmpList(dno in number,empList out empcursor);
      
      END;
      

      再创建包体

      --包体
      CREATE OR REPLACE PACKAGE BODY "MYPACKAGE" AS
      
        procedure queryEmpList(dno in number,empList out empcursor) AS
      
        BEGIN
      
          open empList for select * from emp where deptno=dno;
      
        END;
      
      END;
      

Java操作Oracle

  1. 连接Oracle数据库

    • 先导入连接oracle的jar包
    • 注册驱动
    • 连接数据库
    • 关闭连接

    JDBCUtils.java

    package demo;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    public class JDBCUtils {
    
        private static String driver = "oracle.jdbc.OracleDriver";
        private static String url = "jdbc:oracle:thin:@127.0.0.1:1521/xe";
        private static String user = "scott";
        private static String password = "TIGER";
    
        static{
            //注册驱动
            try {
                Class.forName(driver);
                //DriverManager.registerDriver(driver);
            } catch (ClassNotFoundException e) {
                throw new ExceptionInInitializerError(e);
            }
        }
    
        //连接数据库
        public static Connection getConnection(){
            try {
                return DriverManager.getConnection(url, user, password);
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        //关闭连接
        public static void release(Connection conn,Statement st,ResultSet rs){
            if(rs != null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }finally{
                    rs = null;//-----> Java GC
                }
            }
            if(st != null){
                try {
                    st.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }finally{
                    st = null;
                }
            }
            if(conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }finally{
                    conn = null;
                }
            }       
        }
    }
    
  2. 测试操作oracle的存储过程

    @Test
    public void testProcedure(){
        // {call <procedure-name>[(<arg1>,<arg2>, ...)]}
        String sql = "{call queryEmpInfo(?,?,?,?)}";
    
        Connection conn = null;
        CallableStatement call = null;
        try {
            conn = JDBCUtils.getConnection();
            call = conn.prepareCall(sql);
    
            //对于in参数,赋值
            call.setInt(1, 7839);
    
            //对于out参数,申明
            call.registerOutParameter(2, OracleTypes.VARCHAR);
            call.registerOutParameter(3, OracleTypes.NUMBER);
            call.registerOutParameter(4, OracleTypes.VARCHAR);
    
            //执行
            call.execute();
    
            //取出结果
            String name = call.getString(2);
            double sal = call.getDouble(3);
            String job = call.getString(4);
    
            System.out.println(name+"\t"+sal+"\t"+job);         
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            JDBCUtils.release(conn, call, null);
        }
    }
  3. 测试操作oracle的存储函数

    @Test
    public void testFunction(){
        //{?= call <procedure-name>[(<arg1>,<arg2>, ...)]}
        String sql = "{?=call queryEmpIncome(?)}";
    
        Connection conn = null;
        CallableStatement call = null;
        try {
            conn = JDBCUtils.getConnection();
            call = conn.prepareCall(sql);
    
            //对于out参数,声明,第一个?代表返回值
            call.registerOutParameter(1, OracleTypes.NUMBER);
    
            //对于in参数,赋值,第二个?代表输入的值
            call.setInt(2, 7839);
    
            call.execute();
    
            //取出结果
            double income = call.getDouble(1);
            System.out.println(income);
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            JDBCUtils.release(conn, call, null);
        }       
    }
    
  4. 测试操作oracle游标引用的存储过程

    @Test
    public void testCursor(){
        String sql = "{call MYPACKAGE.QUERYEMPLIST(?,?)}";
        Connection conn = null;
        CallableStatement call = null;
        ResultSet rs = null;
        try {
            conn = JDBCUtils.getConnection();
            call = conn.prepareCall(sql);
    
            //对于in参数,赋值
            call.setInt(1, 20);
    
            //对于out参数,申明
            call.registerOutParameter(2, OracleTypes.CURSOR);
    
            //执行
            call.execute();
    
            //取出结果,cursor相当于集合
            rs = ((OracleCallableStatement)call).getCursor(2);
            while(rs.next()){
                String name = rs.getString("ename");
                double sal = rs.getDouble("sal");
                String job = rs.getString("job");
                System.out.println(name+"\t"+sal+"\t"+job);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            JDBCUtils.release(conn, call, rs);
        }       
    
    }
    

触发器

数据库触发器是一个与表相关联的、存储的PL/SQL程序。每当一个特定的数据操作语句(Insert,update,delete)在指定的表上发出时,Oracle自动地执行触发器中定义的语句序列。

  1. 触发器的作用

    • 数据确认
    • 实施复杂的安全性检查
    • 做审计,跟踪表上所做的数据操作(相当于日志)
    • 数据的备份和同步
  2. 触发器的类型

    • 语句级触发器
      在指定的操作语句操作之前或之后执行一次,不管这条语句影响了多少行。

    • 行级触发器(for each row)
      触发语句作用的每一条记录都被触发。在行级触发器中使用:old和:new伪记录变量,识别值的状态。

  3. 触发器语法

    CREATE  [or REPLACE] TRIGGER  触发器名
       {BEFORE | AFTER}
       {DELETE | INSERT | UPDATE [OF 列名]}
       ON  表名
       [FOR EACH ROW [WHEN(条件) ] ]
    declare
        ……
    begin
       PLSQL块 
    End 触发器名;
    
  4. 触发器具体应用

    例1:禁止在非工作时间插入新员工到emp2表

    详细设计:

    1. 周末:to_char(sysdate,’day’) in (‘sunday’,’saturday’)
    2. 上班前,下班后(9am~18pm):to_number(to_char(sysdate,’hh24’)) not between 9 and 17
    3. 语句级

    代码实现:

    CREATE OR REPLACE TRIGGER  securityemp
        BEFORE INSERT
      ON EMP2
    declare
    begin
       if to_char(sysdate,'day') in ('sunday','saturday') or to_number(to_char(sysdate,'hh24')) not between 9 and 17 THEN
                --禁止insert操作,在插入之前就手动抛出错误
                raise_application_error(-20001,'禁止在非工作时间插入新员工!');
         end if;
    End securityemp;
    

    例2:涨后的薪水不能少于涨前的薪水(数据的确认)

    详细设计:

    1. if 涨后的薪水 < 涨前的薪水
    2. 行级

    代码实现:

    create or replace trigger checksalary
    before update
    on emp
    for each row
    begin
      --if 涨后的薪水 < 涨前的薪水 then
      if :new.sal < :old.sal then
        raise_application_error(-20002,'涨后的薪水不能少于涨前的薪水.涨前:'||:old.sal||'   涨后:'||:new.sal);
      end if;
    end;
    

    例3:限制每个部门人数不超过10人

    详细设计:

    1. 用游标存储所有部门
    2. 循环判断每个部门人数+1后是否超过10人
    3. 超过10人抛出错误

    代码实现:

    CREATE OR REPLACE TRIGGER  checkEmpNumber
        BEFORE INSERT
      ON EMP
    declare
    cursor cemp is select deptno from dept;
    cdept dept.deptno%type;
    countEmp number;
    BEGIN
      open cemp;
        loop
            fetch cemp into cdept;
            exit when cemp%notfound;
            select count(*) into countEmp from emp where deptno = cdept;
            if countEmp+1 > 10 then
                raise_application_error(-20003,'部门:'||cdept||'  员工已有10人!');
            end if;
        end loop;
        close cemp;
    END;
    

    注:手动抛出的错误代码必须在[-20001,-20999]。

猜你喜欢

转载自blog.csdn.net/a1135497143/article/details/80645578
今日推荐