2020:0617 --- Oracle(二)

258 阅读17分钟

今日内容

1. Oracle体系结构
2.基础语法
    中文乱码解决

1. Oracle的体系结构

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

    偏向于硬件向的说法

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

    数据库和实例都可以指Oracle数据库。
    一台电脑上一个Oracle数据库可以跑多个实例,但一般情况
一个Oracle数据库只跑一个实例。
    
    偏向于进程说法。

3. 用户
    用户是在实例下建立的。不同实例可以建相同名字的用户。
    
    Oracle中的用户,是管理表的基本单位。
    而MySql中管理表的基本单位是数据库database。
    
    如果项目用的是MySql,我们可以说当前数据库下有几张表。
    如果用的是Oracle,应该说XX用户下有几张表。
    
    我们用MYSQL做一个项目,会为该项目创建一个新的数据库;
    用Oracle做一个新的项目,只用为该项目创建一个新的用户。
        用户时Oracle中管理表的基本单位。
        
4. 表空间:是一个逻辑空间

5. 数据文件:实实在在的硬件空间
    数据文件是数据库的物理存储单位。数据库的数据是存储在表空间中的
,真正是在某一个或者多个数据文件中。而一个表空间可以由一个或多个
数据文件组成,一个数据文件只能属于一个表空间。一旦数据文件被加入到
某个表空间后,就不能删除这个文件,如果要删除某个数据文件,只能删除
其所属于的表空间才行。
    注: 表的数据,是由用户放入某一个表空间的,而这个表空间会随机
把这些表数据放到一个或者多个数据文件中
    由于oracle的数据库不是普通的概念,oracle是有用户和表空间对数据
进行管理和存放的。但是表不是有表空间去查询的,而是由用户去查的。因
为不同用户可以在同一个表空间建立同一个名字的表!这里区分就是用户了!

6. 完整体系结构

2. 创建表空间

1. 创建表空间
    ```
    --创建表空间
    create tablespace itheima
    datafile 'c:\itheima.dbf' --指定数据文件所在位置,是oracle安装的虚拟机位置
    size 100m                 --指定大小    
    autoextend on             --容量不够时,自动扩展
    next 10m;                 --每次扩展10m
    ```

2.删除刚才创建的表空间

    直接手动删除删不掉,因为这个表空间正在被我们使用。
    删除方法:
    ```
    --删除表空间
    drop tablespace itheima;
    ```
再手动删除就行了

3. 创建用户
    ```
    --创建用户
    create user itheima
    identified by itheima --密码
    default tablespace itheima; --指定出生的表空间
    ```

4. 还没有授权,此时还不能登录
    我们当前登陆的时system超级管理员用户,下面能看到很多表

    如果一个权限低的用户登录进来,肯定看不了这么多表。也就是不同用户
登录进来后能看到的表也不同。
    
    我们要先这个新建的用户授权,这样Oracle才知道你是个什么用户,给你
展示多少张表。

    oracle数据库中常用角色
        connect--连接角色,最基本角色
        resource--开发者角色
        dba--超级管理员
        
    为了方便,我们给itheima用户授权dba,注意给create session权限
    ```
    grant create session,dba to itheima;
    ```

切换到itheima1用户:    

2. Oracle中的数据类型

    1. Varchar, varchar2        
        描述一个字符
        常用的是varchar2
                                
    2. NUMBER                   
        NUMBER(n)表示一个整数,长度是n
        NUMVER(m,n)表示一个小数,总长度是m,小数是n,整数时m-1
    
    3. DATA                 
        表示日期类型,类似于MySql中的datetime精确到时分秒
    
    4. CLOB 
        大对象,表示大文本数据类型,可存4G
    
    5. BLOB
        大对象,表示二进制数据,可存4G
        
    6. char类型
        固定长度,char(6)就算你只存入一个字符,用6个字符长度存取。
        取出来的长度仍然是6
        

2.1 创建一个表
    ```
    ---创建一个表
    create table person(
           pid number(20),
           pname varchar2(10) 
    )
    ```
    
2.2 修改表
    1.添加一列
    ```
    alter table person add gender number(1);
    ```
    2.添加多列
    ```
    alter table person add (gender number(1), birthday date);
    ```
    
    3.修改列类型
    ```
    alter table person modify gender char(1);
    ```
    
    4.修改多列
    ```
    alter table person modify (gender char(1), pname varchar(20));
    ```
    
    5.修改列名称
    ```
    alter table person rename column gender to sex;
    ```
    
    6.删除一列
    ```
    alter table person drop column sex;
    ```

3.数据的增删改

    1.添加一条记录
        
        ```
        insert into person (pid, pname) values (2, '小红');
        ```
        
        1.2. 发现有中文乱码:

        1.3. Oracle增删改时一定要加上提交事务
        
        注意我们JAVA中的connection是默认自动提交事务的。
        如果我们用的是spring框架,事务是spring去管理的。事务会从dao
    层挪到业务层。
        而此时我们没有用任何代码,直接用的PL/SQL连接的Oracle。此时的
    事务是没有任何第三发工具类帮我们管理,所以我们只能手动提交。
        (就是还没用Spring)
    
        ```
        --- 手动提交
        commit;
        ```
        
    2. 修改一条记录
        ```
        update person set pname = '小马' where pid=1;
        commit;
        ```
    
    
    3. 三个删除
        1.删除表中全部记录
            ```
            delete from person;
            ```
        2.删除表结构
            ```
            drop table person;
            ```
        3.先删除表,再创建表。效果等同于删除表中全部记录
            ```
            truncate table person;
            ```
            在数据量大的情况下,尤其是表中带有索引的情况下,该操作效
        率很高,索引可以提高查询效率,但是会影响增删改效率。

4

    解决:中文乱码
        
        1.查看服务器端编码
        select userenv('language') from dual;
        我实际查到的结果为:AMERICAN_AMERICA.ZHS16GBK
        
        2.执行语句 select * from V$NLS_PARAMETERS
        查看第一行中PARAMETER项中为NLS_LANGUAGE对应的VALUE项
    中是否和第一步得到的值一样。
        如果不是,需要设置环境变量.否则PLSQL客户端使用的编码
    和服务器端编码不一致,插入中文时就会出现乱码。
    
        3.设置环境变量
        设置变量名:NLS_LANG,
        变量值:第1步查到的值AMERICAN_AMERICA.ZHS16GBK
        
        4.重新启动PLSQL,插入数据正常

5. 序列

    1.序列:默认从1开始,依次递增。主要用来给主键赋值使用。
      序列不真的属于任何一张表,但是可以逻辑上和表绑定。
      写法:
    ```
    create sequence s_person;
    ```
    
    2.  虽然序列从1开始,但是要先执行一次,才有当前值。
    ```
    select s_person.nextval from dual;
    select s_person.currval from dual;
    ```
    
    3. dual:虚表,只是为了补全语法,没有任何意义
    
    4. 所以今后,我们不要给主键赋值,用序列
    ```
    insert into person (pid, pname) values (s_person.nextval, '小妞');
    ```
    
    5. 注意序列无法回滚。
    
    6. 序列的可选属性---[]内是可写可不写的
        语法:CREATE SEQUENCE 序列名
        [INCREMENT BY n] ---- 每次递增几
        [START WITH n]   ---- 从几开始
        [{MAXVALUE/ MINVALUE n|NOMAXVALUE}]  --- 最大值最小值
        [{CYCLE|NOCYCLE}]   ---- 循环/不循环
        [{CACHE n|NOCACHE}]; ---- 缓存

6. scott用户

    用户名:scott
    密码:tiger
    
    1.解锁scott用户,要有超级管理员权限
        ```
        alter user scott account unlock;
        ```
        
    2. 解锁scott密码[此句也可以用来重置密码]
    
        ```
        alter user scott identified by tiger;
        ```
    3. 切换scott用户
    
    4. 我们重点掌握:EMP表

7. Oracle中的函数

7.1 单行函数

7.1.1 数值函数
    作用于一行,返回一个值
    1. 把小写的字符转换成大小的字符
    ```
    select upper('yes') from dual;
    ```
    2. 把大写字符变成小写字符
    ```
    select lower('YES') from dual;
    ```
    
    3. 首字母大写函数
    
    4.字符串连接函数
    
    5.字符串替换函数
    
    6.获取字符串长度函数
    
    7.round 
      四舍五入,第二个参数表示保留几位小数
      ```
      select round(26.18) from dual;
      select round(26.18,1) from dual;
      ```
      -1表示向前保留1位(整数位第一位舍入)
      ```
      select round(26.18,-1) from dual;
      ```
      
    8. trunc
       直接截掉(不考虑舍入)
       ```
       select trunc(56.16, 1) from dual;
       select trunc(56.16, -1) from dual;
       ```
       
    9. mod 求余数
    ```
    select mod(10,3) from dual;
    ```
7.1.2 日期函数
    1. 需求:查询出EMP表中所有员工入职距离现在几天
        ```
        select sysdate-e.hiredate from emp e
        ```
        sysdate:目前系统时间
        日期可以加减
    
    2. 算出明天此刻:明天这个时候的时间
        ```
        select sysdate+1 from dual;
        ```

        日期可以直接和数字加减。
    
    
    3. 查询emp表中,所有员工入职距离现在几月
        ```
        select months_between(sysdate, e.hiredate) from emp e; 
        ```

    4. 查询emp表中所有员工入职距离现在几年

    5. 查询emp表中所有员工入职距离现在几周
        ```
        select (sysdate-e.hiredate)/7 from emp e;
        ```

    6. 不想要小数
        ```
        select round((sysdate-e.hiredate)/7) from emp e;
        ```

    7.1. 转换函数  -- 日期格式转字符串
        ```
        select to_char(sysdate, 'yyyy-mm-dd hh:mi:ss') from dual;
        ```

        不要0
        ```
        select to_char(sysdate, 'fm yyyy-mm-dd hh:mi:ss') from dual;
        ```

        24小时计数法
        ```
        select to_char(sysdate, 'fm yyyy-mm-dd hh24:mi:ss') from dual;
        ```
    
    7.1. 转换函数  -- 字符串转日期格式
        
        ```
        select to_date(' 2020-6-17 12:45:13', 'yyyy-mm-dd hh24:mi:ss') from dual;
        ```

注意他的区别时:结果出来的一个date日期格式。

7.1.3 通用函数 nvl(e.comm, 0)
    1. 算出emp表中所有员工的年薪
    ```
    select e.sal*12 from emp e;
    ```

    2. 加上奖金
    ```
    select e.sal*12+e.conn from emp e;
    ```

    出现问题:有的员工没有奖金,存在null值。null和任意数字做运算,
              还是null。
              
    3. nvl(e.comm, 0) 
       
       不是null用原来的值,是null用0。
       
       ```
       select e.sal*12+ nvl(e.comm, 0) from emp e;
       ```

7.2 多行函数[聚合函数]

    作用于多行,返回一个值
    
    1.查询总数量
        select count(1) from emp; 
        等价于
        select count(empno) from emp;

        效果同:
        select count(*) from emp; 
        
    但是推荐写:
        select count(1) from emp; 
        
    
    2.
    ```
    select sum(sal) from emp;  --- 查询工资总和
    select max(sal) from emp;  --- 查询最高工资
    select min(sal) from emp;  --- 最大工资
    select avg(sal) from emp;  --- 平均工资
    ```
    
    多行函数一共就5个。

8. 条件表达式

1. 给员工表的员工起别名
```
select e.ename,
       case e.ename 
         when 'SMITH' then '大猫'
           when 'ALLEN' then '小狗'
             when 'WARD' then '小牛'
               else '无名'
                 end 
       
from emp e; 
```

    1.1 else可以省略
    ```
    select e.ename,
           case e.ename 
             when 'SMITH' then '大猫'
               when 'ALLEN' then '小狗'
                 when 'WARD' then '小牛'
                   --else '无名'
                     end 
           
    from emp e;

    ```

    1.2 
    
        ```
       case e.ename 
         when 'SMITH' then '大猫'
           when 'ALLEN' then '小狗'
             when 'WARD' then '小牛'
               --else '无名'
                 end 
        ```
    
    中间的内容没有逗号,查询的是一列的内容。

2. 判断emp表中员工工资,高于3000显示高收入,1500-3000中等,
   其余低收入

    ```
    select e.sal,
       case 
         when e.sal>3000 then '高收入'
           when e.sal>1500 then '中等收入'
             else '低收入'
               end
    from emp e;  
    ```
    
    注意:
        e.sal不能放在case后面,因为是范围判断,直接跟在case后面是等于判断
        
        不满足 when e.sal>3000 then '高收入'时,才会走到下面的
               when e.sal>1500 then '中等收入'
               所以中等收入不用谢小于3000的条件
               
上面两个案例是条件表达式的通用写法,mysql和oracle通用。

3. orcle专用的条件表达式
    每个字段都要加 ,
    ```
    select e.ename,
           decode(e.ename, 
              'SMITH',  '大猫',
                'ALLEN',  '小狗',
                  'WARD' , '小牛',
                    '无名')
                      
    from emp e;
    ```

    3.1 有一个问题:列名太长了
        给列加一个别名

        注意:别名不加引号,或者加双引号

    oralce中除了起别名,其他都用单引号。
    为了保证代码的可移植性强,条件表达式一般用通用的写法

9 分组查询

    1. 查询出每个部门的平均工资
        ```
        select e.deptno, avg(e.sal)
        from emp e
        group by e.deptno;
        ```
    
        注意:
        分组查询中,出现在group by后面的原始列,才能出现在select后面
    没有出现在group by后面的列,想在select后面,必须加上聚合函数。
        
        这样就是错的: e.ename原始列
        ```
        select e.deptno, avg(e.sal), e.ename 
        from emp e
        group by e.deptno;
        ```
    2.查询出平均工资高于2000的部门信息
        ```
        select e.deptno, avg(e.sal) 
        from emp e
        group by e.deptno 
        having avg(e.sal)>2000;
        ```
        
    3. 别名问题:所有条件都不能使用别名判断
    
        给平均工资取一个别名
        ```
        select e.deptno, avg(e.sal) asal 
        from emp e
        group by e.deptno 
        having avg(e.sal)>2000;
        ```

      注意:这样是错的

        所有条件都不能使用别名判断
    
        因为:先走的是条件having asal > 2000,所以此时别名根本识别不出来。
    

    4.查询出每个部门中工资高于800的员工的平均工资
        4.1 先得到所有工资大于800的员工部门信息和工资信息
        ```
        select e.deptno, e.sal
        form emp e
        where e.sal>800
        ```

        4.2 按部门分组
        每个部门都是一行,所以不能在显示单个员工的工资信息了,
    要显示一个部门的总体上的属性:平均工资。
        ```
        select   e.deptno, avg(e.sal)
        from emp e
        where sal > 800
        group by e.deptno;
        ```

        注意:
            1.where是过滤分组前的数据,
            2.having是过滤后的数据
            
    5.
        查询每个部门中工资高于800的员工的平均工资,其中平均工资大于2000的部门
        
        ```
        select   e.deptno, avg(e.sal)
        from emp e
        where sal > 800
        group by e.deptno
        having avg(e.sal)>2000;
        ```

10 多表查询的一些概念

    1.笛卡尔积
    ```
    select *
    from emp e, dept d;
    ```
    查询出来的数据量是:m*n。大部分是无用的数据
    
    2.等值连接---就是内连接的新写法。
      不是为了查询某一个表的全部符合条件的信息(用外连接)。
    ```
    select *
    from emp e, dept d
    where e.deptno=d.deptno;
    ```
    3.内连接
    ```
    select * 
    from emp e inner join dept d
    on e.deptno = d.deptno;
    ```
    效果同等值连接,推荐写等值连接。

10.1 查询出所有部门信息,以及部门下的员工信息【外连接】

    分析:
    如果存在部门没有员工也要查询出来,没有员工也是信息,也就是查询出
    完整的部门信息,外连接。

    1.部门表:

    2.员工表:

    3.为什么要用外连接:
    因为我要查询的结果是上面两张表中的其中一张的完整结构。
    
    4.左连接还是右连接:都行
      但是要明确:
      left join显示的是左侧的全部信息
      right join显示的是右侧的全部信息
      
    5. 写法
        ```
        select *
        from emp e right join dept d
        on e.deptno = d.deptno; 
        ```

10.2 要查询所有员工信息,以及员工所属的部门信息

    分析一下:
    因为每个员工都有部门,所以不会出现上面10.1中的有个部门没有员工的情况。
    
    ```
    select *
    from emp e left join dept d
    on e.deptno = d.deptno;
    ```

10.3 Oracle专用的外连接写法

    1.
    ```
    select *
    from emp e, dept d
    where e.deptno(+) = d.deptno;
    ```

    2.
    ```
    select *
    from emp e, dept d
    where e.deptno = d.deptno(+);
    ```

    为了可移植性考虑,建议永通用。

11. 多表查询中的一些新概念---自查询。

1. 案例:查询出员工姓名,员工领导姓名。

    分析一下员工表结构:
        7369员工的领导是7902
        7902员工的领导是7566
        7566员工的领导是7839
        所以员工的姓名和领导信息都在一张表里:自查询
        
        站在员工的角度分析这个问题这是一张:领导表。
        站在领导的角度分析这个问题这是一张:员工表。
        
        那么也就是这两张表进行联合查询(实际上一张表)
        
    我们可以这样区分两张表:
        领导表中的所有员工编号都是员工表中的员工领导编号。
        领导表.no = 员工表.领导编号
        
2.SQL代码

    e1(员工表)表中所有的领导都是e2(领导表)表中的员工
    ```
    select e1.ename, e2.empno 领导编号, e2.ename 领导姓名
    from emp e1, emp e2
    where e1.mgr = e2.empno;
    ```

3. 自连接
    自连接就是站在不同的角度把一张表看成多张表。
    

4. 案例:查询出员工姓名,员工部门名称,员工领导姓名,员工领导部门名称

    分析:
    1.先得到查询出员工姓名和对应的员工领导姓名。
    ```
        select e1.ename, e2.empno 领导编号, e2.ename 领导姓名
        from emp e1, emp e2
        where e1.mgr = e2.empno;
    ```

    2. 在得到这13个员工的部门名
    ```
    select e1.ename 员工姓名, d1.dname 员工所属部门, e2.ename 领导名
    from emp e1, emp e2, dept d1
    where  e1.mgr = e2.empno 
    and e1.deptno = d1.deptno;
    ```

    3. 再得到13个领导的部门名
    ```
    select e1.ename 员工姓名, d1.dname 员工所属部门, e2.ename 领导名, d2.dname 领导所属部门
    from emp e1, emp e2, dept d1, dept d2
    where  e1.mgr = e2.empno 
    and e1.deptno = d1.deptno
    and e2.deptno =d2.deptno;
    ```

4. 来看一种错误情况
```
    select e1.ename 员工姓名, d.dname 员工所属部门, e2.ename 领导名, d.dname 领导所属部门
    from emp e1, emp e2, dept d
    where  e1.mgr = e2.empno 
    and e1.deptno = d.deptno
    and e2.deptno =d.deptno;
```

分析一下:
    查询结果是11条,正确结果应该是13条。所以里面有过滤。
    
    我们来看条件:
        and e1.deptno = d.deptno
        and e2.deptno =d.deptno;
        那么显然这是一个并列且条件。
        
    意思是:员工和领导的部门编号要一样的。
    
    所以部门表也应该看成两张表来连接查询。

12 子查询

12.1 子查询返回一个值

    1.案例:查询出工资和scott一样的员工信息。
    
        1.先查询出SCOTT的工资(区分大小写的)
        ```
        select sal from emp where ename = 'SCOTT'
        ```
        2.查询出工资和scott一样的员工信息。
        ```
        select * from emp where sal = 
        (select sal from emp where ename = 'SCOTT')
        ```
        
    2. 写 = 时的隐患
    
        我们先查询出SCOTT的工资,如果一个公司又两个人都叫做SCOTT,那么第一步
    查询出来的是一个集合(多行)
        
        那么第二不再写 = 显然是错的,所以应该写in,更保险
        ```
        select * from emp where sal in 
        (select sal from emp where ename = 'SCOTT')
        ```    

12.2 子查询返回一个集合

    1.案例:查询出工资和10号部门任意员工一样的员工信息。
    
        1. 先查询出10号部门员工的工资集合
        ```
        select sal from emp where deptno = 10;
        ```
        
        2. 查询出工资和10号部门任意员工一样的员工信息。
        ```
        select * from emp where sal in
        (select sal from emp where deptno = 10);
        ```

12.3 子查询返回一张表

    1. 案例:查询出每个部门最低工资和最低工资员工姓名,和该员工所在部门名称
    
        分析:
            目前没有一张表直接有 部门最低工资信息,所以我们要先得到这张表。
        
        1. 先查询每个部门最低工资
        ```
        select deptno, min(sal)
        from emp
        group by deptno
        ```

        2. 再用第1步得到的每个部门的最低工资表和部门表联合查询,得到部门信息。
        ```
        select t.deptno, t.msal, d.dname
        from(select deptno, min(sal) msal
            from emp
            group by deptno) t, dept d
        where t.deptno = d.deptno
        ```

        3. 用第1步得到的每个部门的最低工资表和员工表表联合查询,得到员工信息。
        ```
        select t.deptno, t.msal, e.ename
        from(select deptno, min(sal) msal
            from emp
            group by deptno) t, emp e
        where t.msal = e.sal
        ```

        4. 3张表进行联合查询
        ```
        select t.deptno, t.msal, e.ename, d.dname
        from (
          select deptno, min(sal) msal
          from emp
          group by deptno) t, emp e, dept d 
        where t.deptno = e.deptno           
        and t.msal = e.sal                 
        and e.deptno = d.deptno  --- 将两张临时表进行联合查询
        ```

13 分页查询

1. rownum行号
    当我们做select操作的时候,没查询出一行记录,就会在该行上加上一个行号。
行号从1开始一次递增。
    只有select会出来rownum。
    rownum不能加表名:emp.rownum
   
   1.1 查看一下rownum 
    ```
    select rownum,e.* from emp e
    ```

    1.2 加上排序
    ```
    select rownum,e.* from emp e order by e.sal desc
    ```

        这个时候rownum是乱序的,是因为先执行的查询的同时rownum就出来了,
    然后再排序,显然顺序就会被打乱。
        所以排序会打乱rownum的顺序。
        
    
    1.3 解决办法
        先排序,在加rownum : 在将排序好的查询一遍
        
        ```
        select rownum, t.*
        from(
        select * from emp e order by e.sal desc
        ) t;
        ```
小结:如果rownum涉及到排序,但是还要使用rownum的话,可以再次查询排序好的临时       
      表来的到顺序的rownum。

2. emp表工资倒序排列后每页5条记录,查询第二页。

    ```
    select rownum, t.*
    from(
    select * from emp order by sal desc
    ) t where rownum<11 and rownum>5; 
    ```
    
    注意:
        where先于select执行,而rownum是从1开始的。
    那么就不可能同时满足rownum<11 and rownum>5这个条件。
        
        所以最后的查询结果是个空。
        
    结论:rownum不能写上大于一个正数。
    
    2.2 
        解决办法:先查询出rownum<11的数据,这个时候rownum就已经标注上去了。
        再对这个临时表加一个选择:rownum > 5。
        
        注意:必须是临时表中的rownum。
        
        ```
        select * from(
          select rownum rn, t.*
          from(
                select * from emp order by sal desc
          ) t where rownum<11
        )tt where rn>5;
        ```

    2.3 关于分页查询:这一部分时固定的