Oracle--初学小白基础篇(第一版)

文章目录

  • 一、Oracle11g介绍
    • 1、Oracle11g安装和卸载
      • Oracle11g安装
      • Oracle11g卸载
        • 1、使用Oracle Universal Installer管理工具卸载
        • 2、使用`deinstall.bat`批处理文件卸载
    • 2、Oracle管理工具
      • ①SQL*PLUS
      • ②PLSQL Developer
      • ③OEM(Oracle企业管理器)
  • 二、Oracle11g体系结构
    • 1、Oracle为什么要有一个复杂的体系结构?
    • 2、Oracle数据库管理系统的体系结构
      • 1. Oracle服务器
      • 2. Oracle实例
      • 3. Oracle数据库
      • 4. 连接Oracle实例
      • 5. 服务器进程
      • 6. 后台进程
        • 6.1 LGWR
        • 6.2 DBWR
        • 6.3 SMON
        • 6.4 PMON
        • 6.5 CKPT
        • 6.6 ARCH
  • 三、SQL基础
    • 1、简单查询
    • 2、where条件过滤
    • 3、group by子句分组查询
    • 4、having子句分组过滤
    • 5、order by排序
    • 6、多表关联查询
    • 7、Oracle常用函数
      • 1. 字符类函数
      • 2. 数字类函数
      • 3. 时间函数
      • 4. 聚合类函数
    • 8、子查询的使用
      • 1. 单行子查询
      • 2. 多行子查询
      • 3. 关联子查询(重点)
    • 9、DML语句:增删改
      • 1. INSERT
      • 2. DELETE
      • 3. UPDATE
    • 10、事务处理
      • 1. 事务处理的结果
      • 2. 事务的4大特性(ACID)
      • 3. 事务的开始和结束
        • 事务的开始
        • 事务的结束
      • 4. 提交事务和撤销事务的Oracle系统流程
        • 提交事务
        • 撤销事务
      • 5、设置保存点并回滚【分段回滚】
  • 四、PL/SQL基础
    • 1、PL/SQL块结构
    • 2、注释
    • 3、PL/SQL字符集【允许出现的字符】
    • 4、数据类型及变量常量
      • 1. 基本数据类型
      • 2. 特殊数据类型
      • 3. 变量和常量
    • 5、流程控制语句
      • 1. 选择语句
        • 单次判断一种选择语句
        • 单次判断两种选择语句
        • 多次判断多种选择语句
        • 多值选择【case】
      • 2. 循环语句
        • 先执行循环体后判断是否循环[loop]
        • 先循环再判断是否执行循环体[while]
        • 可预判循环次数[for]
    • 6、PL/SQL游标
      • 1. 显式游标
      • 2. 隐式游标
      • 3.for循环中使用游标【重点】
        • for循环+显式游标+select
        • for循环+隐式游标+select
    • 7、PL/SQL异常处理
      • 1. 预定义异常
      • 2. 自定义异常【重点】
        • 2.1 绑定错误编号自定义异常
        • 2.2 设置引发异常的条件,并设置异常出现的相应动作
  • 五、存储过程
    • 1、创建存储过程
    • 2、使用in参数创建存储过程
    • 3、使用out参数创建存储过程
    • 4、使用in out参数创建存储过程
    • 5、使用in参数默认值创建存储过程
    • 6、存储过程学习心得
      • 如何写好一个存储过程?
  • 六、函数
    • 1、创建函数
    • 2、调用函数
    • 3、删除函数
    • 4、使用函数的注意要点
  • 七、触发器
    • 1、创建触发器
      • 1. 语句级触发器
      • 2. 行级触发器
      • 3. 替换触发器
      • 4.用户事件触发器
    • 2、删除触发器
    • 3、触发器学习方法:
  • 八、包
    • 1、创建包规范
    • 2、创建包主体
    • 3、使用程序包
    • 4、程序包学习心得
  • 附件
    • 附件1:emp表和dept表的建表语句

一、Oracle11g介绍

本文主要讲解Oracle11g数据库的使用,学习本文可以让我们对Oracle数据库的掌握程度达到足以支撑日常使用的水平(非DBA)

1、Oracle11g安装和卸载

Oracle11g安装

Oracle下载地址
我这里使用的是:
Oracle--初学小白基础篇(第一版)_第1张图片)
下载的时候要先注册一个Oracle账号,否则是不能下载的。
下载并解压文件,在安装之前需要移动目录:
Oracle--初学小白基础篇(第一版)_第2张图片)
Oracle--初学小白基础篇(第一版)_第3张图片)
安装Oracle11g:
Oracle--初学小白基础篇(第一版)_第4张图片)
进入图形化界面配置,以下只贴截图,不做过多介绍:
Oracle--初学小白基础篇(第一版)_第5张图片)
Oracle--初学小白基础篇(第一版)_第6张图片)
Oracle--初学小白基础篇(第一版)_第7张图片)
Oracle--初学小白基础篇(第一版)_第8张图片)
Oracle--初学小白基础篇(第一版)_第9张图片)
Oracle--初学小白基础篇(第一版)_第10张图片)
Oracle--初学小白基础篇(第一版)_第11张图片)
Oracle--初学小白基础篇(第一版)_第12张图片)
Oracle--初学小白基础篇(第一版)_第13张图片)
在安装的过程中,可能会遇到防火墙警告,点击“允许访问”即可。如果遇到提醒缺少某些服务,点击“继续”即可。
Oracle--初学小白基础篇(第一版)_第14张图片)
Oracle11g的数据库配置助手(Database Configuration Assistant)会帮我们剩余的的安装。这里估计需要几分钟。
Oracle--初学小白基础篇(第一版)_第15张图片)
OEM地址:https://localhost:1158/em
Oracle--初学小白基础篇(第一版)_第16张图片)
进行口令配置:
这里有四列:用户名、是否锁定用户、新密码、确认密码
这里添加几个以后常用到的用户,并设置相应的密码。

用户名 密码
scott tiger
hr hr
sh sh

点击确定回到安装界面,点击“关闭”即可。
Oracle--初学小白基础篇(第一版)_第17张图片

Oracle11g卸载

1、使用Oracle Universal Installer管理工具卸载

该方法是图形化界面卸载方式,打开后会要求使用deinstall.bat批处理文件卸载,故直接使用第二种方法即可。

2、使用deinstall.bat批处理文件卸载

①停止Oracle相关服务
Oracle--初学小白基础篇(第一版)_第18张图片
②双击deinstall.bat批处理文件
路径:E:\app\Administrator\product\11.2.0\dbhome_1\deinstallOracle--初学小白基础篇(第一版)_第19张图片在这里插入图片描述
等待一会,会出现以下界面:
Oracle--初学小白基础篇(第一版)_第20张图片
在输入两次Enter和一次y之后进入等待,估计需要几分钟。删除完终端会自动关闭。
③以管理员身份删除目录app,正常情况下是可以直接删除的。
在这里插入图片描述
④最后找到以下文件,并删除。如果没有删除在重新安装Oracle11g的时候会报错。如下:
Oracle--初学小白基础篇(第一版)_第21张图片
Oracle--初学小白基础篇(第一版)_第22张图片
至此,Oracle11g数据库已经完全删除了。

2、Oracle管理工具

①SQL*PLUS

在cmd中输入以下命令登录到Oracle11g数据库
登录格式SQLPLUS username [/password][@database_name][as sysdba | sysoper]
几点操作介绍:
Win+R输入cmd,进入终端界面。机械键盘的话可能是Alt+R。输入“sqlplus”,进入sqlplus操作界面。
Oracle--初学小白基础篇(第一版)_第23张图片
②登录system用户:
Oracle--初学小白基础篇(第一版)_第24张图片
③连接scott用户
Oracle--初学小白基础篇(第一版)_第25张图片
无用户,以sysdba的身份登录
Oracle--初学小白基础篇(第一版)_第26张图片
这款数据库管理工具虽然是Oracle自带的,但是由于界面的不友好,但是在初学数据库时还是不建议频繁使用的。个人认为,这款工具最大的优点在于刚安装Oracle数据库时,可以检验Oracle数据库是否安装成功。

②PLSQL Developer

这是一款非常好用的图形化Oracle客户端,安装简单,使用起来方便快捷,安装包百度搜一大把,不收费即可正常使用。安装好就可以看到如下图标:
Oracle--初学小白基础篇(第一版)_第27张图片
双击上述图标,会显示如下界面:
Oracle--初学小白基础篇(第一版)_第28张图片
在第一次登录时,点击“取消”,进入以下界面:
Oracle--初学小白基础篇(第一版)_第29张图片
为了能够正常且方便的使用,还需进行以下设置:
Oracle--初学小白基础篇(第一版)_第30张图片
设置字体大小:
Oracle--初学小白基础篇(第一版)_第31张图片
点击“应用”-“确定”,关闭Oracle客户端再重新打开即可正常使用。如以scott用户登录Oracle数据库:
Oracle--初学小白基础篇(第一版)_第32张图片
Oracle--初学小白基础篇(第一版)_第33张图片
这个客户端会在之后的介绍中频繁使用到,打算继续了解Oracle数据库的同学可以先行安装好该客户端。

③OEM(Oracle企业管理器)

OEM地址:https://localhost:1158/em
如果在第一次打开OEM时,很不幸,遇到以下情况:
Oracle--初学小白基础篇(第一版)_第34张图片
赶紧切换谷歌浏览器试试:
Oracle--初学小白基础篇(第一版)_第35张图片
看了看到是可以正常打开的。IE11打不开估计是拦截了。
登录system用户试试:
Oracle--初学小白基础篇(第一版)_第36张图片
在这个界面有Oracle11g数据库所有的信息。
综上,在学习SQL语法和Oracle语法相关知识建议使用PLSQL Developer,在学习DBA相关知识理论时要PLSQL Developer+OEM相结合使用。

二、Oracle11g体系结构

1、Oracle为什么要有一个复杂的体系结构?

内存少而珍贵
②能够高效的管理系统资源。(内存和外存)
③内存访问速度是电子速度,而外存访问速度取决于机械速度。
④如果数据库的所有数据操作在内存上完成,效率是非常高的,但是内存上的数据会因为系统故障、断电等故障丢失,故需要把内存上的所有的数据改动及时写入外存上。
⑤为了高效的使用内存,并且保证不会丢失数据库中的数据。

2、Oracle数据库管理系统的体系结构

Oracle--初学小白基础篇(第一版)_第37张图片

1. Oracle服务器

一台主机上可装有多台Oracle服务器【逻辑概念】,但建议只装一台。
Oracle服务器 = Oracle实例(instance)+Oracle数据库(database)
Oracle服务器的安装方式:
①基于主机安装方式: 日常练习的安装方式,把Oracle服务器和客户端都装在本机上。
②客户端-服务器方式: 一台主机装Oracle服务器,另一台主机装客户端。
③客户端-应用服务器-服务器方式: 生产环境用。即用户登录堡垒机[应用服务器],通过堡垒机访问到真正的数据库。

2. Oracle实例

数据库实例是一种访问数据库的方式。如下图:
Oracle--初学小白基础篇(第一版)_第38张图片
这个小黄人就是一个Oracle实例。当启动一个实例,即分配了SGA5大必需进程。Oracle实例和Oracle数据库是多对一的关系,即一个数据库可被多个实例访问,但是一个实例仅能访问一个数据库。

3. Oracle数据库

Oracle数据库才是真正存放数据的地方。即外存结构或者物理结构。主要有三种类型的文件组成:
①控制文件(control files):存放着维护数据库一致性的数据。
②重做日志文件(redo log files):存放历史数据。
③数据文件(data files):存放数据。

除了以上三种文件外,Oracle体系结构还包含了不属于Oracle数据库的一些文件:
初始化参数文件(parameter files):用来初始化实例。
密码文件(password files):用来存放启动和关闭数据库实例的密码。
归档重做日志文件(archived redo log files):重做日志文件的脱机备份。

4. 连接Oracle实例

前文说到,Oracle实例是一种访问数据库的方式。那么用户是如何操作数据库的呢?
Oracle--初学小白基础篇(第一版)_第39张图片
连接,可以看作是用户登录了数据库,建立了用户进程与Oracle服务器之间的通信通道。连接有如下方式:
①专用服务器连接
②共享服务器连接、多线程(MTS)连接

而专用服务器连接有以下方式:
①基于主机方式: 本机登录
②客户端-服务器方式: 在另一台主机上登录
③客户端-应用服务器-服务器方式: 先登录到应用服务器,再通过应用服务器登录到数据库服务器。

5. 服务器进程

前文说到,当用户进程接收到用户发起的请求,会自动创建一个服务器进程,那么服务器进程到底是什么呢?
Oracle创建一个服务器进程,就会分配给该服务器进程一个私有的内存区,即程序全局区(PGA)。创建服务器进程即分配PGA,终止服务器进程即回收PGA。
Oracle--初学小白基础篇(第一版)_第40张图片

6. 后台进程

以上说的用户进程服务器进程都是前台进程。而Oracle有5个必需的后台进程。即
①LGWR(重做日志写进程)
②DBWR(数据库写进程)
③SMON(系统监督进程)
④PMON(程序监督进程)
⑤CKPT(检查点进程)

还有一个非必需的后台进程,即ARCH(归档重做日志写进程)

6.1 LGWR

作用:将重做日志缓冲区中更改的记录按顺序写入重做日志文件中,减少I/O量
什么时候写?以下情形发生时,将重做日志缓冲区的数据写入到重做日志文件中:
①3s
②超1/3
③commit
④DBWR前

Oracle--初学小白基础篇(第一版)_第41张图片
Oracle--初学小白基础篇(第一版)_第42张图片

6.2 DBWR

作用:将数据库高速缓冲区中修改的数据按顺序写入到数据文件中,减少I/O次数
什么时候写?以下情形发生时将数据库高速缓冲区的数据写入数据文件:
①限额、限时
②满了就写
③检查点发生
④表空间read only、offline、normal
⑤联机备份

6.3 SMON

作用:当Oracle系统崩溃时,SMON在数据库重启时,自动执行Oracle实例的恢复工作。
执行步骤如下:
①前滚:执行所有写入redo log files文件,但是未写入data files文件的操作
②打开数据库
③回滚:将所有未提交的事务撤回

6.4 PMON

作用:在某个进程崩溃后,恢复数据库。
执行步骤如下:
①回滚用户当前的事务
②释放用户所加的所有表一级和行一级的锁
③释放用户所有其他资源

6.5 CKPT

作用:提高系统效率,并保证数据库的一致性。
检查点:当DBWR进程把SGA中所有已经改变了的数据库高速缓冲区中的数据写入到数据文件中时,会产生一个检查点,在这一时刻,数据库是一致的。相当于此时对数据库创建了一个快照。
如上所说,产生检查点相当于创建一个快照。那么就会产生大量的I/O操作,对数据库的正常的联机操作会造成一定的影响。
当产生了检查点,检查点之前的重做日志文件是可以去除的,要恢复数据库,只需要检查点和检查点之后的重做日志即可。

6.6 ARCH

作用:当Oracle系统崩溃时,为了做完全恢复,就必须开启归档模式,生成归档重做日志文件。

对于重做日志缓冲区中已改变的数据,LGWR进程将其按顺序写入到重做日志文件中。而重做日志文件是按照组划分的,当一个重做日志文件组被写满后,会写下一个重做日志文件组,这个动作称之为“日志切换”。又因为重做日志文件组的组数是有限的,随着日志的不断切换,总会有写满的时候,当全部写满了,就会再次从第一个重做日志文件组开始写起,这个动作称之为“日志覆盖”。当数据文件丢失时,需要重做日志文件进行恢复,但是重做日志文件被覆盖了,就会导致被覆盖的内容无法被恢复。此时利用重做日志文件进行恢复被称为"不完全恢复"。

Oracle数据库运行时,存在两种模式,即归档模式和非归档模式。当数据库处于归档模式,会生成一个归档重做日志文件,该文件会对满了的重做日志文件进行备份,然后脱机保存。只有被备份了的重做日志文件组才允许被覆盖。否则必须等待直至备份完成。要达到完全恢复,则必须要归档重做日志文件+重做日志文件才能恢复。

Oracle--初学小白基础篇(第一版)_第43张图片

三、SQL基础

1、简单查询

查询格式:

select [distinct|all] col_name|*
from tables|view|other select
[where子句]
[group by子句]
[having子句]
[order by子句]

查询类型:
①查询所有列

-- 列出emp表的所有的数据
select * from emp;

②查询指定列

-- 列出emp表的empno,ename,deptno列的数据
select empno,ename,deptno from emp;

③列别名

-- 为emp表的empno,ename,deptno列取别名
select empno 员工编号,ename 员工姓名,deptno 部门编号 from emp;

④列带表达式

-- emp表的sal列为原来的1.2倍
select empno 员工编号,ename 员工姓名,deptno 部门编号,sal*1.2 员工工资 from emp;

⑤列去重

--按行(所有列都相同)去重
select distinct(deptno) from emp;
--当某两行的job和deptno同时相同时,只保留一行
--以下在多列使用distinct的结果是一样的
select distinct job,deptno from emp;
select distinct(job),deptno from emp;

2、where条件过滤

where条件过滤类型:
①比较过滤
作用:取数值在某个范围的行。

比较运算符: 大于(>)、小于(<)、大于等于(>=)、小于等于(<=)、不等于(!= 或 <>)、等于(=)

--查询工资大于1000的员工信息
select * from emp where sal>1000;

②like模糊过滤
作用:查询包含或者不包含某个字符或字符串的行。
通配符:

  • 单个字符:_
  • 任意多个字符:%
-- 匹配员工岗位带有MAN的员工信息
select * from emp where job like'%MAN%';
-- 匹配员工岗位以SAL开头的员工信息
select * from emp where job like'SAL%';
-- 匹配员工姓名的倒数第二个字符是T的员工信息
select * from emp where ename like'%T_';

③in过滤
作用:查询某列(某些列)的值与in列表中的值相等的行。

-- 查询员工岗位为CLERK或者MANAGER的员工信息
-- 可以多列匹配,in内可以是子查询【后面会介绍子查询】
select * from emp where job in('CLERK','MANAGER');

④between A and B 两数之间
作用:查询某列值在A和B之间的行,包括A和B。

-- 查询员工工资在1000和3000之间的员工信息
select * from emp where sal between 1000 and 3000;

⑤空值查询
作用:查询某列的值为空的行。

-- 查询无奖金员工的相关信息
select * from emp where comm is null;

⑥逻辑过滤[多条件组合过滤]
作用:筛选出满足多个过滤条件的行。
如果在where子句中含有多个过滤条件,在各条件的逻辑关系不清晰时,可以使用圆括号()进行条件划分。

逻辑与:AND
--查询部门编号为20且员工工资高于1000的员工信息
select * from emp where sal>1000 and deptno=20;

逻辑或:OR
--查询员工工资高于2000或者没有奖金的员工信息
select * from emp where sal>2000 or comm is null;

逻辑非:NOT
-- 查询有奖金的员工信息
select * from emp where comm is not null;
-- 查询工资小于1000或者大于3000的员工信息
select * from emp where sal not between 1000 and 3000;
-- 查询员工岗位不包含MAN的员工信息
select * from emp where job not like'%MAN%';
-- 查询员工岗位不是CLERK和SALESMAN的员工信息
select * from emp where job not in('CLERK','SALESMAN');

3、group by子句分组查询

作用:按某列的值域(值的集合)分组。
当SQL语句中含有group by子句时,所选择列必须是分组后的结果(求和[sum]求平均[avg]计数[count]),否则会报错。【先分组再聚合】

-- 求每个部门的人数、总工资、平均工资
select 
deptno 部门编号,
count(*) 部门人数,
sum(sal) 部门总工资,
avg(sal) 部门平均工资 
from emp group by deptno;

4、having子句分组过滤

作用:在分组查询后,进行二次过滤。在group by子句后使用。

-- 求部门编号为10、20的人数、总工资、平均工资
select 
deptno 部门编号,
count(*) 部门人数,
sum(sal) 部门总工资,
avg(sal) 部门平均工资 
from emp group by deptno
having deptno in(10,20);

5、order by排序

作用:对所得查询结果按照某列(某些列)进行排序,一般放到最后使用。【asc 升序[默认]】【desc 降序】

-- 查询有奖金的员工的员工信息,并按照工资从低往高进行排序
select * from emp where comm is not null 
order by sal;

6、多表关联查询

多表关联查询

7、Oracle常用函数

这里仅列出函数及其作用,具体自行查找资料并测试。

1. 字符类函数

  • ASCII(‘a’):字符转ASCII
  • CHR(90):ASCII转字符
  • CONCAT(s1,s2):字符串从左到右连接
  • INITCAP(str):字符串各单词首字母大写
  • INSTR(s1,s2,i,n):查询从s1的第i个字符开始,s2在s1中出现第n次的位置
  • LENGTH(str):计算字符串的长度
  • LOWER(str):字符串小写形式
  • UPPER(str):字符串大写形式
  • LTRIM(s1,s2):从s1中删除左边的s2
  • RTRIM(s1,s2):从s1中删除右边的s2
  • TRIM(s1,s2):从s1中删除两边的s2
  • REPLACE(s1,s2,s3):把s1中的s2替换成s3
  • SUBSTR(str,m,n):截取str的第m个字符开始的长度为n的字符串

2. 数字类函数

  • ABS(n);返回n的绝对值
  • CEIL(n):返回大于或等于n的最小整数【最小取整】
  • FLOOR(n):返回小于或等于n的最大整数【最大取整】
  • COS(n):返回n的余弦值
  • SIN(n):返回n的正弦值
  • SIGN(n):符号函数,f(a>0)=1,f(a<0)=-1,f(a=0)=0
  • EXP(n):返回e的n次幂
  • LOG(n1,n2):求以n1为底n2的对数
  • MOD(n1,n2):返回n1除以n2的余数【取模】
  • POWER(n1,n2):返回n1的n2次方
  • SQRT(n):返回n的平方根
  • ROUND(n1,n2):对n1保留n2位小数,默认是0位。n2如果为负数,则精确到小数点左边第n2位。【四舍五入】
  • TRUNC(n1,n2):对n1保留n2位小数,默认是0位。n2如果为负数,则精确到小数点左边第n2位。【直接截断】
    Oracle--初学小白基础篇(第一版)_第44张图片
    Oracle--初学小白基础篇(第一版)_第45张图片

3. 时间函数

  • SYSDATE:返回当前时间
  • TO_DATE('2020-05-09 21:40:23','YYYY-mm-dd hh24:mi:ss'):字符串转日期
  • TO_CHAR(sysdate,'YYYY-mm-dd hh24:mi:ss'):日期转字符串
  • ADD_MONTHS(d,i):返回时间d+i月的时间
  • LAST_DAY(d):返回当月的最后一天
  • NEW_TIME(d,s1,s2):返回在时区s1下的日期d,在时区s2的日期
  • MONTHS_BETWEEN(d1,d2):返回d1-d2的月份差
  • 日期格式'YYYY-mm-dd hh24:mi:ss'
    Oracle--初学小白基础篇(第一版)_第46张图片

4. 聚合类函数

聚合类函数是经常使用的函数,要理解“聚合”的含义。

  • MAX(x):求最大值
  • MIN(x):求最小值
  • AVG(x):求平均值,不计入空值。
  • SUM(x):求和【sum=avg*count
  • COUNT(x):计数,不计入空值。
  • VARIANCE(x):求方差
  • STDDEV(x):求标准差
select
max(sal) 最大值,
min(sal) 最小值,
avg(sal) 平均值,
sum(sal) 求和,
count(sal) 计数,
variance(sal) 方差,
stddev(sal) 标准差
from emp;

8、子查询的使用

子查询是在select语句中的另一个select语句。也可以称之为嵌套查询。

-- 常用于from子句作为数据源
-- 查询部门名称为ACCOUNTING的员工信息
select * from emp where deptno=(
select deptno from dept where dname='ACCOUNTING');
--内查询(子查询)
select deptno from dept where dname='ACCOUNTING'
-- 外查询
select * from emp where deptno=(select 语句);

注意:
①子查询要用括号()括起来
②不能在order by子句使用子查询
③不允许超过255层嵌套子查询
④子查询可用在select子句from子句where子句

1. 单行子查询

子查询的结果仅有一行记录。

单行比较运算符:大于(>)、小于(<)、大于等于(>=)、小于等于(<=)、不等于(!= 或 <>)、等于(=)

-- 查询部门名称为ACCOUNTING的员工信息
select * from emp where deptno=(
select deptno from dept where dname='ACCOUNTING');
-- 子查询结果只有一行记录
select deptno from dept where dname='ACCOUNTING'

2. 多行子查询

子查询的结果有多行记录

多行比较运算符:在里面(in) 、所有的(all) 、 任意一个(any)

-- 查询部门位置在DALLAS或者BOSTON的员工信息
select * from emp where deptno in(
select deptno from dept where loc in('DALLAS','BOSTON'));

-- 查询部门编号不是20,但是工资高于部门编号是20的所有员工的信息
select * from emp where deptno<>20 and sal > all(
select sal from emp where deptno=20);

-- 查询部门编号不是10,但是工资小于任意一个部门编号为10的员工工资的员工信息
select * from emp where deptno<>10 and sal < any(
select sal from emp where deptno=10);

在错误使用多行子查询时,会得到以下报错:

错误用法:
select * from emp where deptno<>20 and sal >(
select sal from emp where deptno=20);
多行子查询
select sal from emp where deptno=20

Oracle--初学小白基础篇(第一版)_第47张图片

3. 关联子查询(重点)

单行子查询和多行子查询一般用于where子句,而关联子查询一般用于select子句中。

-- 查询所有员工的信息,包括部门名称等信息
select 
b.ename 员工姓名,
b.empno 员工编号,
b.deptno 部门编号,
(select a.dname from dept a where a.deptno=b.deptno) 部门名称
from emp b;

关联子查询常用于字典值所在字段。
如:某列的值是一些编号,不具有很好的解读性,而在另一张表(字典表)中存在这些编号与真实含义的映射,这时使用关联子查询可以很方便的将该字段的真实含义表达出来。可能会有人想问,这个我用表连接也可以完成,为什么还要子查询呢?
试想一下,这里有100个字段都要映射成真实的值,这时难道关联100张表?关联子查询就在此时发挥出巨大作用。

9、DML语句:增删改

在本文附件中含有emp表和dept表的创建语句,所以在这里放心大胆的操作emp表和dept表。【在生产环境中请谨慎操作,三思而后行】
DML,也叫数据操作语言。而DML操作即是指INSERT|DELETE| UPDATE
DML操作会留有余地,不会自动提交事务,但是当点击了“提交(commit)”,那就表示该事务已经提交了,不能再“回滚(rollback)”了。【有关事务的概念可看第10点

1. INSERT

插入格式
insert into table_name(col1,col2,...) values(val1,val2,...); 【按字段插入,单行插入】
insert into table_name values(val1,val2,...,valn); 【按行插入,单行插入】
insert into table_name select 语句 【按查询结果插入,批量插入】

在对一个表进行insert语句之前,要查看该表的表结构,看看各字段的数据类型及其数据约束。
Oracle--初学小白基础篇(第一版)_第48张图片
Oracle--初学小白基础篇(第一版)_第49张图片
在了解了表结构后,就可以着手书写insert语句的代码了。
首先将emp表和dept表删除,创建dept表和emp表。

DROP TABLE EMP;
DROP TABLE DEPT;
CREATE TABLE DEPT
    (DEPTNO NUMBER(2) CONSTRAINT PK_DEPT PRIMARY KEY,
	DNAME VARCHAR2(14),
	LOC VARCHAR2(13));
CREATE TABLE EMP
       (EMPNO NUMBER(4) CONSTRAINT PK_EMP PRIMARY KEY,
	ENAME VARCHAR2(10),
	JOB VARCHAR2(9),
	MGR NUMBER(4),
	HIREDATE DATE,
	SAL NUMBER(7,2),
	COMM NUMBER(7,2),
	DEPTNO NUMBER(2) CONSTRAINT FK_DEPTNO REFERENCES DEPT);

这时查询表,可以看到表是空的,只有一个表框架。由于createdrop操作都是DDL语言,执行后自动提交事务,故需在生产环境中应谨慎操作,不太理解作用的最好就不要操作。
在这里,先创建一个dept_old表作为dept的前身,之后要用这个dept_old表来批量插入生成dept表。

--创建dept_old表
CREATE TABLE DEPT_OLD
(DEPTNO NUMBER(2) CONSTRAINT PK_DEPT_1 PRIMARY KEY,
DNAME VARCHAR2(14),
LOC VARCHAR2(13));

INSERT INTO DEPT_OLD VALUES(10,'ACCOUNTING','NEW YORK');
INSERT INTO DEPT_OLD VALUES (20,'RESEARCH','DALLAS');
INSERT INTO DEPT_OLD VALUES(30,'SALES','CHICAGO');
INSERT INTO DEPT_OLD VALUES(40,'OPERATIONS','BOSTON');

Oracle--初学小白基础篇(第一版)_第50张图片

-- 按查询结果插入【批量插入】
insert into dept
select * from dept_old;
-- 按字段插入
INSERT INTO EMP(empno,ename,job,hiredate,deptno) VALUES (7369,'SMITH','CLERK',sysdate,20);
-- 按行插入
INSERT INTO EMP VALUES(7499,'ALLEN','SALESMAN',7698,sysdate,1600,300,30);

2. DELETE

删除格式delete from table_name where 过滤条件

-- 删除部门编号为10的员工信息
delete from emp where deptno=10;

很多人喜欢一上来就delete,commit,这样很可能因为书写代码错误造成数据库以及系统故障。在执行delete操作前,最后先执行select语句,看看要删除的内容正不正确。

先查
select * rom emp where deptno=10;
后删
delete from emp where deptno=10;

3. UPDATE

更新格式update table_name set col=value1,col2=value2,... coln=valuen where 过滤条件
重置emp表和dept表。

-- 如更新部门编号为10的员工,工资上涨20%
先查
select * from emp where deptno=10;
再改
update emp set sal=sal*1.2 where deptno=10;


在这里插入图片描述

10、事务处理

1. 事务处理的结果

①事务的所有数据库语句都执行成功,全部提交。【commit
②事务的数据库语句存在报错,全部回滚。【rollback

2. 事务的4大特性(ACID)

每个事务都必须满足这四个特性。
①原子性:事务可以看作数据库语句的集合。一个事务对数据库的所有操作要么全部执行提交,要么全部回滚。
②一致性:数据库在事务开始之前或者事务完成之后都处于一致性状态。一致性状态是一种美好的状态。若事务执行成功,则数据库从一种一致性状态到另一种一致性状态,若事务执行失败,则数据库返回原来的一致性状态。
③隔离性:事务之间是相互独立的。事务查看数据时,该数据要么是其他并发事务执行前的状态,要么是执行后的状态。绝对不会是其他并发事务在执行时的状态。
④持久性:当事务执行完毕,对数据库所做的修改是永久的。

3. 事务的开始和结束

事务的开始

Oracle11g的事务是隐式自动开始,也就是不用用户执行开始事务语句。

事务的结束

以下一种情况一般认为事务结束了。
①执行commit语句提交事务
②执行rollback语句撤销事务
③执行DDL语句(create\alter\drop)
④执行DCL语句(grant\revoke)
⑤正常断开数据库连接、退出SQL*plus环境,系统将自动执行commit,否则执行rollback
其实说白了,有commit或者rollback就算结束一个事务。
注意:DML语句(insert\update\delete)只开启一个事务,不结束一个事务,需手动提交或者撤销。

4. 提交事务和撤销事务的Oracle系统流程

提交事务

①在回滚段【存放着在回滚时用的数据,也就是存放旧数据的文件】中记录当前事务已提交,并给一个系统编号(SCN)
②启动日志写入进程(LGWR),将SGA的重做日志缓冲区中的数据和**当前事务的SCN写入到 重做日志文件中【这时写入的数据才是永久的】
③告诉用户事务已提交。

撤销事务

①使用回滚段中的数据恢复对数据库做的修改
②Oracle后台服务进程释放事务所使用的系统资源
③告诉用户事务已回滚。

5、设置保存点并回滚【分段回滚】

--第一次DML 操作
savepoint a;
-- 第二次DML操作
rollback to a;
--此时,回滚的只是第二次DML操作的内容,而第一次DML操作的内容仍然保留。

四、PL/SQL基础

PL/SQL(Procedural Language/SQL)即过程化程序语言。是ORACLE提供的一种专用的编程语言,可以用来编写包含SQL语句的程序。

1、PL/SQL块结构

[declare]
  -- 声明部分,可选【声明变量、常量、游标】
begin
  -- 主体部分,必选
[exception]
   -- 异常处理部分,可选
end;

注意:
①声明部分仅当前PL/SQL块有效。
②PL/SQL块的结束要么执行成功以end结束,要么执行失败,转而执行异常处理部分并结束。
③PL/SQL每条语句都必须以分号;结束。

2、注释

①单行注释:--
②多行注释:/* */

3、PL/SQL字符集【允许出现的字符】

①大小写字母:a~z A~Z
②数字:0~9
③隐式字符:制表符tab、空格、回车
④数学符号:+ - * / < > =
⑤间隔符:() {} [] ? ! ; : @ # % $ &
注意:字符串格式化日期区分大小写。

4、数据类型及变量常量

1. 基本数据类型

  • 数值类型
    number:存整数或者浮点数
    number(m,n):保留m位有效数字,并且小数点右边有n位。【m为精度,n为刻度范围】
    pls_integer:存整数
    binary_integer:存整数
    ⑤等价number类型:double integer int smallint binary_integer pls_integer
  • 字符类型
    varchar2(n):存最大长度为n的可变长度的字符串。n最大值为32767,没有默认值,必须给出n。【变长存储】
    char(n):存长度为n的字符串。n最大值为32767,默认值1。【定长存储】
    long:可变字符串,最大长度为32767。数据库类型的long变量最大长度为2GB。
  • 日期类型
    date:日期类型。7字节存储:世纪、年、月、日、时、分、秒
  • 布尔类型
    booleanfalse true null

注意:【小存进大】
①数据库类型的char最大长度为2000,而PL/SQL的char类型是大于2000的,故PL/SQL的char类型变量不能赋值给数据库类型的char类型。
②数据库类型的long最大长度为2GB,而PL/SQL的long类型最大长度是32767,故PL/SQL的long类型变量可以赋值给数据库类型的long类型。

2. 特殊数据类型

  • col_name%type:列数据类型
  • record:多列数据类型
    ①定义record数据类型:
    type type_name is record(基本数据类型,逗号分隔);
    ②定义该类型的变量:
    val_name type_name;
  • table_name%rowtype:行数据类型

3. 变量和常量

变量:
变量名 数据类型 [:= 初始值];
常量:
变量名 constant 数据类型 [:= 初始值];

5、流程控制语句

1. 选择语句

单次判断一种选择语句

-- 模板
set serveroutput on
begin
  if 条件 then
    sql_sentence;
  end if;
end;
/

-- 示例
set serveroutput on
begin
  if length('ab')>length('a') then
    dbms_output.put_line('ab');
  end if;
end;
/

单次判断两种选择语句

-- 模板
set serveroutput on
begin
  if 条件 then
    sql_sentence_1;
  else
    sql_sentenct_2;
  end if;
end;
/

-- 示例
set serveroutput on
begin
  if length('ab')>length('a') then
    dbms_output.put_line('ab');
  else
    dbms_output.put_line('a');
  end if;
end;
/

多次判断多种选择语句

-- 模板
set serveroutput on
begin
  if 条件1 then
    sql_sentence_1;
  elsif 条件2 then
    sql_sentenct_2;
  elsif 条件3 then
    sql_sentenct_3;
    ......
  else
    sql_sentenct_n;
  end if;
end;
/

-- 示例
set serveroutput on
declare
 a number := 1;
 b number := 2;
begin
  if a>b then
    dbms_output.put_line('a大于b');
  elsif a<b then
    dbms_output.put_line('a小于b');
  else
    dbms_output.put_line('a等于b');
  end if;
end;
/

多值选择【case】

-- 模板
set serveroutput on
begin
  case var_name
    when1 then 
      sql_sentence_1;
    when2 then 
      sql_sentence_2;
      ......
    when 值n then 
      sql_sentence_n;
    [else sql_sentence;]
   end case;
end;
/

-- 示例
set serveroutput on
declare
 d number;
 s number;
begin
  select to_number(to_char(sysdate,'mm')) into d from dual;
  if d between 1 and 3 then
    s := 1;
  elsif d between 4 and 6 then
    s := 2;
  elsif d between 7 and 9 then
    s := 3;
  else
    s:= 4;
  end if;
  case s
    when 1 then dbms_output.put_line('现在是春天');
    when 2 then dbms_output.put_line('现在是夏天');
    when 3 then dbms_output.put_line('现在是秋天');
    else dbms_output.put_line('现在是冬天');
  end case;
  dbms_output.put_line(d||'月份在第'||s||'季度。');
end;
/

2. 循环语句

先执行循环体后判断是否循环[loop]

-- 模板
set serveroutput on
begin
  loop
    sql_sentence;
    exit when 退出循环表达式;
  end loop;
end;
/

-- 示例
-- 求前100个自然数之和
set serveroutput on
declare
  i number := 0;
  s number := 0;
begin
  loop
    i := i+1;
    s := s+i;
    exit when i=100;
  end loop;
  dbms_output.put_line('前100个自然数的和为'||s);
end;
/

先循环再判断是否执行循环体[while]

-- 模板
set serveroutput on
begin
  while 继续循环表达式 loop 
    sql_sentence;
  end loop;
end;
/

-- 示例
-- 求前100个自然数之和
set serveroutput on
declare
  i number := 0;
  s number := 0;
begin
  while i<100 loop
    i := i+1;
    s := s+i;
  end loop;
  dbms_output.put_line('前100个自然数的和为'||s);
end;
/

可预判循环次数[for]

-- 模板
set serveroutput on
begin
  for i in min..max loop
    sql_sentence;
  end loop;
end;
/

-- 示例
-- 求前100个自然数之和
set serveroutput on
declare
  s number := 0;
begin
  for i in 1..100 loop
    s := s+i;
  end loop;
  dbms_output.put_line('前100个自然数的和为'||s);
end;
/

6、PL/SQL游标

游标可以针对查询结果,检索每一行数据并进行操作。游标主要用在服务器上,处理由客户端发送给服务器的SQL语句。Oracle中游标分为显示游标隐式游标

1. 显式游标

显式游标的处理过程:
Oracle--初学小白基础篇(第一版)_第51张图片
①声明游标

cursor cur_name[(input_par [in] data_type := param_value)]
[return return_type]
is select_sentence;

②打开游标

open cur_name[(param_value)]

③读取游标

-- 游标读取select语句的第一行数据,列数要能与变量对应的列数匹配上。
fetch cur_name into  变量;

游标中有一个指针, 指向当前数据行。对于刚打开的游标,指针一般指向查询结果的第一行,当读取完毕后,游标中的指针才会读取下一行数据。当读取完到查询结果的最后一行数据后,游标会指向下一行,这是游标的指针指向空行,即没有发现数据行。
④关闭游标

close cur_name;

显式游标的属性:
cursor_name%found:fetch读取数据后,发现数据返回true,否则返回false。
cursor_name%notfound:fetch读取数据后,发现数据返回false,否则返回true。
cursor_name%rowcount:返回select语句的行数
cursor_name%isopen:判断游标是否打开,打开返回true,关闭返回false。
显式游标的使用:

set serveroutput on
declare
  -- 声明record类型
  type re_ename_sal is record(
    var_ename emp.ename%type,
    var_sal emp.sal%type
  );
  -- 声明record变量
  ename_sal re_ename_sal;
  -- 声明游标
  cursor cur_es(var_deptno in number := 10) 
  is select ename,sal from emp where deptno=var_deptno;
begin
  open cur_es(20);  --打开游标
  fetch cur_es into ename_sal;  --读取第一行数据
  while cur_es%found loop
    dbms_output.put_line('员工'||ename_sal.var_ename||'的工资为'||ename_sal.var_sal);
    fetch cur_es into ename_sal;  --读取下一行数据
  end loop;
  close cur_es;  -- 关闭游标
end;
/

2. 隐式游标

隐式游标不用声明、不用打开、不用读取、也不用关闭。这些操作都由Oracle自行完成,我们只需要使用好隐式游标的属性即可。常用于updatedelete语句。【隐式游标的初始状态类似显式游标第一次fetch之后的结果】
隐式游标的属性:
sql%found:fetch读取数据后,发现数据返回true,否则返回false。
sql%notfound:fetch读取数据后,发现数据返回false,否则返回true。
sql%rowcount:返回select语句的行数
sql%isopen:判断游标是否打开,打开返回true,关闭返回false。

set serveroutput on
begin
  -- update语句自动生成一个隐式游标
  update emp set sal=sal*1.5 where deptno=10;
  if sql%notfound then
    dbms_output.put_line('无调薪的员工!');
   else
     dbms_output.put_line('有'||sql%rowcount||'个员工需要调薪!');
   end if;
end;
/

3.for循环中使用游标【重点】

对于for循环里的i,可以看作是游标的指针所指的行。随着i的变化,游标的指针会自动移到下一行,无需人为的fetch。显式游标可以声明,但不许打开、读取、关闭,这些由Oracle自动完成。

for循环+显式游标+select

set serveroutput on
declare
  -- 声明游标
  cursor cur_es(var_deptno in number := 10)
  is select ename,empno from emp where deptno=var_deptno;
begin
  for i in cur_es loop
    dbms_output.put_line('员工'||i.ename||'的编号是'||i.empno);
  end loop;
end;
/

for循环+隐式游标+select

-- in里的select语句自动生成一个隐式游标
set serveroutput on
begin
  for i in (select ename,empno from emp where deptno=10) loop
    dbms_output.put_line('员工'||i.ename||'的编号是'||i.empno);
  end loop;
end;
/

7、PL/SQL异常处理

用于PL/SQL块的exception部分。

exception
  when 异常1 then 
    处理方法1;
  when 异常2 then
    处理方法2;

1. 预定义异常

Oracle--初学小白基础篇(第一版)_第52张图片

2. 自定义异常【重点】

2.1 绑定错误编号自定义异常

错误异常编号:Oracle系统发生错误时,会显示错误编号和相关描述信息。
如果不好理解,可以将该错误编号与自定义的名称绑定在一起,再次发生该错误时,可以更好的发现问题并解决问题。
举例:
当插入一条语句,出现异常,该异常说的是违反了外键约束,deptno的字段值50不在外键列所在的表中。
Oracle--初学小白基础篇(第一版)_第53张图片

set serveroutput on
declare
  FK_battle exception;   -- 定义异常变量
  pragma exception_init(FK_battle,-02291);   -- 绑定错误编号
begin
  insert into emp(empno,ename,deptno) values('1000','ABC',50);
exception 
  when FK_battle then 
    dbms_output.put_line('该插入违反了外键约束,请修改外键列的值。');
end;
/

Oracle--初学小白基础篇(第一版)_第54张图片

2.2 设置引发异常的条件,并设置异常出现的相应动作

需要条件:
①声明异常变量
②设置引发异常的条件
③设置异常发生时的动作

set serveroutput on
declare
  FK_battle exception;    -- 声明异常变量
  empno_row emp.empno%type;
  ename_row emp.ename%type;
  deptno_row emp.deptno%type;
begin
  empno_row := '1000';
  ename_row := 'ABC';
  insert into emp(empno,ename,deptno) values(empno_row,ename_row,deptno_row);
  if deptno_row is null then   -- 引发异常的条件
    raise FK_battle;
  end if;
exception 
  when FK_battle then 
    dbms_output.put_line('请输入部门的值!');     -- 异常发生时的动作
end;
/

Oracle--初学小白基础篇(第一版)_第55张图片

五、存储过程

存储过程是一个命名的PL/SQL程序块。存储过程在创建的时候已经编译,在执行和调用的时候效率极高。
1、参数:
in输入参数[默认参数模式][提供数据]
out输出参数[输出数据]
in out输入输出参数[类似递归公式,即提供数据也输出数据]
2、返回值:无返回值
【有返回值:f(x)输入x返回y,y作为返回值】
【无返回值:f(x,y)输入x,得到y,y仅仅只是其中的一个参数】
3、执行方式:
exec[ute]命令执行【SQL命令窗口执行】
PL/SQL调用
4、查看存储过程
①查看存储过程是否存在

select * from all_procedures where object_name='PRO_NAME';

②查看存储过程的源代码

select text from user_source where type='PROCEDURE' and name='PRO_NAME';

1、创建存储过程

-- 存储过程的创建格式
-- data_type:in参数和out参数不能指定数据类型的长度。如varchar2正确,varchar2(10)则错误。
-- inner_par:内部参数只在该存储过程中有效,定义时以分号( ; )结束。
create or replace procedure pro_name(
par1_name in_out_type data_type,
par2_name in_out_type data_type,
...) as | is
inner_par1 inner_par_data1;
inner_par2 inner_par_data2;
begin
  plsql_sentences;
[exception]
  [dowith_sentences;]
end [pro_name];
--常用语句:
commit;   【提交事务】
rollback;  【回滚事务】
:=   等式赋值
=> 传值赋值
set serveroutput on   -- 显示命令窗口的输出信息
dbms_output.put_line('输出一句话');  --输出信息
show error;   【查看创建存储过程失败的错误信息】
--执行和调用存储过程
exec pro_name;   【执行存储过程pro_name】
-- 调用存储过程pro_name
begin
  pro_name
end;
/

为了在之后做实验更方便,使用system用户给scott用户授予dba的权限。

-- 在system用户下执行
grant dba to scott;

Oracle--初学小白基础篇(第一版)_第56张图片

1、创建一个存储过程,向dept表插入一条记录。【看代码要从框架入手,逐条解读】
-- 编译存储过程
create or replace procedure pro_name
as
begin
  insert into dept values(50,'MARKET','BEIJING');
  commit;
  dbms_output.put_line('已插入一条记录!');
end pro_name;
/   -- 执行上一个PL/SQL块

-- 执行存储过程
exec pro_name; 

-- 调用存储过程
begin
  pro_name;
end;
/

-- 检验结果
select * from dept;

Oracle--初学小白基础篇(第一版)_第57张图片

2、使用in参数创建存储过程

in模式参数的使用:使用3in模式参数,向dept表插入一条记录。
-- 可用desc tablename; 查看表结构
create or replace procedure pro_name(
a in number,  -- 不能指定数据类型的长度
b in varchar2,
c in varchar2
) as
begin
  insert into dept values(a,b,c);
  commit;
  dbms_output.put_line('已插入一条记录!');
end pro_name;
/

exec pro_name(60,'ADMIN','SHANGHAI');

Oracle--初学小白基础篇(第一版)_第58张图片
传参方式:
名称传参,绝对不混乱:exec pro_name(a=>60,b=>'ADMIN',c=>'SHANGHAI');
顺序传参,顺序不乱则不乱:exec pro_name(60,'ADMIN','SHANGHAI');
混合传参,不建议使用。

3、使用out参数创建存储过程

out模式参数的使用:编写一个存储过程,当输入部门编号为30时,可以查询相应的部门名和部门地点。
create or replace procedure pro_name(
a in number,
b out dept.dname%type,
c out dept.loc%type
) as
begin
  select dname,loc into b,c from dept where deptno=a;
exception
  when no_data_found then
    dbms_output.put_line('部门编号不存在!');
end pro_name;
/

在调用含有out参数的存储过程时,要先声明用于存放输出参数的值。
-- 执行存储过程
set serverout on
variable var_dname varchar2(20);
variable var_loc varchar2(20);
exec pro_name(30,:var_dname,:var_loc); 

-- 调用存储过程
declare
  var_dname dept.dname%type;
  var_loc dept.loc%type;
begin
  pro_name(30,var_dname,var_loc);
  dbms_output.put_line(var_dname||'位于'||var_loc);
end;
/

4、使用in out参数创建存储过程

in out模式参数的使用:编写一个存储过程,输入一个数字n,若n是奇数,则输出n+1,若n是偶数,则输出n-1create or replace procedure pro_name(
n in out number
) as
begin
  if mod(n,2)=1 then
    n := n+1;
  else
    n := n-1;
  end if;
end;
/

-- 调用存储过程
declare
  n1 number;
  n2 number;
begin
  n1 := 100;
  n2 := n1;
  pro_name(n2);
  dbms_output.put_line('输入n1的值为'||n1||',输出为:'||n2||'。');
end;
/

5、使用in参数默认值创建存储过程

in参数默认值
在声明变量后边加【default '默认值'】
默认值何时生效:当没有对应的参数传入时则自动使用默认值。
create or replace procedure pro_name(
a in number,
b in varchar2 default '无部门',
c in varchar2 default '无地点'
) as
begin
  insert into dept values(a,b,c);
  commit;
  dbms_output.put_line('已插入一条记录!');
end pro_name;
/

-- 执行存储过程
exec pro_name(70);

Oracle--初学小白基础篇(第一版)_第59张图片

6、存储过程学习心得

如何写好一个存储过程?

首先要弄清楚以下几个问题:

  1. 需要哪些输入?
  2. 需要哪些输出?
  3. 输入、输出参数的数据类型分别是什么?是否有默认值
  4. begin ... end;/ 如何书写主体部分的逻辑实现?【PL/SQL代码块】
  5. 调用或者执行的时候是否需要先定义变量用于存放输出值
  6. 要得到的输出结果的形式是怎样的?

六、函数

函数用于计算和返回一个值,相较于存储过程,函数可以有任意多个输入参数,但有且仅有一个返回值。

1、创建函数

create or replace function fun_name(
par1 data_type,
par2 data_type,
...
) return return_data_type
is | as
[inner_par inner_par_type]
begin
  sql_sentence;
[exception]
  dowith_sentence;
end [fun_name];
/
定义一个函数,返回emp表中某个部门所有员工工资的和,并保留两位小数。
create or replace function fun_name(var_deptno number)    -- 输入参数
return number   -- 返回值类型
as
sum_a number;    --函数内部变量
begin
  select round(sum(sal),2) into sum_a from emp where deptno=var_deptno;   --调用参数的使用直接使用 参数名,而调用变量的时候要使用 :变量名
  return(sum_a);
exception
  when no_data_found then
     dbms_output.put_line('输入的部门编号不存在,请重新输入!');
     return(0);
end;
/

Oracle--初学小白基础篇(第一版)_第60张图片

2、调用函数

--调用函数
--要声明一个变量存放返回值
set serveroutput on
declare
  sum_sal number;
begin
  sum_sal := fun_name(10);    -- 函数赋值符号 :=
  dbms_output.put_line('总工资为:'||sum_sal);
end;
/

Oracle--初学小白基础篇(第一版)_第61张图片

3、删除函数

drop function fun_name;

Oracle--初学小白基础篇(第一版)_第62张图片

4、使用函数的注意要点

  1. 注意函数的书写框架
  2. 注意函数的返回值类型
  3. 注意调用函数时赋值符号:=
  4. return(返回值)

七、触发器

触发器,可以看作是一种应急响应动作。可以在数据库执行insertupdatedelete等操作时做出相应的功能响应。

  • 触发器的种类:
    ①语句级触发器:无论影响多少行,都只执行一次响应动作。
    ②行级触发器(for each row):影响多行,每一行都会执行一次响应动作。
    ③替换触发器(instead of):触发事件涉及对象为视图,而不是基本表。【替换触发器也是行级触发器
    ④用户事件触发器:指与DDL操作或者用户登录、退出数据库等事件相关的触发器。
    ⑤系统事件触发器:指在Oracle数据库系统的事件中进行触发的触发器。如Oracle实例的启动和关闭。

  • 触发器:触发器是通过触发事件来执行的。

  • 触发事件:一般指DML(insert update delete)DDL(create alter drop)操作

  • 触发事件涉及对象on table_name | view_name | user_name | db_name

  • 响应动作begin ... end; /

  • 触发时机:什么时候执行响应动作?
    before:在DML操作之前执行响应动作【先执行响应动作,再执行触发事件】【先做日志再做事情】
    after:在DML操作之后执行相应动作【先执行触发事件,再执行响应动作】【先做事情,再做事后处理】
    instead of:替代DML操作【执行响应动作,不执行触发事件】

  • for each row:行级触发器

  • when tri_condition:触发条件,满足条件才触发

1、创建触发器

-- 创建触发器的格式
create or replace trigger tri_name
[before | after | instead of] tri_event
on table_name | view_name | user_name | db_name
[for each row][when tri_condition]
begin
  plsql_sentences;
end;
/

1. 语句级触发器

需求:设计一个触发器,当用户在dept表上执行insert、update、delete操作时,将DML语句的类型和执行时间写入到表dept_DML中。
dept_DML定义如下:

--定义测试表
drop table dept_DML;
create table dept_DML(
DML_type varchar2(10),
oper_date date
);

Oracle--初学小白基础篇(第一版)_第63张图片

-- 创建语句级触发器
create or replace trigger tri_dept
  before insert or update or delete  -- 触发事件
  on dept
declare
  var_tar varchar2(10);
begin  -- 响应动作
-- 写日志
  if inserting then 
    var_tar := 'INSERT';
  elsif updating then
    var_tar := 'UPDATE';
  elsif deleting then
    var_tar := 'DELETING';
  end if;
  insert into dept_DML values(var_tar,sysdate);
end tri_dept;
/

Oracle--初学小白基础篇(第一版)_第64张图片

-- 检测结果
insert into dept values(80,'MASK','GUAGNZHOU');
update dept set dname='TARGET' where deptno='10';
delete from dept where deptno=80;

Oracle--初学小白基础篇(第一版)_第65张图片

2. 行级触发器

需求:创建一个触发器,为表addr添加主键。

-- 序列
drop sequence seq_id;
create sequence seq_id;
seq_id.nextval  --序列的属性,指向当前序列最大值的下一个值,初始值为1
列标识符:
:new:【新值标识符】指向当前insertupdate的行,可以调用字段
:old:【原值标识符】指向当前deleteupdate的行,可以调用字段

-- 使用行级触发器生成主键
-- 创建一个地址表,用于存放地址
drop table addr;
create table addr(
id int primary key,
address varchar2(50)
);

-- 创建触发器 
create or replace trigger id_addr
before insert
on addr
for each row
begin
  select seq_id.nextval into :new.id from dual;
end;
/

-- 检验结果
select * from addr;
insert into addr((id) values(1);  -- 插入不成功
insert into addr(address) values('北京');  -- 插入成功
insert into addr(id,address) values(5,'广州');  -- 插入不成功,id仍然使用seq_id

Oracle--初学小白基础篇(第一版)_第66张图片
Oracle--初学小白基础篇(第一版)_第67张图片
Oracle--初学小白基础篇(第一版)_第68张图片

3. 替换触发器

在执行触发事件时,由响应动作代替触发事件执行。【即触发事件不执行,转而去执行响应动作】
需求:在基表为empdept的联接视图v_emp_dept上插入数据,发现不能通过视图去修改基本表,创建一个替换触发器,将insert联接视图修改为insert emp和dept。

-- 创建一个视图
drop view v_emp_dept;
create view v_emp_dept
as
select a.empno,a.ename,a.deptno,b.dname,b.loc from emp a inner join dept b on a.deptno=b.deptno;

-- 创建替换触发器,在视图中插入数据
create or replace trigger insert_emp_dept
instead of insert
on v_emp_dept
for each row   -- 替换触发器默认也是行级触发器,该行可有可无
declare
  row_dept dept%rowtype;
begin
  select * into row_dept  from dept where deptno=:new.deptno;   -- 在PL/SQL块中使用select语句时,要使用into子句。
  insert into emp(empno,ename,deptno) values(:new.empno,:new.ename,:new.deptno);
exception
  when no_data_found then
    insert into dept values(:new.deptno,:new.dname,:new.loc);
    insert into emp(empno,ename,deptno) values(:new.empno,:new.ename,:new.deptno);
end insert_emp_dept;
/

-- 检验结果,插入语句
insert into v_emp_dept values(8977,'JACK',30,'HOTEL','SHENZHEN'); 
insert into v_emp_dept values(7866,'TOM',90,'SALE','SHANGHAI');  

Oracle--初学小白基础篇(第一版)_第69张图片
Oracle--初学小白基础篇(第一版)_第70张图片
Oracle--初学小白基础篇(第一版)_第71张图片

4.用户事件触发器

  • 用户事件create alter drop analyze comment grant remove rename truncate suspend logon logoff
  • DDL操作create alter drop
    当执行一条DDL语句时,下列属性都会有对应的值,当执行下一条DDL语句时,下列值会被刷新。
    用户事件(DDL操作)的属性:
    ora_dict_obj_name --对象名称
    ora_dict_obj_type --对象类型
    ora_sysevent --DDL操作名称
    ora_login_user --操作用户
-- 创建一个表,用于收集用户DDL操作信息
-- drop table user_ddl_log;
create table user_ddl_log(
obj_name varchar2(20),
obj_type varchar2(20),
ddl_name varchar2(20),
oper_user varchar2(20),
oper_date date
);

-- 创建一个用户事件触发器
-- drop trigger tri_user_ddl;
create or replace trigger tri_user_ddl
  before create or alter or drop
  on scott.schema
begin
  insert into user_ddl_log values(
  ora_dict_obj_name,
  ora_dict_obj_type,
  ora_sysevent,
  ora_login_user,
  sysdate    -- 系统当前时间
);
end;
/
-- 检验结果
select * from user_ddl_log;
create table a(id int);
create view b as select * from emp;
select * from user_ddl_log;

Oracle--初学小白基础篇(第一版)_第72张图片
Oracle--初学小白基础篇(第一版)_第73张图片

2、删除触发器

drop trigger tri_user_ddl;

在这里插入图片描述

3、触发器学习方法:

  • 六要素:名字、触发时机、触发事件、对象、类型、主体
  • 了解清楚触发事件响应动作的前后关系
  • 了解清楚触发器响应动作的逻辑,清晰的知道每一步的作用。

八、包

程序包由PL/SQL程序元素(变量和数据类型)、匿名PL/SQL块、命令PL/SQL块(存储过程和函数)组成。
程序包=包规范+包主体

1、创建包规范

create or replace package pack_name is
[declare_variable];
[declare_type];
[declare_cursor];
[declare_function];
[declare_procedure];
end pack_name;
/
-- 包规范
-- 包含一个函数和一个存储过程
-- 函数返回emp表中某个部门所有员工工资的和,并保留两位小数。
-- 存储过程向dept表插入一条记录。
create or replace package pack_name is
  function fun_name(var_deptno number) return number;
  procedure pro_name(
   a in number,
   b in varchar2,
   c in varchar2);
end pack_name;
/

Oracle--初学小白基础篇(第一版)_第74张图片

2、创建包主体

-- 包主体格式
create [or replace] package body pack_name is
 [inner_variable]
 [cursor_body]
 
 [function_title]
 {begin
 fun_plsql;
 [exception]
 [dowith _ sentences;]
 end [fun_name]}
 
 [procedure_title]
 {begin
 pro_plsql;
 [exception]
 [dowith _ sentences;]
 end [pro_name]}
 ...
end [pack_name];
-- 包主体
create or replace package body pack_name is

function fun_name(var_deptno number)    -- 输入参数
return number   -- 返回值类型
as
sum_a number;    --函数内部变量
begin
  select round(sum(sal),2) into sum_a from emp where deptno=var_deptno;   --调用参数的使用直接使用 参数名,而调用变量的时候要使用 :变量名
  return(sum_a);
exception
  when no_data_found then
     dbms_output.put_line('输入的部门编号不存在,请重新输入!');
     return(0);
end;

procedure pro_name(
a in number,  -- 不能指定数据类型的长度
b in varchar2,
c in varchar2
) as
begin
  insert into dept values(a,b,c);
  commit;
  dbms_output.put_line('已插入一条记录!');
end pro_name;

end pack_name;
/

Oracle--初学小白基础篇(第一版)_第75张图片

3、使用程序包

set serveroutput on
declare
  sum_sal number;
begin
  sum_sal := pack_name.fun_name(10); 
  dbms_output.put_line('总工资为:'||sum_sal);
  pack_name.pro_name(60,'ADMIN','SHANGHAI');
end;
/

Oracle--初学小白基础篇(第一版)_第76张图片

4、程序包学习心得

  • 知道包规范和包主体之间的相同和不同。【框架上相差一个body
  • 了解包规范各元素的书写格式
  • 了解包主体各元素的书写格式
  • 加强掌握变量、数据类型、存储过程、函数的使用
  • 未命名PL/SQL块命名的PL/SQL程序中使用程序包。

附件

附件1:emp表和dept表的建表语句

-- 表A和表B若存在外键关联关系,如A的某一非主键的字段是B的主键。则删除和创建表A、B的顺序如下:
-- 删除A删除B创建B创建A
DROP TABLE EMP;
DROP TABLE DEPT;
CREATE TABLE DEPT
    (DEPTNO NUMBER(2) CONSTRAINT PK_DEPT PRIMARY KEY,
	DNAME VARCHAR2(14),
	LOC VARCHAR2(13));
CREATE TABLE EMP
       (EMPNO NUMBER(4) CONSTRAINT PK_EMP PRIMARY KEY,
	ENAME VARCHAR2(10),
	JOB VARCHAR2(9),
	MGR NUMBER(4),
	HIREDATE DATE,
	SAL NUMBER(7,2),
	COMM NUMBER(7,2),
	DEPTNO NUMBER(2) CONSTRAINT FK_DEPTNO REFERENCES DEPT);
	
INSERT INTO DEPT VALUES(10,'ACCOUNTING','NEW YORK');
INSERT INTO DEPT VALUES (20,'RESEARCH','DALLAS');
INSERT INTO DEPT VALUES(30,'SALES','CHICAGO');
INSERT INTO DEPT VALUES(40,'OPERATIONS','BOSTON');

INSERT INTO EMP VALUES
(7369,'SMITH','CLERK',7902,to_date('17-12-1980','dd-mm-yyyy'),800,NULL,20);
INSERT INTO EMP VALUES
(7499,'ALLEN','SALESMAN',7698,to_date('20-2-1981','dd-mm-yyyy'),1600,300,30);
INSERT INTO EMP VALUES
(7521,'WARD','SALESMAN',7698,to_date('22-2-1981','dd-mm-yyyy'),1250,500,30);
INSERT INTO EMP VALUES
(7566,'JONES','MANAGER',7839,to_date('2-4-1981','dd-mm-yyyy'),2975,NULL,20);
INSERT INTO EMP VALUES
(7654,'MARTIN','SALESMAN',7698,to_date('28-9-1981','dd-mm-yyyy'),1250,1400,30);
INSERT INTO EMP VALUES
(7698,'BLAKE','MANAGER',7839,to_date('1-5-1981','dd-mm-yyyy'),2850,NULL,30);
INSERT INTO EMP VALUES
(7782,'CLARK','MANAGER',7839,to_date('9-6-1981','dd-mm-yyyy'),2450,NULL,10);
INSERT INTO EMP VALUES
(7788,'SCOTT','ANALYST',7566,to_date('20-6-1981','dd-mm-yyyy'),3000,NULL,20);
INSERT INTO EMP VALUES
(7839,'KING','PRESIDENT',NULL,to_date('17-11-1981','dd-mm-yyyy'),5000,NULL,10);
INSERT INTO EMP VALUES
(7844,'TURNER','SALESMAN',7698,to_date('8-9-1981','dd-mm-yyyy'),1500,0,30);
INSERT INTO EMP VALUES
(7876,'ADAMS','CLERK',7788,to_date('9-7-1981','dd-mm-yyyy'),1100,NULL,20);
INSERT INTO EMP VALUES
(7900,'JAMES','CLERK',7698,to_date('3-12-1981','dd-mm-yyyy'),950,NULL,30);
INSERT INTO EMP VALUES
(7902,'FORD','ANALYST',7566,to_date('3-12-1981','dd-mm-yyyy'),3000,NULL,20);
INSERT INTO EMP VALUES
(7934,'MILLER','CLERK',7782,to_date('23-1-1982','dd-mm-yyyy'),1300,NULL,10);

写这篇博客,意义在于让自己能够更好的掌握Oracle数据库,在需要的时候可以快速的找到相关的知识,也希望能给入门Oracle的同志们一个快捷的方式,不像我一样跌跌撞撞。

你可能感兴趣的:(ORACLE,数据库,oracle,sql,mysql)