Fork me on GitHub

Oracle数据库学习笔记(四)

Oracle 数据库

Oracle是一个强大的关系型数据库产品,功能丰富,产品安全性有保障,是Oracle公司的主打产品

本系列笔记可能会遇到的专业词汇有:

  • DB, DataBase, 数据库
  • DBMS,DataBase Management System, 数据库管理系统
  • RDBMS,Relationship DataBase Management System, 关系型数据库管理系统
  • SQL, Standard Query Language, 标准化查询语言

本系列学习笔记包含如下的课程内容:

  • 数据库的基本概念
  • DDL 命令
    • Create
    • ALTER
    • DROP
  • DML 命令
    • insert
    • update
    • delete
  • DQL 命令
    • select 命令
    • 关联查询
    • 分组查询
    • 子查询
  • DCL 命令
  • DTL 命令
    • commit
    • rollback
    • savepoint
  • 数据字典
  • 数据库对象
    • 序列
    • 索引
    • 视图
    • 同义词

##关联查询

  • 它是用来在多表查询的一种机制,Oracle支持 内联、左外联、右外联、全联查询。默认是采用内联。
  • 早期语法:

    select [distinct] [alias.]列名[,列名]|*
    from 表名1 alias[,表名2 alias][,表名3 alias]…
    [where 子句]
    [order by 子句]

    可以看出来,它是采用
    ‘,’号隔开多表,利用where 指定关联条件。如:

    1
    2
    3
    -- 查询员工名、工资以及所在的部门名称。
    select e.first_name,e.salary, d.name
    from s_emp e, s_dept d

    这条查询语句由于没有指定“关联条件”,所以产生了迪卡尔积,
    迪卡尔积会造成资源的浪费,我们在查询中是要避免的。改为如下:

    1
    2
    3
    4
    -- 表之间的关联关系,绝大多数都是主外键关系
    select e.first_name,e.salary, d.name
    from s_emp e, s_dept d
    WHERE e.dept_id = d.id
  • 新语法:

    select [distinct] [alias.]列名[,列名]|*
    from 表名1 alias [inner|[left|right|full outer]] join 表名2 alias
    on 关联条件
    [where 子句]
    [order by 子句]

    上面示例利用新语法改写后:

    1
    2
    3
    select e.first_name,e.salary, d.name
    from s_emp e join s_dept d
    on e.dept_id = d.id;
  • 练习

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    -- 找出Sales 部门的所有员工
    select e.* from s_emp e join s_dept d
    on e.dept_id = d.id where d.name = 'Sales';
    -- 找出在 Asia 地区工作的员工
    select e.* from s_emp e join s_dept d on e.dept_id = d.id
    join s_region r on d.region_id = r.id
    where r.name = 'Asia';
    -- 找出客户'Hamada Sport' 的所有订单号、费用、下订日期
    select o.id,o.total,o.date_ordered from s_customer c join s_ord o
    on c.id = o.customer_id
    where c.name = 'Hamada Sport';
    -- 找出所有在'Asia'客户的信息
    select c.* from s_customer c join s_region r
    on c.region_id = r.id
    where r.name = 'Asia';
    --- 查询出客户名及它的订单号,总费用
    select c.name, o.id, o.total
    from s_customer c
    left join
    s_ord o on c.id = o.customer_id
    order by c.name;
     
    select c.name, o.id, o.total
    from s_ord o
    right join
    s_customer c
    on c.id = o.customer_id
    order by c.name;
    --- 查询订单号,订单费用以及订单所对应的客户名
    select o.id, o.total, c.name
    from s_ord o
    left join
    s_customer c
    on o.customer_id = c.id;
  • 我们的关联查询分为2大类:

    1.内联查询

    [inner] join

    内联图片

    2.外联查询

    a.左外联 以关联的左边为准,即使右边没有与之匹配的记录,
    则左边的记录也要出现在结果集中,右边全部以NULL值显示。
    left [outer] join

    左外联图片

    b.右外联
    以关联的右边为准,即使左边没有与之匹配的记录,
    则右边的记录也要出现在结果集中,左边全部以NULL值显示。
    right [outer] join

    右外联图片

    c.全联
    full [outer] join

    全外联图片

    d.交叉联 返回连接两张的笛卡尔积
    cross [outer] join

  • 自关联查询:同一张表自己关联自己,如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    -- 查询出所有的员工名以及员工的上司名
    select e.first_name "员工名",m.first_name "上司名"
    from s_emp e join s_emp m
    -- 关联条件
    on e.manager_id = m.id
     
    这个语句只查询出了24行结果,少了 Carmen,因为Carmen员工的manager_id
    是空值。改为:
    select e.first_name "员工名",m.first_name "上司名"
    from s_emp e left join s_emp m
    -- 关联条件
    on e.manager_id = m.id
    注意:关联的条件不一定总是做等值比较的。
     
    -- 找出Womansport所购买的订单信息(订单号,费用,支付方式)
    select o.id, o.total, o.payment_type, c.name from s_ord o join s_customer c on o.customer_id = c.id
    where c.name = 'Womansport';
    -- 找出Operations部门工作的员工名,工资,并且按照工资降序排列
    select e.first_name, e.salary from s_emp e join
    s_dept d on e.dept_id = d.id where d.name = 'Operations' order by 2 desc;

##分组查询

  • 所谓分组,就是看待数据的”角度”不同。也就是把某类值相同的看做一组。
    语法:

    SELECT [DISTINCT] [alias.][列名 列别名[,列名 列别名]|*]
    FROM 表名 [alias] [inner|outer join]
    [表名[alias] ON 关联条件]
    [WHERE 子句]
    [GROUP BY 子句]
    [HAVING 子句]
    [Order by 子句]

  • 常用的分组函数

    函数名称|作用
    -|-
    SUM([distinct] 列|表达式|值)|求和,会自动忽略null值
    AVG([distinct] 列|表达式|值)|求平均值,自动忽略null值
    MAX(列|表达式|值)|求最大值
    MIN(列|表达式|值)|求最小值
    COUNT([distinct] 列|*)|求记录数,包含重复值和null值

  • 练习

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    -- 找出员工的最高、最低、平均、以及工资总和
    select max(salary) 最高工资, min(salary) 最低工资,
    avg(salary) 平均工资, sum(salary) 工资总和 from
    s_emp;
     
    -- 找出各部门员工的最高、最低、平均、以及工资总和
    select max(salary) 最高工资, min(salary) 最低工资,
    avg(salary) 平均工资, sum(salary) 工资总和 from
    s_emp group by dept_id;
     
    --- 找出各41,42,50部门员工的最高、最低、平均、以及工资总和
    select dept_id,max(salary) 最高工资, min(salary) 最低工资, avg(salary) 平均工资, sum(salary) 工资总和 from s_emp where dept_id in (41,42,50) group by dept_id;
     
    --- 找出各41,42,50部门员工的最高、最低、平均、以及工资总和且该部门的最高工资大于2000
    select dept_id,max(salary) 最高工资,
    min(salary) 最低工资, avg(salary) 平均工资,
    sum(salary) 工资总和 from s_emp
    where dept_id in (41,42,50) group by dept_id
    having max(salary) > 2000;
     
    --- 找出各41,42,50部门员工的最高、最低、平均、以及工资总和以及部门名称且该部门的最高工资大于2000
    select e.dept_id,d.name,max(salary) 最高工资,
    min(salary) 最低工资, avg(salary) 平均工资,
    sum(salary) 工资总和 from s_emp e join s_dept d
    on e.dept_id = d.id
    where dept_id in (41,42,50) group by dept_id,d.name
    having max(salary) > 2000;
  • 注意:

    只有出现在group by 后面的列(即用来做为分组条件的列),才有资格
    写在SELECT的后面,除非使用组函数进行修饰。

    WHERE 子句中不可以使用组函数,它是对数据分组之前进行过滤。

    HAVING 子句中可以使用组函数,它是对数据分组之后进行过滤。

    GROUP BY 子句后面的字段不能使用*, 别名, 列的序号

  • 如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    统计工资超过1100元以上的员工,各个职称各有多少人。
    select title,count(e.*)
    from s_emp e where salary > 1100
    group by title;
     
    -- 统计共计多少个员工
    select count(*) from s_emp;
     
    -- 统计共计多少个职称[不能重复]
    select count(distinct title) from s_emp;
     
    -- 找出订单数量超过(含)2个的客户
    select c.id,c.name,count(*) "订单数"
    from s_customer c join s_ord o on c.id = o.customer_id
    group by c.id,c.name
    having count(*) >= 2;
  • 练习:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    -- 找出超过(含)4个员工的部门id及部门名称
    select d.id,d.name, count(*) from s_dept d
    join s_emp e on e.dept_id = d.id group by
    d.name,d.id having count(*) >= 4;
     
    -- 找出订单总费用超过10000元的客户
    select c.name from s_customer c join s_ord o on c.id = o.customer_id group by c.id,c.name having sum(o.total) > 10000;
     
    -- 统计各区域的客户数量,按它的降序排序
    select r.name 区域, count(*) 客户数量 from s_customer c join s_region r on r.id = c.region_id
    group by r.id, r.name order by 2 desc;
     
    select count(*) from s_customer group by region_id
    order by 1 desc;
    -- 统计各经理的所直接管理的员工数
    select e.manager_id, m.first_name, count(*)
    from s_emp e join s_emp m on e.manager_id = m.id
    group by e.manager_id, m.first_name;
     
    select m.first_name 上司名字, count(*) 直接下属数量
    from s_emp e join s_emp m on e.manager_id = m.id
    group by m.id, m.first_name;
     
    -- 查询职位为Warehouse Manager直接管理的下属数量
    select m.title, m.first_name 上司名字, count(*) 直接下属数量
    -- 分组前筛查
    from s_emp e join s_emp m
    on e.manager_id = m.id
    where m.title = 'Warehouse Manager'
    group by m.id, m.first_name, m.title;
     
    -- 分组后筛查
    select m.title, m.first_name 上司名字, count(*) 直接下属数量
    from s_emp e join s_emp m
    on e.manager_id = m.id
    group by m.id, m.first_name, m.title
    having m.title = 'Warehouse Manager';
     
    -- 统计订单中各种支付类型的总费用
    select payment_type, sum(total) from s_ord group by payment_type;

##子查询

  • 定义:就是指查询中嵌套查询。
    子查询必须用()括起来,子查询可以出现在很多位置,比如: 当列、当表、当条件等
  • 子查询的本质:
    a.内敛视图
    b.把子查询的结果作为外部查询的条件

  • 语法:

    SELECT (子查询)
    FROM (子查询)
    WHERE (子查询)
    GROUP BY 子句
    HAVING (子查询)
    ORDER BY 子句

  • 举例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    -- 找出与Ben同一个部门的员工
    select e1.first_name from s_emp e1 join
    s_emp e2 on e1.dept_id = e2.dept_id and
    e2.first_name = 'Ben' where e1.first_name <> 'Ben';
     
    step1. 找出Ben所在的部门号
    select dept_id from s_emp where first_name = 'Ben';
     
    step2. 再根据上面的查询结果做为第二个查询的条件
    select first_name from s_emp where dept_id = (
    -- 子查询
    select dept_id from s_emp where first_name = 'Ben'
    )
    and first_name <> 'Ben';
     
    --- 查询出客户名,电话号码以及订单数
    -- 使用关联查询
    select c.name, c.phone, count(o.id) from s_customer c
    left join s_ord o
    on c.id = o.customer_id
    group by c.id, c.name, c.phone
    order by c.id;
     
    -- 无关子查询
    select c.name, c.phone, nvl(r.rtotal, 0) from
    s_customer c left join
    (
    select customer_id, count(*) rtotal from s_ord group by customer_id
    ) r on c.id = r.customer_id
    order by c.id;
     
    -- 相关子查询
    select c.name, c.phone, (
    select count(*) from s_ord o where o.customer_id = c.id
    ) from s_customer_id c
    order by c.id;
  • 子查询分类:

  1. 无关子查询
    是指子查询中没有使用外部查询所定义的变量/别名,而且内查询是可以单独运行的。

  2. 相关子查询
    是指子查询中要使用外部查询所定义的变量/别名,内查询不能单独运行。

  • 子查询的特点:
  1. 子查询很灵活,可以解决很多其他查询方式不能解决的问题
  2. 子查询效率很低,其中相关子查询效率最低
  3. 子查询嵌套的层数越多,效率越低
  4. 查询效率:关联查询 > 无相关子查询 > 相关子查询
  • 练习:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    -- 查询出客户名及他拥有的订单数(使用子查询)
    -- 无关子查询
    select c.name, nvl(r.rtotal, 0) from
    s_customer c left join
    (
    select customer_id, count(*) rtotal from s_ord group by customer_id
    ) r on c.id = r.customer_id order by c.id;
     
    -- 相关子查询
    select c.name, (
    select count(*) from s_ord o where o.customer_id = c.id
    ) from s_customer_id c order by c.id;
     
    -- 查询出部门名及此部门的员工数。(使用子查询)
    -- 无关子查询
    select d.name, r.ecount from s_dept d left join
    (
    -- 按部门分组,查询出每个部门的人数
    select dept_id, count(*) ecount from s_emp
    group by dept_id
    ) r on d.id = r.dept_id;
     
    -- 相关子查询
    select d.name,
    (
    select count(*) from s_emp e
    where e.dept_id = d.id
    ) from s_dept d order by d.id;
     
    -- 查询出工资超过公司平均工资的员工.
    第一步, 查询公司的平均工资
    select avg(salary) from s_emp;
    第二步, 查询工资大于第一步查询出的平均工资的员工信息
    select * from s_emp where salary > 平均工资
    第三步, 合并查询语句
    select * from s_emp where salary >
    (select avg(salary) from s_emp);
     
    -- 查询部门平均工资超过公司平均工资的部门
    第一步,查询公司的平均工资
    select avg(salary) from s_emp;
    第二步,查询部门的平均工资
    select avg(salary) from s_emp group by dept_id;
    第三步,查询出部门平均工资大于公司平均工资的部门ID
    select e1.dept_id, avg(salary) from s_emp e1 group by e1.dept_id having avg(salary) >
    (select avg(salary) from s_emp);
    第四步,查询出相关部门
    select distinct * from s_dept d where d.id in
    (
    select e1.dept_id from s_emp e1 group by e1.dept_id having avg(salary) >
    (select avg(salary) from s_emp)
    );
     
    select d.name from s_dept d join
    (
    select e1.dept_id did, avg(salary) from s_emp e1 group by e1.dept_id having avg(salary) >
    (select avg(salary) from s_emp)
    ) r on d.id = r.did;
     
    --- 找出各个部门中大于他所在部门平均工资的员工名和工资
    第一步, 查询部门的平均工资
    select avg(salary) from s_emp group by
    dept_id;
    第二步,
    select e.first_name, e.salary from s_emp e join
    (
    select dept_id, avg(salary) vsal from s_emp group by dept_id
    ) r on e.salary > r.vsal and e.dept_id = r.dept_id;
     
    -- 相关子查询
    select first_name, salary from s_emp e1 where salary >
    (
    select avg(salary) from s_emp e2 where e2.dept_id = e1.dept_id
    )
     
    -- 找出职称相同员工
    分析:首先查询出根据职称进行分组,数量大于2的职称.第二步查询出职称为第一步查询的结果的员工即为满足条件的员工
    第一步,
    select title from s_emp group by title having count(*) >= 2;
    第二步,
    select first_name, title from s_emp where title in
    (
    select title from s_emp group by title having count(*) >= 2
    );
  • 把子查询当做“表”来看,那么这个”表”就是一个内联视图[inline view]。如:

    1
    2
    3
    select * from (
    select id,first_name,start_date,salary from s_emp where dept_id=41
    ) v_emp;

要理解为什么要使用这种子查询,先来看一个伪例:rownum
它总是从1开始,依次递增,绝不会产生间隔。如:

1
2
3
4
select d.*,rownum from s_dept d where rownum = 1; // 正确
select d.*,rownum from s_dept d where rownum = 5; // 错误
select d.*,rownum from s_dept d where rownum > 5; // 错误
select d.*,rownum from s_dept d where rownum < 5; // 正确
  • rownum 的作用
    1.TopN问题
    解题思路:
    a).利用子查询先按要求的列做排序.
    b).再利用外部查询以及rownum 来过滤出 TopN记录
  • 练习:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    -- 查询出工资排名前三的员工
    select * from
    (select first_name, salary from s_emp order by salary desc)
    where rownum <= 3;
     
    -- 查询出41部门工资最高的员工
    select * from
    (
    select first_name, salary from s_emp where
    dept_id = 41 order by salary desc
    )
    where rownum = 1;
     
    -- 查询出拥有订单数排名前3的客户
    select * from
    (
    select c.name, count(*) from s_customer c
    join s_ord o on o.customer_id = c.id group
    by c.id, c.name order by 2 desc
    )
    where rownum <= 3;

2.分页查询问题

  • 通项公式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    select * from
    (
    select inner_.*, rownum rn_ from
    (
    -- 核心业务语句
    ) inner_
    where rownum <= endValue
    ) outter_
    where outter_.rn_ >= stratValue;
  • 练习:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    -- 查询出工资排名第二和第三的员工
    select * from
    (
    select inner_.*, rownum rn_ from
    (
    select first_name, salary
    from s_emp order by salary desc
    ) inner_
    where rownum <= 3
    )
    outter_
    where outter_.rn_ >= 2;
     
    -- 查询出员工表中第6到第10的数据。
    select * from
    (
    select v1.*, rownum rm_ from
    (
    select * from s_emp
    ) v1
    where rownum <= 10
    ) v2
    where v2.rm >= 6;
     
    -- 查询出在'Asia'地区工作的员工的第二行至第四行记录。
    select * from
    (
    select v1.*, rownum rm_ from
    (
    select * from s_emp e join s_dept d on
    e.dept_id = d.id join s_region r on
    d.region_id = r.id where r.name = 'Asia'
    ) v1
    where rownum <= 4
    ) v2
    where v2.rm >= 2;
  • 关于子查询的集合操作

    关键字|作用
    -|-
    UNION|返回两个子查询的并集,不含重复记录
    UNION ALL|返回两个子查询的并集,包含复复记录
    INTERSECT|返回交集
    MINUS|两个子查询相减

exists运算符

  • exists用来判断子查询是否有结果,如果有,则返回true;否则,返回false.如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    -- 找出与Ben同部门的员工
    select * from s_emp e1 where exists
    (
    select 1 from s_emp e2 where
    e2.dept_id = e1.dept_id and
    e2.first_name = 'Ben'
    ) and e1.first_name <> 'Ben';
     
    -- 找出各部门工资排名前2名的员工
    分析: 存在这样的员工,与我同一部门并且比我工资高的员工不超过1个
    select dept_id,first_name,salary from s_emp e1
    where exists (
    select 1 from s_emp e2
    where e1.dept_id = e2.dept_id
    and e1.salary < e2.salary
    having count(*) <= 1
    )
    order by dept_id,salary desc;
     
    select dept_id,first_name,salary from s_emp e1
    where not exists (
    select 1 from s_emp e2
    where e1.dept_id = e2.dept_id
    and e1.salary < e2.salary
    having count(*) > 1
    )
    order by dept_id,salary desc;
     
    -- 找出各个部门中工资最高的员工
    select dept_id,first_name,salary from s_emp e1
    where not exists (
    select 1 from s_emp e2
    where e1.dept_id = e2.dept_id
    and e1.salary < e2.salary
    ) order by e1.dept_id;
     
    select dept_id,first_name,
    salary from s_emp e1
    where not exists (
    select 1 from s_emp e2
    where e1.dept_id = e2.dept_id
    and e1.salary < e2.salary
    having count(*) >= 1
    )
    order by dept_id,salary desc; // 等同于上面, 多加了个条件
     
    select dept_id,first_name,
    salary from s_emp e1
    where exists (
    select 1 from s_emp e2
    where e1.dept_id = e2.dept_id
    and e1.salary < e2.salary
    having count(*) = 0
    )
    order by dept_id,salary desc;
     
    select * from s_emp e1 join (
    select dept_id, max(salary) maxsal from s_emp group by dept_id) r
    on r.dept_id = e1.dept_id and e1.salary = r.maxsal;

其他运算符

  • any运算符

    1
    2
    3
    4
    5
    6
    7
    8
    -- 找出与41部门任意一个人的工资相同的其他部门的员工
    select * from s_emp where salary = any (
    select salary from s_emp where dept_id = 41
    ) and dept_id <> 41;
     
    select * from s_emp where salary in (
    select salary from s_emp where dept_id = 41
    ) and dept_id <> 41;
  • all运算符

    1
    2
    3
    4
    5
    6
    7
    -- 找出工资比41部门所有人工资都高的员工
    select * from s_emp where salary > all (
    select salary from s_emp where dept_id = 41
    );
     
    select * from s_emp where salary > (
    select max(salary) from s_emp where dept_id = 41);