oracle数据库笔记(包括增删查改,索引,plsql,游标,存储函数和存储过程,包)

oracle数据库笔记

注释

1、单行注释:用--

2、多行注释:用/**/

用户的身份

1、管理员用户

管理员用户可以访问数据库中的任何数据,不需要权限,也可以创建用户、修改用户密码、赋予用户权限等行为。

2、普通用户

普通用户被管理员创建,所有访问权限来自于管理员的赋予。创建的用户都是普通用户 初始状态不具备任何权限。

如果想要用户可以正常进行登录,需要我们提供对应的权限。

**- connect 权限 连接权限 (允许用户进行登录)

  • resource 权限 资源权限(允许用户创建属于自己的表)
  • dba 权限 管理员权限 (允许用户以管理员身份进行登录) 不要赋予该权限
  • 权限撤回 revoke
  • 用户状态 lock unlock 用户一般情况下,在创建的时候,默认都是解锁的,可以在创建用户的时候,定义用户的状态为锁定状态,锁定状态的用户,就算具有权限,也无法登录数据库
  • 可以使用alter关键字对指定用户进行解锁
    **

用户、表的创建

用户创建、添加权限、删除修改权限的例子

-- 查询scott用户下的emp表的所有记录
select * from scott.emp;
-- 创建普通用户  
-- 创建一个叫做mqq的用户 定义密码为a12345   
-- 创建的用户都是普通用户 初始状态不具备任何权限 
create user mqq identified by a12345;  
--连接权限
grant connect to mqq;
--资源权限
grant resource to mqq;
--  也可以合并写为一条 
grant connect,resource to mqq; 
-- 撤回权限
revoke connect from mqq;
revoke resource from mqq;
-- 也可以合并写为 
revoke connect,resource from mqq;
-- 锁定用户
create user mqq identified by a12345 account lock;
-- 使用alter关键字对指定用户进行解锁
alter user mqq account unlock;

创建表

create table 表名称(
表字段 字段类型,
表字段 字段类型
);

基本字段类型有:

number(n) – 最多可以容纳8位长度的数据

number(n,m) – 限制字段最多能够容纳n位数字 其中小数部分m位 整数部分n-m位

char(n): 定长 最大不能超过2000字节

  • 存储的是字符串类型 代表的是该字段所能容纳的数据的
  • 最大字节大小 字符集 英文字符 一个字符占据一个字节
  • 中文字符 根据所使用的编码不同 一个中文字符可能占据2 3 甚至是4个字节
  • 当前oracle 所使用的字符集 一个汉字占据三个字节

**varchar2(n) ** 不定长 最大不能超过4000字节

char和varchar2之间的区别:

  • char 定长类型,如果规定20个字节,但只使用了三个字节,则oracle会使用空格自动补足剩下的17个字节
  • varchar2 类型后面的数字只是定义了字段能够存储的最大值。但是,实际在存储时候所占据的空间大小以存储的数据为准
  • varchar2 节约了空间 但是牺牲了效率 时间换空间
  • char类型 就牺牲了空间 但是提高了效率 空间换时间
  • 阿里的要求 字段值几乎相等的情况下 必须使用char类型

date 时间类型数据,值如果为sysdate(oracle 的一个自带函数),代表当前的时间。

to_ date 将字符串转换成指定格式的日期 to_ date(‘2018-09-01 06:06:06’,‘YYYY-MM-DD HH24:MI:SS’)
timestamp 精确日期类型 可以精确到秒后6位 甚至包含时区
clob 存储大文本 或者是xml配置文件
blob 存储二进制对象 图片 音频 视频等等
现在的开发中 blob 和 clob使用比较少 因为数据库存储大数据量的数据 性能消耗过大
一般来讲需要存储的资源 会以公网url的方式 存储目标资源的公网路径 进行存储

插播一个硬盘和内存

  • 所有的数据都是在硬盘中进行读和写入的
  • 但是在对数据进行操作 或者是运行程序的时候 需要将数据从硬盘提取到内存中进行读取
  • 你想要将数据提取到内存中 就必须向内存申请空间

约束

  • primary key -主键约束,值不可以为空,不可以出现重复值,一张表中只能有一个字段建立主键约束
  • alter table 表名 add constraint 约束名称 check(检查约束语句)-检查约束
  • alter table 表名 modify(字段名 not null);-非空约束 限制该字段字段值不可以为空
  • alter table 表名 modify(字段名 default ‘默认值’);-默认约束 插入数据时,如果没有给添加了默认约束的字段提供值,则约束会给其提供默认值
  • alter table 表名 add constraint 约束名称 unique(字段名); -唯一约束 限制字段中插入的值不可以重复(主键约束可以理解为是一种特殊的唯一约束) 该字段的值不可重复
  • alter table 需要建立约束的表名 add constraint 约束名称 foreign key(需要建立约束的表的字段) references 被关联的表(被关联的表的字段);(alter table 子表表名 add constraint 外键约束的名称 foreign key(字段名) references 父表名称(字段名)) 被关联字段必须是唯一的(1. 子表在插入数据的时候 必须在父表中拥有关联记录 2. 父表的数据 如果在子表中有关联数据存在 父表数据无法删除 3.被建立外键约束的父表 是不可以被直接删除的)

建立约束例子

-- 检查约束 限制性别的字段值只能是男或者女
alter table student add constraint ch_stu_gen check(gender = '男' or gender='女');
-- 检查约束 限制学生表中age的范围在18 ~25 
alter table student add constraint ch_stu_age check(age between 18 and 25);
-- 非空约束 限制姓名字段值不可以为空
alter table student modify(sname not null);
-- 默认约束 
alter table student modify(addr default '南京');
-- 唯一约束 地址不可重复
alter table student add constraint un_stu_addr unique(addr);

插入/修改/删除数据

插入:

  • 插入全部字段值
    insert into 表名 values(字段1,字段2,…);括号中需要有所有字段的值
  • 插入部分指定字段值
    insert into 表名(指定字段1,指定字段2) values(指定字段值1,指定字段值2);

修改:

  • 修改一个字段
    update 表名 set 要修改字段名 = ‘要修改字段的值’ where 条件字段=‘条件字段值’;
  • 修改几个字段
    update 表名 set 要修改字段名1 = ‘要修改字段名1’ ,要修改字段名2 = 要修改字段值2 where 条件字段= ‘条件字段值’;

删除:

  • 删除数据
    delete from student where 删除条件字段=‘删除条件字段值’;

数据库语句的分类

  • 数据定义语言 DDL create alter drop turncate(清空表数据)
  • 数据操纵语言 DML insert update delete
  • 数据查询语言 DQL select order by where group by having join 等和查询相关的数据库关键字
  • 事务控制语言 TCL commit rollback savepoint
  • 数据控制语言 DCL grant revoke
  • 冷知识 oracle单表最多支持一亿条数据 mysql 单表最多只支持500W条

数据添加的本质是硬盘读写,大量的硬盘读写会导致硬盘性能下降,数据库为了避免这样的情况,所以有了日志文件,并且引入了session会话。

oracle是支持多个用户同时登录的数据库,每当发生了用户登录的行为,则我们的数据库都会为该用户分配一个session会话,每一个用户都有一个自己的日志文件,创建表create用户属于DDL语句,而用户所执行的所有DDL语句都会被立刻提交到数据库。但是所有的DML语句并不会被立刻提交到数据库,而是会存放在于会话锁绑定的一个日志文件中,如果你不提交sql语句到数据库,则管理员和其他用户都无法看到你没提交到数据库的执行的sql语句得到的数据。

truncate删除的数据是不可以恢复的 DDL操作 DDL操作是直接提交数据库的。

将结果提交到数据库中,必须执行指令commit;,不然只能在当前用户的会话中查看到。

savepoint 叫做存档点 对当前日志文件中的数据操作进行存档 savepoint A;

rollback 回滚 读档 可以将日志文件中的数据状态 回滚到指定存档点的时候 rollback to A;

  • 1.日志文件中的数据 只有在你使用commit或者切换用户登录的情况下 才会提交
  • 2.commit 会提交并清空当前用户的日志文件中的所有数据 包括存档点
  • 3.在数据没有被提交的情况下 使用rollback to 保存点的名称 可以使日志文件的数据恢复到定义保存点时候的状态 如果直接使用rollback 没有指定保存点 数据会默认回滚到上一次commit的时候

向公司的数据库插入100w条数据 这个是业务
我需要准备100w条insert语句 负责数据库插入,这100w条insert语句 就可以看住是一个整体,这个整体叫做事务。

  • 事务的四大特性 原子性 一致性 持久性和隔离性
  • 原子性 所有的事务中的dml语句 要么全部提交 要么全部不提交
  • 持久性 确保所有数据都能够长期的存储在数据库中执性的sql语句需要及时被commit
  • 隔离性 在A事务操作数据的时候 B事务 不可以操作数据 如果B想要操作 只能在A操作之前操作 或者在A操作完成之后进行操作 目的是避免多个事务同时操作一个数据 所产生的错误数据(脏数据)

字段修改(增/删/重名命/修改字段类型/删除用户/删除表):

  • 增加字段 alter table 表名 add 字段 字段类型;
  • 删除字段 alter table 表名 drop column 字段名;
  • 重命名字段 alter table 表名 rename column 原字段名 to 修改后的字段名;
  • 修改字段的数据类型 原本是字符串类型的数据 如果表里有数据 不可以转为number类型 alter table 表名 modify 字段名 字段类型;
  • 用户修改密码 alter user 要修改的用户名 identified by 密码;
  • 用户删除 drop user 用户名;-- 表的删除 drop table 表名;

查询语句:

单表简单查询

    • 代表oracle的通配符表示选择表中的所有字段,from 之后代表你数据的来源,所有的查询语句 最少要有select 和 from关键字
  • 可使用别名 修改字段在显示时候的效果 在字段名之后空格跟别名

  • 字符串连接符号 || 可以将字段的值和指定的字符串进行拼接。例子: select ename||‘的薪水为’||sal 薪资情况 from emp;

  • concat 也是做字符串拼接的 但是每一次只能拼接两个

    select concat(concat(concat(‘员工的姓名叫’,ename),‘他的收入为’),sal) 薪资情况 from emp;

  • 字段的值可以经过数学运算之后再显示

  • where关键字 条件筛选

  • 运算符号:and or > < >= <= = 不等于 != <> between and in not

  • 空值处理 如果对应的某一个字段没有值 可以用null代表空值,值为0 不等于空值。

  • nvl 对字段的空值进行处理 可以指定值为null的字段值 如何显示

      查询所有人的奖金,奖金为空的显示为0 不为空的正常显示
      select ename,nvl(comm,0) from emp;
    
  • nvl2 不管你如何查询 改变的只是查询结果的显示 不会改变表中原有的数据

  • floor 对向下取证

      comm表示你还需要处理的字段 comm+1000 代表如果字段的值不为空 则+1000显示 100 代表如果字段的值为空 则显示为100 
      查询所有人的奖金 所有奖金为空的人 设置奖金显示为100 所有奖金不为空的人 奖金在原有基础上+1000
      select ename,nvl2(comm,comm+1000,100) 修改显示之后的奖金 from emp;
      查询所有部门编号为20并且sal>1500的所有员工的姓名,部门编号和年收入
      年收入=(sal+comm)*12
      select ename,deptno,(sal+nvl(comm,0))*12 from emp where deptno=20 and sal >1500;
    
  • 聚合函数 对多行数据进行统计的函数 如果没有指定数据统计的范围 默认统计所有数据

    • min max avg count sum

    • 最小值 最大值 平均 统计出现次数 求和

        查询emp表中所有员工的最低薪水 最高薪水 平均薪水 薪水总合
        select min(sal) 最低薪水,max(sal) 最高薪水,floor(avg(sal)) 平均薪水,sum(sal) 薪水综合 
      

      from emp;

  • count 统计的值指定字段下 非空值的数量

      -- 统计所有奖金不为空的人数
      select count(comm) from emp;
    
  • count(*) 代表的是统计记录行数

      -- 统计emp表的员工人数
      select count(*) from emp;
    
  • group by 分组

    • 分组会将数据按照你所指定的分组字段进行分类 所有指定字段值相同的数据会被分配到一组 你可以使用我们的聚合函数对每一组的数据进行分可别统计

        -- 查询各部门的平均工资?
        select deptno,avg(sal) from emp group by deptno;
      
    • 可以使用聚合函数分别统计不同组的相关信息

    • 数据被分组之后 select后面只可以出现 被分组的字段本身 和五种聚合函数

    • 如果出现了其他字段查询 会无法执行

    • 数据被分组之后 从行数据 变成了组数据 原本行数据字段不可以被查询

    • 可以同时基于两个字段进行分组 分组的依据 是这两个字段值的组合

        select count(*),deptno,job from emp group by (deptno,job);
      
  • 条件筛选 having 用来筛选分组之后的数据(组数据)

  • 条件筛选 where 用来筛选分组之前的数据 (行数据)

      -- 查询各个部门中去除JAMES这个员工之外的所有部门的平均工资情况,并且只筛选平均工资大于2000的部门数据
      select deptno,avg(sal) from emp where ename!='JAMES' group by 
      deptno having avg(sal)>2000;
    
  • 排序 按照指定的字段的值 对数据进行排序

    • asc 代表升序 asc通常可以省略不写

    • desc 代表降序

        -- 查询所有员工的信息 并且按照工资进行升序排序
        select * from emp order by sal asc;
        select * from emp order by sal desc;
      
  • 多条件排序

    • 空值在进行比较的时候 会被当做无穷大 要排序的字段如果包含空值 请使用nvl函数

    • 将其转换为0

        -- 查询所有员工的信息 按照工资进行升序排序 工资相等的按照comm降序排序
        select * from emp order by sal asc,nvl(comm,0) desc;
        select * from emp order by nvl(comm,0) desc;
      
  • 去重 distinct

      -- 查询emp表中的所有部门信息
      select distinct deptno from emp order by deptno;
    
  • sql关键字的执行流程

书写顺序— 执行顺序

select-----6.从前面的筛选之后的数据中 选择指定的字段进行显示

distinct—5.按照指定字段 对数据进行去重

from-------1.from 决定了数据的来源

where------2.where 对分组之前的数据进行筛选

group by—3.group by 对筛选之后的数据 进行分组

having-----4. 对分组之后的数据进行筛选

order by—7.对前六部得到的数据筛选完成之后按照指定字段进行排序

	--查询各个部门的平均工资,并且按照平均工资降序排序
	-- 使用别名的时候 别名的定义必须在别名的使用之前
	-- 字段 表名 order by排序字段 可以使用别名
	select deptno,avg(sal) a from emp group by deptno order by a desc;

子查询

  • 不相关子查询 把一条查询语句的查询结果 作为另一条语句的查询条件或者数据来源

  • 子查询语句运行完成之后 外部语句才会执行

      -- 查询所有薪资比SMITH高的员工信息
      select * from emp where sal >(select sal from emp where ename='SMITH')
    

rowid rownum 伪列

  • rowid(为索引提供服务的) 数据库中的任意一行数据 都有一个独一无二的rowid

  • rowid相当于是每一条记录的身份证

  • e相当于是emp表的别名 代表emp

  • e.* 访问emp表的所有非伪列字段

  • 如何在查询数据的时候 进行数据的去重?

      create table jerk(
      id number,
      jname varchar2(20)
      );
    
    • 使用distinct去重

        select distinct id,jname from jerk;
      
    • 使用分组去重

        select id,jname from jerk group by id,jname;
      
    • 使用rowid进行去重

        select rowid,j.* from jerk j;
      
    • 利用分组 从每一个jerk中 获取一个rowid 利用子查询进行筛选
      – max min 将重复数据分到一组
      – 字符比较大小是根据ASCII码值比较的
      select * from jerk where rowid in
      (select max(rowid) from jerk group by id)

  • rowid优点

    • 不仅可以用去查询去重 也可以用来删除重复的数据

    • 删除jerk表中的重复记录,从每个组中提取一个rowid ,删除的时候 不删除该数据

        delete from jerk where rowid not in
        (select max(rowid) from jerk group by id)
      

模糊查询

  • like 后面的是匹配的表达式

  • % 代表匹配任意的 0个 或1个 或任意个字符

  • _ 代表匹配任意单个字符

      -- 查询所有姓名以j开头的员工信息
      select * from emp where ename like 'J%'
      -- 查询姓名第二个字符为A的员工信息
      select * from emp where ename like '_A%';
      -- 查询姓名第二个字符为A 倒数第二个字符为E的员工信息
      select * from emp where ename like '_A%E_';
    

交并集

  • minus 去交集 A集合去除自己和B集合重复的部分

  • union 取并集

  • 如果把集合比喻为查询语句 union时候 顺序不影响结果,minus 集合在前在后的顺序对minus的结果是有影响的
    虚表

  • 这张表是没有任何属性的 用来给程序员临时显示数据使用的

    select 8888*8888 from dual;

日期相关函数

select sysdate,add_months(sysdate,1) from dual;
-- 添加月份的第一个参数 可以是系统函数 sysdate 可以是你表中的日期类型字段
-- 也可以是你使用to_date转换而生成的日期
-- 查询2044年的100个月之后的日期
select add_months(to_date('2044-04-04','YYYY-MM-DD'),100) from dual;
select last_day(sysdate) from dual;
  • 常用的单行函数

      -- emp 中 有一个员工的入职时间
      -- 要你根据入职时间获取员工的工龄
      -- 三种常用方法 选择你喜欢的使用就可以了
      -- 日期类型的数据 是可以直接相减的 得到的是天数 需要除以365
      select ename,floor((sysdate-hiredate)/365) 入职时间 from emp
      -- 使用to_char 函数 将日期类型中的年份提取出来 进行计算
      select ename,to_char(sysdate,'YYYY')-to_char(hiredate,'YYYY') 入职时间 from emp
      -- 使用extract 对日期的指定数据进行提取
      select ename,extract(year from sysdate)-extract(year from hiredate) 入职时间 from emp;
    
      -- rownum 分页查询
      -- rownum 伪列 相当于是每一行数据所对应的行号
      -- rownum只和你from 之后的数据源有关
      select rownum ,e.* from emp e;
      -- 查询emp表的前十条记录
      select * from emp where rownum<11;
      -- 工资前十高的员工信息
      select * from emp where rownum<11 order by sal desc;
      -- 利用子查询 先对数据进行排序 再进行rownum的筛选
      select * from 
      (select * from emp order by sal desc)
      where rownum<11;
      -- rownum 不可以使用大于作为筛选条件 
      -- rownum在进行比对的时候 只要一次不满足 后续比对就会停止
      select * from emp where rownum>0;
      -- 我即想使用rownum的特性 进行数据筛选 又想避开rownum弊端
      -- 在子查询中 rownum是伪列 它具有特殊的效果 
      -- 如果我们把子查询和语句的结果 单做是一张表 rownum的别名r 就变成了
      -- 一个普通字段  他具备这rownum的属性 但是不具备rownum的特性
      -- 通过这种方式规避rownum的弊端 实现数据截取
      --  查询emp表中第六条到第十条记录
      select * from
      (select rownum r,e.* from emp e)
      where r between 6 and 10
      -- 查询emp表中 工资第六到第十高的员工,并且要求员工的部门为10 求出符合要求的员工的姓名和sal
      select ename,sal from 
      (select * from 
      (select rownum r,a.* from
      (select * from emp order by sal desc)a)
      where r between 6 and 10)
      where deptno = 10;
    

多表查询

  • 笛卡尔积就是将两个集合中所有元素的匹配可能

  • 数据库将每一张表 当做是一个集合 每一行记录当做是一个元素 将两张表以笛卡尔积的方式拼接在一起 列出所有记录和记录之前组合的可能性 得到的数据就是两张表做笛卡尔积之后的数据。

  • 等值连接 select * from emp e,dept d where e.deptno = d.deptno;

  • 企业要求 笛卡尔积不可以做三表及以上

  • 92sql范式 和 97sql范式

    • 两种范式的区别在于数据筛选的写法和性能的差异

        -- 查询所有员工的姓名 部门编号和部门名称
        -- 92范式书写
        select e.ename,d.deptno,d.loc from emp e,dept d where e.deptno=d.deptno;
        -- 97sql范式 所使用的都是连接查询 
        select e.ename,d.deptno ,d.loc from emp e join dept d on e.deptno=d.deptno;
      
    • 97 范式中 表和表还是做笛卡尔积 但是进行等值连接的时候 进行连接的字段会写在 on 后面 从代码角度而言 更加清晰

  • 在可以使用子查询的场景下 子查询的性能最优

  • 如果查询的字段 来自于两张表 就无法使用子查询 所以优先选择连接查询

  • 子查询>连接查询>直接笛卡尔积

  • 97sql提供了五种连接查询的方式

    • cross join 直接笛卡尔积
    • inner join 内连接 一般可以简写成join
    • left join 左连接
    • right join 右连接
    • full outer join 满外连接
  • 只会复制表的字段和字段的值 不会复制约束

      create table emp1 as select * from emp;
    
  • left join 在连接的时候 会以左表的数据为主 不管左表中的数据在右表中是否有匹配数据 都强制显示

  • right join 在连接的时候 会以右表的数据为主 不管右表中的数据 在左表中是否有匹配数据 都强制显示

  • inner join 一般可以简写为join 如果发现一张表的数据在另一张表中没有匹配信息 则不显示

  • full outer join 满外连接 不管左右两张表中的数据 是否有匹配信息存在 都强制显示

  • 自连接 表自己和自己做笛卡尔积 不等值连接

数据库建模 三大范式

  • 第一范式 所有的列不可以再分(列的原子性)
  • 第二范式 所有的字段都和主键直接相关
  • 第三范式 表和表之间只存关系 不存数据

数据库对象(表 序列 索引 表空间 同义词 视图 游标 触发器 plsql 存储过程 存储函数 包)

  • 序列是用来生成连续的整数数据的对象 一般用来为数据库表的主键提供值

  • 语法如下:

    • create sequence 序列名称

    • start with xx -序列从某一个整数值开始生成数字

    • increment by xx -序列的增长间隔 如果是正数 则升序生成 如果是负数则降序生吃鞥

    • maxvalue xx | nomaxvalue -指定序列所生成数字的最大值 或者不设置最大值

    • minvalue xx | nominvalue -指定序列所生成数字的最小值 或者不设置最小值

    • 就算你不设置最大最小值 序列所生成值也是有上限的

    • 升序情况 默认最大值为1027 降序默认最大值为-1

    • 最小值 升序默认最小值为1 降序默认最小值为-1026

    • cycle | nocycle – cycle 表示序列所生成的值达到最大值之后 会从最小值重新开始循环

    • nocycle表示没有循环 一旦到达表截止 序列就不可以再生成新的值

    • cache xx | nocache – 序列所生产的数字叫做序列号

    • 如果使用cache缓存 序列会根据序列的规则预先生成一组序列号 存储在内存中 方便用户直接使用

    • 当内存中的序列号用完的时候 系统会自动生成一组新的序列号 保存在内存中

    • oracle默认会生成20个序列号

    • nocache 不会预先存储序列号在内存中而是每次获取的时候再进行计算

        -- 创建一个从1开始 最大值为40 每次增长间隔为1 不使用循环 并且使用缓存 序列中有预先分配好的30个序列号的序列
        drop sequence myseq;
        create sequence myseq
        maxvalue 40
        minvalue 1
        start with 1
        increment by 1 
        nocycle 
        cache 30 
      
    • 序列对象默认提供了两个属性

    • nextval 从序列中取出下一个值

    • currval 从序列中取出当前值(如果序列没有生成过序列号 是无法获取当前值的)

    • dual

        select myseq.nextval from dual;
        select myseq.currval from dual;
      
        create sequence myseq1
        maxvalue 40
        minvalue 1
        start with 3
        increment by 5 
        cycle 
        nocache
      
    • 在序列设置循环的情况下 起始值只在第一次循环有效

    • 第二次循环开始之后 就会从最小值开始生成 起始值就无效了

    • 在定义序列的时候 序列的最小值 必须小于等于起始值

    • 最大值必须大于等于起始值

        -- 为jerk表创建序列 利用序列自动生成主键
        create sequence myseq2 
        minvalue 1
        start with 1
        increment by 1 
        nocycle
        nomaxvalue
        nocache
        insert into jerk values(myseq2.nextval,'m');
        insert into jerk values(myseq2.nextval,'q');
        insert into jerk values(myseq2.nextval,'q');
      
    • 在我们后续学完plsql语言和触发器之后 可以结合序列和触发器 实现表主键的自动增长

    • 序列本身是有弊端的 最大值太小 只有1027 数据量大的情况下比较容易重复

    • 所以一般在开发的时候 数据库的主键会采用uuid

    • 开发过程中 会有一个工具叫做uuid生成器 可以确保数据库的主键字段值一定不会重复

  • 视图本质是一个预查询

    • 创建视图之前 所有普通用户必须由管理员赋予创建视图的权限

    • 切换到sys 赋予创建视图的权限

        grant create view to scott;
      
    • 视图语法

        create[or replace][force|noforce] view 视图名称
        as
        select 查询语句
        [with read only]
      
    • 如果在创建视图的时候 选择了or replace参数 当视图已经存在的时候 则会使用当前视图进行替换

    • force 及时视图基于的 表并不存在 也可以创建视图 只不过视图不可以正常使用

    • with read only 表示当前视图是一个只读视图

        --创建一个视图 对应的查询需求为 查询emp表中各个部门工资前2高的员工信息
        create or replace  view emp_rank
        as
        select a.empno,a.ename,a.job,nvl(a.comm,0) co,d.dname,d.loc from 
        (select e.*,row_number()over(partition by deptno order by sal desc)rn from emp e)a
        join dept d on a.deptno=d.deptno where rn <3
        select * from test_view;
        -- 如果你在创建视图的时候 不添加只读约束 使用者是可以通过视图修改基表数据的
        delete from test_view where ename='JONES';
        select * from emp;
        -- 一般来说 为了保护基表的数据 所以在创建视图的时候 必须添加只读约束
      
    • 视图的意义何在?

      • 1.简化复杂查询
      • 2.保护基表数据
      • 3.屏蔽使用者不关心 或者不希望被使用者访问的字段信息
  • 同义词可以理解为数据库对象的别名

      	-- 切换到管理员用户 
      	create user cll identified by a123;
      	-- 赋予用户最基本的权限 以及创建同义词的权限
      	grant connect,resource to cll;
      	grant create synonym to cll;
      	-- 切换回cll用户
      	-- 对scott用户的emp表建立一个同义词
      	create synonym myemp for scott.emp;
      	-- scott用户赋予用户cll权限
      	grant select on emp to cll;
      	-- 赋予表的所有权限
      	grant all on emp to cll;
    
  • 索引 优化查询

    • 建议拓展阅读 树 节点 二叉树 完全二叉树 前序后序中序便利

    • hash散列BTree B+Tree

    • select * from emp where ename = ‘SMITH’ 表中有一亿条数据

    • 在没有索引的情况下 数据库会从第一条数据遍历到最后一条数据索引

    • 索引是建立在数据库的某一个字段中的 索引会对建立索引的字段的值

    • 进行预排序 当你使用了建立索引的字段作为where 条件或者order by条件的时候

    • 数据库就不会进行全表遍历 而是通过索引 直接获取目标行的rowid 通过rowid直接访问数据

    • 当我们创建表的时候 数据库会自动为所有的主键和唯一约束的字段 自动创建索引

        1.普通索引 普通索引允许字段中的值出现重复的
        -- 给emp表的ename字段建立普通索引
        create index ename_idx on emp(ename);
        2.唯一索引 建立的字段必须是主键或者唯一键
        create unique index dname_idx on dept(dname);
        3.联合索引 将两个字段捆在一起 将他们的值的组合作为索引排序的依据 
        create index test_idx on emp(mgr,sal);
        -- 位图索引 聚簇索引(了解即可)
      
    • 索引的特点:

      1. 极大的提高了查询的性能 表的数据量越大 索引带来的性能提升就越大
      2. 建立索引的字段 重复值越少 索引的性能越好 性别这样的字段 是不适合建立索引的
      3. 索引也不是越多越好 一张表最好不要超过五个索引 某个字段是否需要被建立索引 应该考虑这个字段是否是被查询高频使用的字段或者是排序字段 如果字段的使用评率很高 那么就算有重复值 也必须建立索引
      4. 我们对表数据的增删改 会导致数据库自动重建索引 在建立索引的时候 需要考虑 性能的消耗 但是 如果表的数据量非常大 不管这张表是否会频繁增删改查 都必须创建索引 因为查询性能是第一优先级
      5. 在进行查询的时候 需要注意where之后条件的使用 不合规范的使用会导致数据库 放弃使用索引 而进行全表检索(sql优化)
  • 表空间

    • 块 区 段

    • 数据块block

    • oracle中的最小存储单位 为了屏蔽不同操作系统存储结构所带来的差异性

    • oracle定义了数据块的概念 将对所有数据的操作 转换成对oracle块的操作 数据的读写就不会受到操作系统的影响了

    • oracle所有对数据的操作和空间分配 实际上都是针对数据块block进行操作的

    • 我们从表中搜索到一行数据 oracle会从硬盘或者内存缓冲区中 读取到改行所在的数据块

    • 数据块 一般是以kb作为单位来定义的 默认是8kb

    • block之上的概念是 区 extent

    • 区是比块大一级的存储结构 表示的是一连串连续的block集合

    • 物理存储读写是随机读写的

    • 在进行存储信息读写的时候 oracle将分配数据块进行存储 但是没法保证

    • 所有分配的block都是连续存储的 所以才定义了分区

    • 块的上一级 段 segment 是分区的上一层单位

    • 段的类型可以是表 可以是其他的数据库对象

    • 一般来说一个数据库对象对应一个段 一个段对应多个区 一个区对应多个块

    • 数据库提供了一个系统视图 用来展示每一个数据库对象的存储详情

    • 表空间 数据库的逻辑组成部分

      • 从物理上讲 数据库的数据是存放在表空间所对应的数据文件中的
      • 从逻辑上讲 数据库的数据 是存放在表空间内的
    • 表空间一般是由一个或者多个数据文件所组成的 数据文件和日志文件都是数据库中非常重要的文件
      -一个表空间至少有一个数据文件 一个表空间可以拥有多个段 但是 一个段只能属于一个表空间

    • system 系统表空间

    • sysaux 系统辅助表空间

    • Users表空间 如果是普通用户的数据库对象 没有指定表空间存储的情况下 默认会存放在users表空间下

    • temp 当数据库进行大量的子查询计算或者排序计算的时候 如果数据库的内存比较紧张会将一部分的临时数据 存放在临时表空间中

    • 用户自定义表空间

        	-- 创建自定义表空间
        	create tablespace myspace
        	DATAFILE 'c:/myspace_1.ora' size 10M,
        	'c:/myspace_2.ora' size 50M
        	extent management local -- 表空间中区的分配由oracle系统自动分配
        	
        	-- 可以在创建数据库对象(段)的时候 指定其所属表空间
        	create table lzs(
        	lid number
        	)
        	tablespace myspace;
        	create table wjx(
        	wid number primary key
        	)
        	tablespace myspace;
        	-- 可以在创建用户的时候 指定该用户所使用的表空间 该用户后续创建的所有数据库对象 都会自动存储到
        	-- 你指定的表空间内
        	create user css identified by a123 
        	default tablespace myspace;
        	
        	-- 修改表空间的数据文件大小 
        	alter database datafile 'c:/myspace_1.ora' resize 50M;
        	-- 可以给指定表空间添加数据文件
        	alter tablespace myspace add datafile 
        	'c:/myspace_3.ora' size 10M;
      
        	-- 删除表空间 管理员是拥有直接删除表空间的权限的 如果是普通用户删除
        	--需要赋予对应的权限
        	grant drop tablespace to xx;
        	-- 删除表空间以及表空间内的所有数据
        	drop tablespace myspace including contents;
      
  • 数据库的数据文件是不可以从磁盘物理删除的 数据库在启动的时候 会自动检查所有的表空间的数据文件是否正常 如果你没有用过数据库 而是直接在本地通过磁盘物理的删除了数据文件 则数据库将无法启动

  • plsql是一个强类型语言 编译语言 高级语言

    • 强类型语言和弱类型语言

      • 强类型语言的特点 在声明变量并使用的时候 必须声明变量的数据类型 plsql java c++
      • 弱类型语言的特点 声明变量的时候 不需要提供变量的类型 变量的类型由变量的值所决定 javascript shell
    • 编译语言 脚本语言

    • C++ 代码必须先经过编译 才可以执行 plsql java

    • 脚本语言拥有对应的解释器 解释执行即可 python javascript shell

    • 高级语言 c++ c# java php ruby go javascript

    • 中级语言 C语言(丹尼斯里奇 为了编写unix系统)

    • 低级语言 汇编语言 每一个指令操作的都是硬件 缺点:入门门槛太高 太难理解

    • 机器语言 0101

    • 越低级的语言 离计算机底层越近 执行效率越高 嵌入式开发使用的都是C语言

    • 如果普通用户想要执行plsql语言 需要通过sys用户进行权限赋予

        grant create procedure to scott;
        grant execute any procedure to scott;
        grant debug connect session to scott;
        -- 切换到scott用户
        declare
        --你在plsql中需要使用的变量 在delcare出定义声明
        -- plsql语言和sql语句的数据类型是通用的 number char varchar2 date 是可以在plsql中直接使用的
        -- plsql中=代表值比较 赋值使用:=
        sname varchar2(20):='fangling';
        -- 也可以使用default关键字给变量提供初始值 但是在代码执行的时候 赋值还是通过:=
        sname2 varchar2(20) default 'zhoukun';
        begin
          --  你要运行的代码逻辑
          -- plsql的控制台打印 类似于c++ 的print
          dbms_output.put_line(sname||'的好基友是'||sname2);
          end;
      
    • plsql中的常量定义 一旦被定义 值无法被修改

        declare
        pi constant number:=3.14;
        r number default 3;
        area number;
        begin
          -- pi:=3.141502654;
          area:=pi*r*r;
          dbms_output.put_line('面积为'||area);
          end;
      
    • plsql中 除了可以直接使用sql语句的数据类型之外 还有标量数据类型和属性数据类型

    • 标量数据类型 该类型的变量的只有一个值 包括数字型 字符型 日期型 布尔类型

    • 所有的标量数据类型

      • varchar2 char number date 常用的 通用类型
      • timestamp oracle默认的高精度日期类型
      • boolean 布尔类型 true false
      • binary_integer plsql附加数据类型 值介于-231~231之间的整数
      • natural 自然数类型 表示从0开始的正整数
      • natrualn 和natrual几乎一样 但是值不可以为null
      • positive 正整数
      • positiven 非空正整数
      • real 18位精度的浮点型
      • int integer smallint 都是整数类型
      • numberde number的子类型 表示38位精度的整数
      • String 字符类型 和varchar2完全相同
    • 属性数据类型 当声明的变量的值 是数据库表的某一行 或者是数据库某个字段的类型的时候 使用属性数据类型

    • %rowtype 代表的是数据库的某一行数据

        declare
        myemp emp%rowtype;-- 声明一个变量用来存储emp表的一行记录(不可以存储多行)
        begin 
          select * into myemp from emp where empno=7934;
          -- 你想访问该行数据中存储的那一个字段的值 就通过.+字段名进行访问
          dbms_output.put_line(myemp.ename||'--'||myemp.sal);
          end;
      

    -%type 数据类型的借用 引用某一个变量或者数据库的表字段数据类型 作为自己的数据类型

      	declare
      	sal emp.sal%type;-- 定义一个变量叫做sal 数据类型和emp表的sal字段保持一致
      	mysal number(4):=3000;
      	totalsal mysal%type;-- 定义变量totalsal的数据类型和mysql的保持一直
      	begin
      	  -- 查询emp弄为7934的员工薪水 打印该薪水加上mysal之后的数据结果
      	  select sal into sal from emp where empno=7934;
      	  totalsal:=sal+mysal;
      	  dbms_output.put_line(totalsal);
      	  end;  
    
    • plsql的运算符号

    • – 算数运算符 + - * / ** 求幂 3**2=9
      – 关系运算符 > < >= <= = 只能用来做值的比较 不可以用于赋值
      – 不等于 != <>
      – 范围运算符 … 1…100
      – 逻辑运算符 and or not 和sql语句中所使用的是一样的

    • 顺序结构 分支结构 循环结构

    • 分支结构 if else

    •   if 条件 then 
          -- 你要运行的语句
         end if;
          
        if 条件一 then 
          满足条件一所执行的语句
          elsif 条件二 then 
          满足条件而所执行的语句
          else
            以上条件都不满足的时候 所执行的语句
            end if;
         -- 使用plsql进行数据修改 查询emp的JAMES的sal  如果大于900 则工资增加10000
         -- 如果工资小于900 工资扣100
         -- 如果工资等于900 工资设置为1
        declare
        newsal emp.sal%type;
        begin
         select sal into newsal from emp where ename='JAMES';
         if newsal>900 then
           update emp set sal=sal+10000 where ename='JAMES';
           elsif newsal=900 then 
           update emp set sal=1 where ename='JAMES';
           else
              update emp set sal=sal-500 where ename='JAMES';
         end if;
         commit;      
        end; 
        -- 循环结构 while loop for in
        -- 求1-100的和
        declare
        counter number(3):=0;
        sumResult number:=0;
        begin
          while counter<100 loop
            counter:=counter+1;
            sumResult:=sumResult+counter;
            end loop;
            dbms_output.put_line('1-100的计算结果为:'||sumResult);
          end;
        
        -- for in  方式求 1-100和
        declare
        counter number(3):=0;
        sumResult number:=0;
        begin
          for counter in 1..100 loop 
            sumResult:=sumResult+counter;
            end loop;
                dbms_output.put_line('1-100的计算结果为:'||sumResult);
          end; 
      -- 打印emp表中 所有员工的ename和sal
      -- %rowtype变量 一次只能存储一行数据 需要借助rownum
      -- 通过循环和rownum 每一次循环读取表中的一行数据 存到%rowtype变量中 然后打印
      -- 一直到循环结束
      declare
      emp_row emp%rowtype;-- 用来存储emp表一行记录的变量
      v_count number(2); -- 记录表的行数 
      v number(2) default 0;-- 记录循环次数
      begin
        -- 先获取表一共有多少行数据 该数据决定了循环的最终次数
        select count(*) into v_count from emp;
        -- 死循环 直接使用 loop end loop 在循环内设置循环退出的条件
        loop
           v:=v+1;
           select a.empno,a.ename,a.job,a.mgr,a.hiredate,a.sal,a.comm,a.deptno
           into emp_row from
           (select rownum r,e.* from emp e)a where a.r=v;
           -- 打印数据 
           dbms_output.put_line(emp_row.ename||'--'||emp_row.sal);
           -- 设置循环结束的条件
           exit when v=v_count;
          end loop;
        end;
      
    • 练习 借助for循环和if else进行判断
      – 遍历所有员工 将员工的工资和所有员工的平均工资进行对比
      – 如果员工工资大于公司平均工资 则涨薪500
      – 并且打印更新之后的薪水 打印内容如下
      – xx员工的工资太高了 所以给其涨薪500 当前sal为xx
      – 如果员工工资小于公司的平均工资 则降薪1000 并且打印更新最后的薪水
      – xx员工的薪资太低了 为了鼓励该员工 给其降薪1000 当前sal为xx
      – 如果员工的工资降薪之后小于0 则设置薪水为0

      declare
      v_count number(2); -- 记录循环总次数
      v number(2) default 0; -- 记录循环次数
      avg_sal emp.sal%type; --  存放平均工资的变量
      v_sal emp.sal%type;-- 存放的是员工的薪水
      emp_row emp%rowtype; -- 存放的是emp表的某一整行数据
      v_empno emp.empno%type;-- 存储员工编号
      v_ename emp.ename%type;-- 存储员工姓名
      begin
        -- 获取表的总记录数
        select count(*) into v_count from emp;
        -- 获取公司的平均工资
        select avg(sal) into avg_sal from emp;
        -- 遍历过程
        loop 
           v:=v+1;
           select a.empno,a.ename,a.job,a.mgr,a.hiredate,a.sal,a.comm,a.deptno
           into emp_row from
           (select rownum r,e.* from emp e)a where a.r=v;
           -- 行记录中 将后续需要使用的数据取出 存放到我们定义好的变量中
           v_sal:=emp_row.sal;
           v_empno:=emp_row.empno;
           v_ename:=emp_row.ename;
           -- 比较判断
           if v_sal>avg_sal then 
             update emp set sal=sal+500 where empno=v_empno;
             dbms_output.put_line(v_ename||'员工工资太高,所以加薪500元,当前sal为:'||(v_sal+500));
             else 
               -- 先判断降薪之后工资是否可能小于0
                if(v_sal-1000)<0 then 
                update emp set sal=0 where empno=v_empno;
                 dbms_output.put_line(v_ename||'员工工资太低,所以降薪,当前sal为0');
                 else
                   update emp set sal=sal-1000 where empno=v_empno;
              dbms_output.put_line(v_ename||'员工工资太低,所以降薪1000元,当前sal为:'||(v_sal-1000));
              end if;
              end if; 
              -- 表的数据遍历借书 循环节数
              exit when v=v_count;
         end loop;
             commit;
        end;
      
  • 触发器 在事件发生的时候 自动执行的sql语句 不能被直接调用 也不可以接受任何参数

    • 根据触发器的创建语句 以及操作的不同对象 触发器分为几类
      • 1 DML触发器
        • 当我们对指定数据进行DML操作的时候 所触发的触发器

        • 语句级触发器 行级触发器

        • 语句级触发器只会触发一次

        • 行级触发器 只要满足条件 就会触发 会多次触发

        • before after触发器

        • before触发器 在触发事件 发生之前 触发阿奇的代码就会执行

        • after 在触发事件发生自后 执行触发器的代码

            create [or replace] trigger 触发器名称
            {before|after}
            触发条件
            on 表名
            [for each row ] -- 如果加上这一行就说明当前触发器为行级触发器
            where 触发条件 -- 声明 什么时候会触发触发器
            begin
              --触发器的代码
              end;
          

    -触发器例子

      -- 在创建触发器之前 先创建两张表 用于后续的场景
      drop table student;  
      create table student(
      id number(20),
      stu_no varchar2(20),
      stu_name varchar2(20),
      stu_age number(2),
      stu_major varchar2(30)
      );
       -- 日志记录表
       create table stu_log(
       log_id number,-- 日志id
       log_action  varchar2(100),-- 记录你对学生表进行的操作
       log_date date,-- 记录操作发生的时间
       log_message varchar2(30)-- 记录操作相关的信息
       );
       --  创建序列 用于后续使用
       create sequence seq_test
       start with 1 
       nomaxvalue
       nominvalue
       nocycle
       nocache
       
       -- 创建一个行级触发器(before触发器) 用来实现主键id的自动插入
       create or replace trigger modify_stu --  创建触发器
       before insert on student -- 定义触发条件 在向学生表插入数据之前
       for each row -- 定义为行级触发器
       -- 触发器被触发之后所执行的代码
       declare
       next_id number;
       begin
         -- 从序列中取出数据 存放在变量next_id上 方便后续使用
         select seq_test.nextval into next_id from dual;
         -- :new代表的是你即将插入的insert语句
         -- :new.id代表的是你即将插入的insert语句对应的id字段值
         -- 由你从序列中获取的数据 代替你insert语句中原有的id属性
         :new.id:=next_id;
         end;
      -- 向学生表中插入数据 但是不提供id
      insert into student(stu_no,stu_name,stu_age,stu_major)
      values('11001100','王建宇',22,'计算机');   
      insert into student(stu_no,stu_name,stu_age,stu_major)
      values('11001101','王宇',22,'计科');    
       
      select * from student; 
       
      --行级触发器(after触发器)
      -- 功能把所有对student表的操作记录都记录到stu_log表中
      
      create or replace trigger modify_stu1
      -- 当插入数据或者删除数据或者修改学生表的stu_name属性的时候 就会触发
      after insert or delete or update of stu_name on student 
      for each row
      begin
        -- 对触发不同条件提供不同的操作
        if inserting then -- 如果是insert操作触发了触发器
        --当有新的数据插入到学生表中的时候 日志表记录行为为insert 并且记录插入数据的时间
        -- 和新插入的学生姓名
        -- :new.stu_name 代表你新插入的语句中的stu_name字段的值  
          insert into stu_log values(1,'insert',sysdate,:new.stu_name);
          elsif deleting then -- 对删除行为所执行的触发器
          -- 如果是删除操作触发了触发器 日志表会记录相关信息
          -- :old代表的是被删除的行记录
          -- :old.stu_name 将被删除的数据的stu_name属性值
          insert into stu_log values(2,'delete',sysdate,:old.stu_name);
          elsif updating then 
          -- :old.stu_name 代表的是被更新之前的学生姓名
          -- :new.stu_name 代表的是被更新之后的学生姓名
          insert into stu_log values(3,'update_old',sysdate,:old.stu_name);
          insert into stu_log values(4,'update_new',sysdate,:new.stu_name);
          end if;
        end;  
    
    
      	-- 语句级触发器(before触发器) 用来控制对表的修改
      	create or replace trigger modify_stu
      	before insert or update or delete on student
      	begin
      	if deleting then 
      	  raise_application_error(-20001,'该表不允许删除数据');
      	  -- 自定义错误 前者是错误的编号 后者是错误的信息
      	  -- 错误id  20000之前是系统错误id  我们无法使用
      	  -- 当错误出现的时候 DML操作不会被执行
      	  elsif updating then 
      	  raise_application_error(-20002,'该表不允许更新数据');
      	  elsif inserting then 
      	  raise_application_error(-20003,'该表不允许插入数据');
      	  end if;
      	end;
    
  • 游标 用来遍历多行数据的工具

    • 显示游标 隐式游标 有点类似于C语言的指针

    • 游标的属性

      • 游标名称%FOUND 返回的是一个布尔值 代表最近一次游标对数据的提取是否成功

      • 成功为true 否则为false

      • 游标名称%NOTFOUND 获取的布尔值于%FOUND正好相反

      • 游标名称%ISOPEN 当游标已经被打开的时候 返回ture 否则返回fasle

      • 游标名称%ROWCOUNT 返回的是游标已经读取的记录数量

      • fetch 游标名称 into XX 移动游标指针并提取数据至指定变量
        -游标例子

        – 查询emp表的所有员工姓名和薪水
        declare
        cursor c_cursor
        is
        select ename,sal from emp;
        v_ename emp.ename%type;
        v_sal emp.sal%type;
        begin
        – 游标在使用之前 需要进行打开
        open c_cursor;
        – 从游标中 将数据提取出来
        –fetch c_cursor into v_ename,v_sal;
        while c_cursor%FOUND loop
        – 对当前游标的指针中是否有数据进行判断
        dbms_output.put_line(v_ename||‘—’||v_sal);
        fetch c_cursor into v_ename,v_sal;
        end loop;
        – 游标在使用完成之后 需要进行关闭
        close c_cursor;
        end;

        – 使用游标 给emp表中员工工资低于1200的员工涨薪50
        declare
        v_empno emp.empno%type;
        v_sal emp.sal%type;
        cursor c_cursor is select empno,sal from emp;
        begin
        open c_cursor;
        loop
        fetch c_cursor into v_empno,v_sal;
        exit when c_cursor%NOTFOUND;
        if v_sal<1200 then
        update emp set sal = sal+50 where empno=v_empno;
        dbms_output.put_line(‘员工编号为’||v_empno||‘的员工已涨薪!’);
        end if;
        dbms_output.put_line(‘游标读取的记录数据量为:’||c_cursor%rowcount);
        end loop;
        close c_cursor;
        end;

    • 显式游标的每次使用 都需要经历打开 关闭 判断 fetch提取 使用太过麻烦

    • 这些操作不应该由程序员完成

    • 隐式游标 只需要定义和使用 打开 关闭 判断 提取 都被省略了

        -- 遍历emp表所有员工的ename和sal
        declare
        cursor c_cur is select ename,sal from emp;
        begin
          -- 省略了游标的open
          -- v_row相当于是你临时定义的一个rowtype 类型的变量
          -- 用来存储你从游标中提取的整行数据
          -- fetch 和 %found也被隐式游标自动省略了
          for v_row in c_cur loop 
            dbms_output.put_line(v_row.ename||'--'||v_row.sal);
            end loop;
            -- 游标的关闭 也被省略了
          end;
          
      
          -- 利用游标 遍历emp表的数据 将其中sal>500 并且职位为SALESMAN的员工
          -- 从emp表删除
        rollback;
        declare
        cursor c_cur is select empno from emp where job='SALESMAN' and sal>500;
        begin
          for v_row in c_cur loop 
            delete from emp where empno = v_row.empno;
            end loop;
          end;
      
  • 存储过程和存储函数

    • plsql匿名代码块

    • 将plsql的代码 存放在存储过程中 而存储过程又是存放在数据库中的

    • 只要用户有对应的权限 所有的用户都可以使用该存储过程实现功能

    • 定义存储过程,调用存储过程的例子

        -- 定义一个存储过程 用来获取emp表的总人数
        create or replace procedure emp_count
        as
        v_count number(2);
        begin
          select count(*) into v_count from emp;
          dbms_output.put_line('emp表总人数为'||v_count);
          end;
        
        -- 调用存储过程
        begin
          emp_count;
          end;
      
        -- 定义一个存储过程 用来展示所有员工的信息 和员工的总人数
        create or replace procedure  emp_list
        as 
        cursor emp_cursor 
        is
        select empno,ename,sal from emp;
        begin
          -- 借助游标遍历数据
          for emp_row in emp_cursor
            loop 
              dbms_output.put_line(emp_row.empno||'---'||emp_row.ename||'---'||emp_row.sal);
              end loop;
              -- 在当前存储过程中 调用其他已经写好的存储过程
              emp_count;
          end;  
          
          -- 调用
          begin
            emp_list;
          end;  
      
    • 我们希望可以根据不同的条件 让存储过程获取不同的结果,所以引入了参数的概念

    • in out in out参数

    • in 参数 定义一个输入的参数变量 用于从存储过程外把参数传递给存储过程内

    • out参数 定义一个输出参数变量 用于从存储过程获取数据

    • in out参数 兼备以上两者的工程

      	 -- 编写一个给指定员工增加指定工资的存储过程 change_sal
      	 select * from emp;
      	 -- p_empno和p_raise都是我们指定的参数的名称 in代表这是一个传入参数 number代表的是参数的
      	 -- 数据类型 default代表默认值 当你调用存储过程没有提供参数的时候 参数就会使用默认值做为自己的值
      	 create or replace procedure change_sal(p_empno in number default 7788,p_raise in number default 10)
      	 as
      	 v_ename  varchar2(10);
      	 v_sal number(5);
      	 begin
      	   -- 获取指定员工的ename和sal  并且存储到本地变量中
      	   select ename,sal into v_ename,v_sal from emp where empno=p_empno;
      	   -- 指定JAMES员工不可以加薪水
      	   if p_empno=7900 then 
      	     dbms_output.put_line('该员工不配涨薪');
      	     raise_application_error(-20001,'该员工不允许涨薪,管老师说的');
      	     end if;
      	     -- 通过update 语句 对指定员工进行sal更新
      	     update emp set sal=sal+p_raise where empno=p_empno;
      	     -- 打印被加薪人的情况
      	     dbms_output.put_line('打工人'||v_ename||'的工资被改为'||(v_sal+p_raise));
      	     -- 异常处理 代码发生错误的时候 所执行的操作
      	     -- others代表所有的错误类型 后续我们会加入指定错误类型
      	     exception when others then
      	      dbms_output.put_line('发生错误,拒绝修改!');
      	      rollback;      
      	   end; 
      	
      	begin
      	 change_sal;
      	  end;
      	
      	-- out参数 可以用来返回存储过程的运行结果
      	create or replace procedure emp_count(p_total out number)
      	as
      	begin
      	  --由存储过程给参数进行赋值
      	  select count(*) into p_total from emp;
      	  end;
      	  
      	-- 在plsql中 调用搓出过程 并且获取out输出参数的值
      	declare
      	v_empcount number;
      	begin
      	      -- 调用存储过程 将存储过程的out参数的值 传递给v_empcount
      	      -- 等价于 v_empcount:=p_total
      	      emp_count(v_empcount);
      	      dbms_output.put_line('打工人总人数为:'||v_empcount);
      	end;  
      	
      	
      	-- in out 参数 不仅负责输入还负责输出
      	
      	-- 定义一个存储过程 负责给指定的电话号码加区号
      	create or replace procedure add_region(p_phone in out varchar2)
      	as
      	begin
      	  p_phone:='025-'||p_phone;
      	  end;
      	-- 用来测试的plsql 
      	declare 
      	v_phone varchar2(100);
      	begin
      	  v_phone:='88888888';
      	  -- 由于 v_phone是 in out类型的参数
      	  -- 先将v_phone的值传递给add_region存储过程的内容
      	  -- p_phone的值就变为了88888888 经过存储过程的拼接 变成了025-88888888
      	  -- 又由于其out参数的特性 这个处理好的电话号会再次被传递给v_phone
      	  -- 等价于 v_phone:=p_phone
      	  add_region(v_phone);
      	  dbms_output.put_line('加区号的电话号码为:'||v_phone);
      	  end;
      
      • 存储函数 存储函数和存储过程都一样 都是plsql代码的封装

      • 但是区别在于 存储函数拥有参数 但是参数只能是in 类型 in可以省略不写

      • 在定义存储函数的时候 会定义return数据类型 也就是返回类型

      • 在代码执行部分 会出现return表达式 但是只有一个return语句会执行

      • 存储函数一旦执行了return 函数就会立刻借书运行

          	-- 创建一个存储函数 通过empno获取ename
          	create or replace function get_emp_ename(p_empno number default 7788)
          	-- 定义的是返回值类型
          	return varchar2
          	as 
          	v_ename varchar2(10);
          	begin
          	  select ename into v_ename from emp where empno=p_empno;
          	  return(v_ename);
          	  exception when NO_DATA_FOUND then 
          	    dbms_output.put_line('没有该编号的雇员');
          	    return(null);
          	    when too_many_rows then 
          	      dbms_output.put_line('返回记录过多,请重新输入!');
          	      return(null);
          	      when others then 
          	        dbms_output.put_line('发生其他类型的错误!');
          	        return(null);
          	  end;
          	-- 测试语句
          	declare
          	v_ename varchar2(20);
          	begin
          	  v_ename:=get_emp_ename(7900);
          	  dbms_output.put_line('雇员7900的姓名为:'||v_ename);
          	  v_ename:=get_emp_ename(7839);
          	  dbms_output.put_line('雇员7839的姓名为:'||v_ename);
          	  end;
        
      • 数据字典 存放了当前用户的所有存储过程和存储函数的源代码

      • user_source 系统提供的视图

          select * from user_source where name = 'GET_DEPT_NAME';
        
      • USER_OBJECTS 查看一个存储过程或者函数是否处于有效状态

          select * from user_objects
        
        • status表示数据库对象的状态 valid表示可用的 (通过编译 可以运行)
        • invalid 不可用(有错误 未通过编译)
  • 包 package

    • 是用来存储相关程序结构的对象 一般存储于数据字段中
    • 包由两个部分组成 一个是包头 package 一个是包体 packagebody
    • 包头相当于是对外的操作接口 对调用者而言是可见的
    • 包体是包的代码部分和实现部分 对调用者而言是不可见的黑盒
    • 包相遇是遥控器主体 ,包头相当于遥控器上的所有按键,包体相当于是遥控器内部的电子元器件
    • 让使用者和设计者分离 使用者只需要知道如何通过按键实现不同的遥控功能即可 遥控器上的每一个按键对应的是某一个存储过程或者存储函数
    • 封装 高内聚 低耦合
    • 包中可以包含什么程序结构?存储过程 存储函数 变量 常量 游标
      • 包头拥有说明部分 可以出现在包的不同位置
      • public 公有元素 在包头中说明 整个应用程序都可以访问
      • private 私有元素 在包体的说明部分说明 只能被包的内部其他部分访问
      • local 本地元素 在包中包含的存储过程或者函数的声明部分声明 只能在定义该元素的过程或者函数中使用
    • 包的特点 可以方便的将存储过程和存储函数组织到一起,如果有多个包 每一个包是独立的 在不同的保重 存储过程和存储函数可以重名
    • oracle自己 也提供了很多系统预定义的系统包 这些包可以在数据库的任何地方使用
      • dbms_output .put_line

      • dbms_output 控制台相关程序

      • dbms_ddl 编译过程 函数 和包

      • dbms_mail oracle的邮箱机制

      • dmbs_lock oracle的复杂锁机制

          -- 创建一个简化版的 用来的管理emp信息的包 叫做emoployee 可以用来从emp表获取员工信息
          -- 修改员工名称 修改工资等等功能
          -- 在创建包的时候 包和包体可以一起编译 也可以分开编译
          -- 如果在一起编译 包头写在前 包体写在后 中间是用 /分割
          create or replace package employe -- 包头部分
          is
          procedure show_detail;-- 包头中声明了一个存储过程 用来展示信息
          procedure get_employe(p_empno number);
          procedure save_employe;
          procedure change_name(p_newname varchar2);
          procedure change_sal(p_newsal number);
          end employe;
          /
          create or replace package body employe -- 包体部分
          is 
          employe emp%rowtype;  
          -- 为你所有的包头中声明的存储过程或者函数提供代码实现
          -- show_detail存储过程的代码实现 展示雇员信息的
          procedure show_detail
            as 
            begin
          dbms_output.put_line('-------雇员信息如下--------');
          dbms_output.put_line('雇员编号:'||employe.empno);
          dbms_output.put_line('雇员名称:'||employe.ename);
          dbms_output.put_line('雇员职务:'||employe.job);
          dbms_output.put_line('雇员工资:'||employe.sal);
          dbms_output.put_line('雇员部门编号:'||employe.deptno);                        
            end show_detail;  
          -- 从emp表获取一个雇员信息
          procedure get_employe(p_empno number)
          as 
            begin
              select * into employe from emp where empno=p_empno;
              dbms_output.put_line('获取雇员'||employe.ename||'信息成功!');
            end get_employe;  
          -- 保存雇员信息到雇员表
          procedure save_employe
            as
             begin
               update emp set ename=employe.ename,sal=employe.sal where empno=employe.empno;
               dbms_output.put_line('雇员信息保存成功!');
               end save_employe;  
          -- 修改雇员姓名
          procedure change_name(p_newname varchar2)
          as
          begin
            employe.ename:=p_newname;
            dbms_output.put_line('修改名称成功!');
            end change_name;       
               
          -- 修改雇员工资
          procedure change_sal(p_newsal number)
            as
          begin 
            employe.sal:=p_newsal;
            dbms_output.put_line('修改工资完成!');
            end change_sal;   
            end employe;    
        
      • 包是长期存储在数据库中

      • 我们在包体中定义了变量 employe rowtype

      • 这个变量只要被赋值过 变量的值可以长期存储

      • 通过包中定义的变量 存储数据

      • 包中的操作 都是针对于这个变量进行操作

      • 如果需要数据最终存储到对应表中 必须要执行dml语句

          begin
           -- employe.get_employe(7900);
          --   employe.show_detail;
          employe.change_sal(666);
          employe.change_name('KOBE');
          employe.save_employe;
            end;
           select * from emp;
        
          -- 创建一个包 对emp表进行完整的crud
          create or replace package emp_pk -- 包头
          is
          v_emp_count number(5);-- 存放雇员总人数
          -- 负责初始化雇员人数(给变量进行赋值)和工资修改的上下限
          procedure init(p_max number,p_min number);
          procedure list_emp;-- 展示雇员信息
          -- 插入雇员
          procedure insert_emp(p_empno number,p_ename varchar2,p_job varchar2,p_sal number);  
          procedure delete_emp(p_empno number);--  删除雇员
          -- 修改雇员工资
          procedure change_emp_sal(p_empno number,p_sal number);
          end emp_pk;
          /
          create or replace package body emp_pk
          is
          v_message varchar2(50);-- 存放用来输出的信息
          v_max_sal number(7);-- 后续在定义工资的最大值时候 用来在包中存储最大值的变量
          v_min_sal number(7);-- 工资的下限
          -- 定义函数 判断对应的雇员是否存在
          -- 在包头中声明的存储过程或者函数 可以被所有其他用户调用
          -- 如果没有在包头中声明 而是定义在包体中 只能在当前包中被 其他存储过程或函数调用
          -- 外界不可以获取
          function exist_emp(p_empno number) return boolean;
          procedure show_message;-- 用来展示数据的存储过程
           
           -- 进行初始化的存储过程
           procedure init(p_max number,p_min number)
             is
             begin
             select count(*) into v_emp_count from emp;-- 给包头中定义的用来存储雇员表总人数的变量赋值
             --  最大最小薪资的赋值
             v_max_sal:=p_max;
             v_min_sal:=p_min;
             v_message:='初始化过程已经完成!';
             show_message;-- 这是后续我们需要写的专门负责打印信息的过程 可以先把调用写好 
             end init;
           -- 进行雇员信息查询的存储过程
           procedure list_emp
             is
             begin
               dbms_output.put_line('姓名                   职务                        工资');
               -- 这是游标的最最最简单写法 相当于定义了一个匿名游标 游标指向的数据范围
               -- 为select * from emp emp_rec就相当于是用来读取游标信息的rowtype类型变量
               for emp_rec in(select * from emp)
                 loop
                   dbms_output.put_line(emp_rec.ename||'                '||emp_rec.job||'         '||emp_rec.sal);
                   end loop;
             end list_emp;  
          -- 插入雇员的存储过程
          procedure insert_emp(p_empno number,p_ename varchar2,p_job varchar2,p_sal number)
           is
          begin
            -- 这是我们后续编写的存储函数 用来判断对应雇员的编号的数据是否存在的 
            -- 先把调用写好
            if NOT EXIST_EMP(p_empno) then
              insert into emp(empno,ename,job,sal) values(p_empno,p_ename,p_job,p_sal);
              commit;
              -- 插入数据成功之后 我们原本记录的雇员总人数此时应该+1 
              v_emp_count:=v_emp_count+1;
              v_message:='雇员'||p_empno||'已经被成功插入';
              else 
               v_message:='雇员'||p_empno||'已经存在,数据插入失败!';
               end if;
               show_message;   
               -- 异常处理
               exception when others then 
                 v_message:='发生错误,雇员'||p_empno||'插入失败,请检查参数!';
                 show_message;
            end insert_emp;
            --  删除雇员的过程
          procedure delete_emp(p_empno number)
            is
            begin
              -- 在删除之前 先判断被删除的雇员是否存在
              if exist_emp(p_empno) then 
                delete from emp where empno=p_empno;
                commit;
                -- 由于数据被删除了 表记录总数应该减一
                v_emp_count:=v_emp_count-1;
                v_message:='雇员'||p_empno||'已经被删除!';
                else
                  v_message:='雇员'||p_empno||'不存在,无法被删除!';
                  end if;
                  show_message;
                  exception when others then 
                    v_message:='雇员'||p_empno||'删除失败,请检查参数!';
                    show_message;
              end delete_emp;  
          -- 修改雇员工资
          procedure change_emp_sal(p_empno number,p_sal number)
            is
            begin
            -- 对工资是否超出范围进行判断
            if(p_sal>v_max_sal or p_sal<v_min_sal) then
            v_message:='工资超出修改范围';
            elsif not exist_emp(p_empno) then -- 如果员工不存在 也不可以修改  
            v_message:='雇员'||p_empno||'不存在,不可以修改工资!';
            else 
              update emp set sal=p_sal where empno=p_empno;
              commit;
              v_message:='雇员'||p_empno||'工资已经修改成功!';
              end if;
              show_message;
              exception when others then 
                v_message:='雇员'||p_empno||'修改异常,请检查参数!';
                show_message;
              end change_emp_sal;  
          -- 显示信息过程
          procedure show_message
           is
           begin
             dbms_output.put_line('提示信息:'||v_message);
             end show_message;     
          -- 判断雇员是否存在的函数
          function exist_emp(p_empno number) return boolean
            is
            v_num number;-- 局部变量 在包中的函数的内部定义的 只有在当前函数中可以使用
            begin
              select count(*) into v_num from emp where empno=p_empno;
              if v_num=1 then 
                return true;
                else 
                return false;
                end if;  
              end exist_emp;   
          end emp_pk;
          
          
          -- 初始化包的数据
          begin
            emp_pk.init(20000,0);
            end;
          -- 显示员工列表
          begin
            emp_pk.list_emp;
            end;  
            
          -- 插入雇员信息
          begin
            emp_pk.insert_emp(8888,'小周','STUDENT',50);
            end;  
          -- 通过全局变量v_emp_count查看雇员人数
          begin
            dbms_output.put_line(emp_pk.v_emp_count);
            end;  
          -- 删除记录
          begin
            emp_pk.delete_emp(8888);
              emp_pk.list_emp;
            end;  
          -- 修改雇员工资
          begin
            emp_pk.change_emp_sal(7900,50000);
            end;  
            
          -- 通过包的拥有者将包的执行权限赋予给指定用户 
          grant execute on emp_pk to cll;
        
      • v_emp_count 公有变量 定义在包头中 是可以直接被外部通过包名.变量名直接访问的

      • v_max_sal 和 v_min_sal 定义在包体内 不能被外界访问的 只能通过

      • 包内部的过程或者函数来进行访问和修改

      • 同样 exist_emp和show_message 是私有过程 没有在包头中定义 就不可以通过包直接访问

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值