RaymondHuang
RaymondHuang
发布于 2023-10-21 / 33 阅读
0
0

Chapter 3 —— SQL语言(3)

多表联合查询

· 我们知道,查询的时候的同一个条件不能重叠,比如下面这一句就是错的,C#的查询条件出现了两次。

SELECT DISTINCT `S#` FROM `SC` WHERE `C#`="001" AND `C#`="002";

· 但是我们难免会遇到必须要使用复合条件的情况,这样我们就自然而然的想到之前学过的表连接了。

· 在SQL中,表连接的语法如下:

SELECT `列名` [, `列名2`,...]
FROM `表名1`, `表名2`, ...
WHERE 检索条件;

等值连接

· 等值连接是比较常用的连接方式,我们用一个例子来理解一下。

· 有以下三张表,需要查询每个学生及其选修课的情况:

SELECT `Student`.*, `SC`.*
FROM `Student`, `SC`
WHERE `Student`.`S#` = `SC`.`S#`;

· 需要注意,因为Student和SC表里面都有S#这一列,因此要带上表名来表示是哪个表里的S#

· 其实,第二行就相当于进行笛卡尔积,第三行就是对笛卡尔积的结果进行筛选,因此必须要有连接条件。

· 上面的查询的结果是这样的:

· 可以看到出现了两列S#,如果我只想保留一列,那就在查询结果上改一改即可:

· 如果不写查询条件的话,就会直接出笛卡尔积:

SELECT *
FROM `Student`, `SC`;

· 当我们需要自己和自己连接的时候,就涉及到一个改名操作了。

· 我们拿一开始的问题做例子,在下面的表中筛选出即学过001也学过002课程的学生。

SELECT `SC`.`C#`
FROM `SC`, `SC` AS `SC1` # 改名,避免歧义
WHERE `SC`.`S#` = `SC1`.`S#`
AND `SC`.`C#`="001"
AND `SC1`.`C#`="002";

· 等值连接还能写成更容易阅读的形式:

SELECT `SC`.`C#`
FROM `SC`
INNER JOIN `SC` AS `SC1` # 这种写法能让人一看就懂
ON `SC`.`S#` = `SC1`.`S#`
WHERE `SC`.`C#`="001" AND `SC1`.`C#`="002";

· 我们来看一个例子,有一张Employee表,请找出工资比自己的经理多的人:

SELECT `name` as `Employee` # 这个改名是因为输出规定
FROM Employee
INNER JOIN Employee as `e` # 这个改名是为了避免歧义
ON Employee.`id` = e.`manaerId`
WHERE Employee.`salary` > e.`salary`;

· 除了等值连接之外,还支持左外连接右外连接,但是不支持全外连接(后面用其他方法实现)。

· 我们来看一个例子,有一张Employee表和Bonus表,请找出奖金小于1000元的人:

SELECT `name`, `bonus`
FROM `Employee`
LEFT JOIN `Bonus`
ON `Employee`.`empID` = `Bonus`.`empID`
WHERE `bonus` IS NULL OR `bonus` < 1000; # 没奖金也算!

· 注意我们要使用左连接,因为要保留所有的人!

· 最后看一个例子:

SELECT `Peoject`.`project_id` AVG(`experience_years`) AS `average_years`
FROM `Project`
INNER JOIN `Employee`
ON `Project`.`employee_id` = `Employee`.`employee_id`
GROUP BY `Project`.`project_id`;

嵌套查询

· 首先,我们把一个查询语句称为一个查询块

SELECT 列名1 [[, 列名2]...]
FROM 表名
[WHERE 检索条件];

· 如果把一个查询块放到WHERE子句HAVING子句里面,这样的查询就被称为嵌套查询:

SELECT `Sname` # 外层查询,父查询
FROM `Student`
WHERE `S#` IN (
    SELECT `S#` # 内层查询,子查询
    FROM `SC`
    WHERE `C#` = "002"
);

· 注意,子查询要使用括号括起来;两个查询要用IN来连接,子查询的结果要和父查询的筛选条件的目标在同一个域中

· 举个例子,有一张Student表,现在要找出和李勇在同一个系的同学:

SELECT *
FROM `Student`
WHERE `Sdept` IN (
    SELECT `Sdept`
    FROM `Student`
    WHERE `Sname` = "李勇"
);

· 再看一个例子,在下面三张表中查询选修了“数据库”的学生的学号姓名:

SELECT `Student`.`S#`, `Student`.`Sname`
FROM `Student`
WHERE `S#` IN (
    SELECT `SC`.`S#`
    FROM `SC`
    WHERE `SC`.`C#` IN (
        SELECT `Course`.`C#`
        FROM `Course`
        WHERE `Cname` = "数据库"
    )
);

· 注意,当子查询返回的值确定是一个的时候,IN可以更换为等号(=),如刚刚到例子可以改为:

SELECT *
FROM `Student`
WHERE `Sdept` = ( # 这里的IN改为等号
    SELECT `Sdept`
    FROM `Student`
    WHERE `Sname` = "李勇"
);

· 当然,嵌套查询也可以使用比较运算符,但是要注意子查询必须要返回单值,且对象在同一个域中!

· 也可以使用ANYALL谓词来修饰子查询,ANY指的是子查询结果中的任意一个值,ALL指的是子查询结果中的所有值

· 举个例子,在Student表中查询非IS系中比IS中任意一个学生年龄小的学生姓名和年龄:

SELECT `Sname`, `Sage`
FROM `Student`
WHERE `Sage` < ANY(
    SELECT `Sage`
    FROM `Student`
    WEHRE `Sdept` = "IS"
)
AND `Sdept` != "IS";

· 在看一个例子,找出年龄最小的学生的姓名和年龄:

SELECT `Sname`, `Sage`
FROM `Student`
WHERE `Sage` <= ALL( # 注意是用小于等于,不然就筛不出任何一个结果了
    SELECT `Sage`
    FROM `Student`
);
# 当然还有另一种写法
SELECT `Sname`, `Sage`
FROM `Student`
WHERE `Sage` <= (
    SELECT MIN(`Sage`)
    FROM `Student`
);

基于派生表的查询

· 我们知道了嵌套查询可以在WHERE或HAVING中嵌套,但其实还可以在FROM中嵌套。

· 用户自己创建的表叫做基本表,SELECT的结果叫做派生表

· 有时候我们可能有这个想法,先把一个表筛选出来作为中间变量,再把它用到一个其他查询中,这个时候派生表就派上用场了。

· 举个例子,在SC表中找出每个学生自己最高分的课程号:

SELECT `S#`, `C#`
FROM SC, (
    SELECT `S#`, MAX(`Score`)
    FROM `SC`
    GROUP BY `S#`
) AS `m`(`m_S#`, `m_score`) # 给派生表取个别名m,里面的两列分别取别名m_S#和m_score
WHERE `S#` = `m`.`m_S#`
AND `Score` = `m`.`m_score`;

· 我们来分析一下这个查询。

· 首先子查询,查询到了每个学生自己的最高分是多少分;然后给查询出来的表取一个别名;然后再通过等值连接连接原表和派生表,通过分数确定最高分的课程。

集合查询

· 当然,我们也可以对查询结果进行求交集、并集和做差。

· 我们使用UNION来把两个查询结果进行合并,系统会自动去掉重复元组;使用UNION ALL可以保留重复元组

· 看个简单的例子,筛选出下表中既选修了001也选修了002课程的同学:

SELECT `S#`
FROM `SC`
WHERE `C#` = "001"
UNION
SELECT `S#`
FROM `SC`
WHERE `C#` = "002";

· 同时,我们也可以使用UNION来实现全外连接

SELECT *
FROM `Student`
LEFT JOIN `SC` # 先左外连接
ON `Student`.`S#` = `SC`.`S#`
UNION
SELECT *
FROM `Student`
RIGHT JOIN `SC` # 再右外连接
ON `Student`.`S#` = `SC`.`S#`;

· 我们使用INTERSECT将多个查询结果取交集,系统会自动去掉重复元组;使用INTERSECT ALL保留重复元组

· 继续上面那张表,这次要选出学了001又学了002课程的学生:

SELECT `S#`
FROM `SC`
WHERE `C#` = "001"
INTERSECT
SELECT `S#`
FROM `SC`
WHERE `C#` = "002";

· 真是太优雅了。

· 我们使用EXCEPT来对查询结果求差,我们通过一个例子来理解。

· 还是刚刚的表,如果我要选出不选002课程的同学,一个很直观的想法是:

SELECT `S#`
FROM `SC`
WHERE `C#` != "002";

· 但其实这样子会把98040202这个人也筛出来了,因为他选了002和003,即使002被筛出去了,003也被留下来了。

· 这个时候,我们就可以使用求差来避免这个问题:

SELECT DISTINCT `S#` # 先把所有人筛出来,去重
FROM `SC`
EXCEPT # 然后去掉
SELECT `S#` # 选过002课程的人
FROM `SC`
WHERE `C#` = "002";


评论