目录
什么是多表查询?
多表查询的基本语法
多表关系
测试数据准备
一对多
多对多
一对一
查询
内连接查询
外连接查询
自连接查询
联合查询 union, union all
子查询
标量子查询
列子查询
行子查询
表子查询
DISTINCT 去重
单列去重
多列去重
注意事项
练习
查询研发部所有的员工信息以及工资等级 三表查询讲解
为什么 AVG(e.salary) 不能直接与 e.* 一起使用?
查询低于本部门平均工资的员工信息
总结
什么是多表查询?
在关系型数据库中,数据通常分散在多个表中,而不是存储在单个表中。多表查询是指从一个以上的表中检索数据并将其组合以满足特定需求的操作。通过多表查询,您可以执行以下操作:
- 检索与多个表关联的数据。
- 在多个表之间建立关联,以便于数据分析。
- 聚合和计算多个表中的数据。
- 更新和删除多个表中的数据。
多表查询通常涉及使用 JOIN 子句将不同的表连接在一起,以创建一个包含所需数据的结果集。
多表查询的基本语法
在 MySQL 中,使用 JOIN
子句来执行多表查询。JOIN
子句用于将两个或多个表中的行组合在一起,以创建一个包含来自这些表的数据的结果集。基本的 JOIN
子句语法如下:
SELECT 列名
FROM 表1
JOIN 表2
ON 表1.列 = 表2.列;
其中:
SELECT
语句指定要检索的列。表1
和表2
是要连接的表。ON
子句指定连接条件,即哪些列应该匹配以创建连接。
多表关系
- 一对多(多对一)
- 多对多
- 一对一
测试数据准备
员工表 && 部门表
CREATE TABLE dept(
id INT PRIMARY KEY AUTO_INCREMENT COMMENT "主键",
name VARCHAR ( 50 ) NOT NULL COMMENT "年龄"
) COMMENT "部门表"
-- 插入数据
INSERT INTO dept(name) VALUES ("研发部"),("市场部"),("财务部"),("销售部"),("总裁办")
CREATE TABLE emp (
id INT AUTO_INCREMENT COMMENT 'ID' PRIMARY KEY,
name VARCHAR ( 50 ) NOT NULL COMMENT '姓名',
age INT COMMENT '年龄',
job VARCHAR ( 20 ) COMMENT '职位',
salary INT COMMENT '薪资',
entrydate DATE COMMENT '入职时间',
managerid INT COMMENT '直属领导ID',
dept_id INT COMMENT '部门ID'
) COMMENT '员工表';
-- 添加数据
INSERT INTO emp (name, age, job, salary, entrydate, managerid, dept_id) VALUES
('金庸', 66, '总裁', 20000, '2000-01-01', NULL, 5),
('张无忌', 20, '项目经理', 12500, '2005-12-05', 1, 1),
('杨逍', 33, '开发', 8400, '2000-11-03', 2, 1),
('韦一笑', 48, '开发', 11000, '2002-02-05', 2, 1),
('常遇春', 43, '开发', 10500, '2004-09-07', 3, 1),
('小昭', 19, '程序员鼓励师', 6600, '2004-10-12', 2, 1);
一对多
案例:部门与员工
关系:一个部门对应多个员工,一个员工对应一个部门
实现:在多的一方建立外键,指向一的一方的主键
多对多
案例:学生与课程
关系:一个学生可以选多门课程,一门课程也可以供多个学生选修
实现:建立第三张中间表,中间表至少包含两个外键,分别关联两方主键
一对一
案例:用户与用户详情
关系:一对一关系,多用于单表拆分,将一张表的基础字段放在一张表中,其他详情字段放在另一张表中,以提升操作效率
实现:在任意一方加入外键,关联另外一方的主键,并且设置外键为唯一的(UNIQUE)
查询
合并查询(笛卡尔积,会展示所有组合结果):select * from employee, dept;
笛卡尔积:两个集合A集合和B集合的所有组合情况(在多表查询时,需要消除无效的笛卡尔积)
消除无效笛卡尔积:select * from employee, dept where employee.dept = dept.id;
-- 多表查询
SELECT * FROM emp,dept
-- 隐式内连接
SELECT * FROM emp,dept WHERE emp.dept_id = dept.id
内连接查询
内连接查询的是两张表交集的部分
隐式内连接:SELECT 字段列表 FROM 表1, 表2 WHERE 条件 ...;
显式内连接:SELECT 字段列表 FROM 表1 [ INNER ] JOIN 表2 ON 连接条件 ...;
显式性能比隐式高
-- 查询员工姓名,及关联的部门的名称
-- 隐式
SELECT e.name,d.name FROM emp AS e, dept AS d WHERE e.dept_id = d.id
-- 显式
SELECT e.name ,d.name FROM emp AS e INNER JOIN dept AS d ON e.dept_id = d.id
外连接查询
左外连接:
查询左表所有数据,以及两张表交集部分数据SELECT 字段列表 FROM 表1 LEFT [ OUTER ] JOIN 表2 ON 条件 ...;
相当于查询表1的所有数据,包含表1和表2交集部分数据
右外连接:
查询右表所有数据,以及两张表交集部分数据SELECT 字段列表 FROM 表1 RIGHT [ OUTER ] JOIN 表2 ON 条件 ...;
-- 左外连接 (相当于查询表1的所有数据,包含表1和表2交集部分数据) emp的所有数据&&对应的部门
SELECT e.*, d.name FROM emp AS e LEFT OUTER JOIN dept AS d ON e.dept_id=d.id
-- 右外连接 (查询右表所有数据,以及两张表交集部分数据)dept的所有数据&&对应员工的名称
SELECT d.*, e.name FROM emp AS e RIGHT OUTER JOIN dept AS d ON e.dept_id=d.id
-- 同样可以达成右外
SELECT d.*, e.name FROM dept AS d LEFT OUTER JOIN emp AS e ON e.dept_id=d.id
自连接查询
当前表与自身的连接查询,自连接必须使用表别名
语法:SELECT 字段列表 FROM 表A 别名A JOIN 表A 别名B ON 条件 ...;
自连接查询,可以是内连接查询,也可以是外连接查询
-- 查询员工及其所属领导的名字 (员工的 managerid 等于 领导的id)
SELECT e.name AS emp_name,m.name AS manage_name FROM emp AS e INNER JOIN emp AS m ON e.managerid=m.id
-- 没有领导的也查询出来
SELECT e.name AS emp_name,m.name AS manage_name FROM emp AS e LEFT OUTER JOIN emp AS m ON e.managerid = m.id
联合查询 union, union all
把多次查询的结果合并,形成一个新的查询集
SELECT 字段列表 FROM 表A ...
UNION [ALL]
SELECT 字段列表 FROM 表B ...
注意事项
- UNION ALL 会有重复结果,UNION 不会
- 联合查询比使用or效率高,不会使索引失效
- 对于联合表查询的多张表的列数必须一致,字段类型也需要一致
-- 将薪资大于 8000 的员工,和年龄大于50岁的员工查出来
SELECT * FROM emp WHERE salary > 8000
UNION ALL
SELECT * FROM emp WHERE age > 50
-- 合并之后去重
SELECT * FROM emp WHERE salary > 8000
UNION
SELECT * FROM emp WHERE age > 50
子查询
SQL语句中嵌套SELECT语句,称谓嵌套查询,又称子查询。SELECT * FROM t1 WHERE column1 = ( SELECT column1 FROM t2);
子查询外部的语句可以是 INSERT / UPDATE / DELETE / SELECT 的任何一个
根据子查询结果可以分为:
- 标量子查询(子查询结果为单个值)
- 列子查询(子查询结果为一列)
- 行子查询(子查询结果为一行)
- 表子查询(子查询结果为多行多列)
根据子查询位置可分为:
- WHERE 之后
- FROM 之后
- SELECT 之后
标量子查询
子查询返回的结果是单个值(数字、字符串、日期等)。
常用操作符:- < > > >= < <=
-- 查询研发部所有员工
SELECT id FROM dept WHERE name = "研发部"
-- 根据研发部ID,查询员工信息
SELECT * FROM emp WHERE dept_id = 1;
-- 合并
SELECT * FROM emp WHERE dept_id = (SELECT id FROM dept WHERE name = "研发部")
-- 查询 小昭 入职之后的员工信息
SELECT * FROM emp WHERE entrydate > (SELECT entrydate FROM emp WHERE name='小昭')
列子查询
子查询 返回的结果是一列(可以是多行)。
常用操作符:
操作符 | 描述 |
---|---|
IN | 在指定的集合范围内 |
NOT IN | 不在指定的集合范围内 |
ANY | 子查询返回列表中,有任意一个满足即可 |
SOME | 与ANY等同,使用SOME的地方都可以使用ANY |
ALL | 子查询返回列表的所有值都必须满足 |
-- 查询比研发部所有人工资都高的员工信息
-- 1.查询所有 研发部 工资
SELECT id FROM dept WHERE name = '研发部'
SELECT salary FROM emp WHERE dept_id = 1
-- 查询比研发部所有人工资都高的员工信息 (比最大的大)
SELECT * FROM emp WHERE salary > all(SELECT salary FROM emp WHERE dept_id = (SELECT id FROM dept WHERE name = '研发部'))
-- 查询比研发部任意一人工资高的员工信息 (比最小的大)
SELECT * FROM emp WHERE salary > ANY(SELECT salary FROM emp WHERE dept_id = (SELECT id FROM dept WHERE name = '研发部'))
行子查询
返回的结果是一行(可以是多列)。
常用操作符:=, <, >, IN, NOT IN
INSERT INTO emp (name, age, job, salary, entrydate, managerid, dept_id) VALUES
('小王', 25, '程序员鼓励师', 6600, '2024-10-03', 2, NULL)
-- 查询与小王的薪资及直属领导相同的员工信息
SELECT * FROM emp WHERE salary = 6600 AND managerid = 2
-- 等价于上边的写法
select * from emp where (salary,managerid)=(6600,2)
SELECT salary, managerid FROM emp WHERE name = "小王"
select * from emp where (salary,managerid)=(SELECT salary, managerid FROM emp WHERE name = "小王")
表子查询
返回的结果是多行多列
常用操作符:IN
-- 查询小王,杨逍的职位和薪资相同的员工
-- 1.查询职位和薪资
SELECT job,salary FROM emp WHERE name="小王" OR name="杨逍"
-- 查询小王,杨逍的职位和薪资相同的员工
SELECT * FROM emp WHERE (job,salary) IN (SELECT job,salary FROM emp WHERE name="小王" OR name="杨逍")
-- 查询入职日期是2005-01-01之后的员工,及其部门信息
-- 1.查询入职日期是2005-01-01之后的员工
SELECT * FROM emp WHERE entrydate > '2005-01-01'
-- 2.查询这部分员工对应的部门信息
SELECT e.*,d.* FROM (SELECT * FROM emp WHERE entrydate > '2005-01-01') AS e LEFT OUTER JOIN dept AS d ON e.dept_id=d.id
查询入职日期是2005-01-01之后的员工 的 查询步骤
-
子查询:
(SELECT * FROM emp WHERE entrydate > '2005-01-01') AS e
- 这个子查询从员工表
emp
中筛选出入职日期在 2005 年 1 月 1 日之后的所有员工记录,并将结果集别名为e
。这个别名e
用于后续的连接操作。
-
左连接(LEFT JOIN):
LEFT JOIN dept AS d ON e.dept_id = d.id
- 这个连接操作将子查询结果
e
与部门表dept
进行左连接。连接条件是员工表中的dept_id
与部门表中的id
相等。 - 左连接的特点:左连接会返回左表(子查询结果
e
)中的所有记录,即使这些记录在右表(部门表dept
)中没有匹配的记录。对于没有匹配的记录,右表的列值将显示为NULL
。
-
选择列:
SELECT e.*, d.*
- 这个部分表示选择左表(子查询结果
e
)和右表(部门表dept
)中的所有列。e.*
表示选择子查询结果中的所有列,d.*
表示选择部门表中的所有列。
DISTINCT
去重
DISTINCT
是 SQL 语句中的一个关键字,用于去除查询结果中的重复行,确保每一行都是唯一的。它通常用于 SELECT
语句中,可以对单个列或多个列进行去重操作。以下是 DISTINCT
的一些常见用法和示例:
单列去重
如果你只想选择某一列的唯一值,可以使用 DISTINCT
关键字。例如,假设有一个员工表 emp
,你想查询所有不同的职位:
SELECT DISTINCT job FROM emp;
这条语句会返回员工表中所有不同的职位,每个职位只出现一次,即使表中有多个员工拥有相同的职位。
结果:
job |
---|
程序员 |
产品经理 |
测试工程师 |
多列去重
DISTINCT
也可以用于多个列,它会根据这些列的组合值来去除重复行。例如,如果你想查询所有不同的部门和职位组合:
SELECT DISTINCT dept_id, job FROM emp;
结果:
dept_id | job |
---|---|
1 | 程序员 |
2 | 产品经理 |
1 | 测试工程师 |
这条语句会返回员工表中所有不同的部门和职位组合,每个组合只出现一次。
注意事项
- 性能影响:使用
DISTINCT
会增加查询的复杂度,特别是当数据量较大时。数据库需要对结果进行排序和去重操作,这可能会导致性能下降。如果可能,尽量避免在大数据集上使用DISTINCT
。 - 索引优化:如果经常需要对某些列进行去重操作,可以考虑在这些列上创建索引,以提高查询效率。
- 组合列去重:当使用多个列进行去重时,数据库会根据这些列的组合值来判断是否重复。这意味着即使某些列的值相同,但只要组合值不同,这些行就不会被去重。
练习
准备薪资等级表
CREATE TABLE salgrade ( grade INT, losal INT, hisal INT ) COMMENT '薪资等级表';
INSERT INTO salgrade
VALUES
( 1, 0, 3000 );
INSERT INTO salgrade
VALUES
( 2, 3001, 5000 );
INSERT INTO salgrade
VALUES
( 3, 5001, 8000 );
INSERT INTO salgrade
VALUES
( 4, 8001, 10000 );
INSERT INTO salgrade
VALUES
( 5, 10001, 15000 );
INSERT INTO salgrade
VALUES
( 6, 15001, 20000 );
INSERT INTO salgrade
VALUES
( 7, 20001, 25000 );
INSERT INTO salgrade
VALUES
( 8, 25001, 30000 );
-- 1.查询员工的,姓名 年龄 职位 部门信息,(隐式内连接)
SELECT e.name,e.age,e.job,d.name FROM emp AS e,dept AS d WHERE e.dept_id=d.id
-- 2.查询 年龄 小于30岁的员工的姓名,年龄 职位 部门信息,(显式内连接)
SELECT e.name,e.age,e.job,d.name FROM emp AS e INNER JOIN dept AS d ON e.dept_id=d.id WHERE e.age<30
-- 3.查询拥有员工的部门id 部门名称
SELECT DISTINCT d.id,d.name FROM emp AS e INNER JOIN dept AS d ON e.dept_id=d.id
-- 4.查询所有年龄大于40的员工,及其归属的部门名称,如果员工没有分配部门,也需要展示出来
SELECT e.*,d.name FROM emp AS e LEFT OUTER JOIN dept AS d ON e.dept_id=d.id WHERE e.age>40
-- 5.查询所有员工的工资等级
SELECT * FROM emp AS e INNER JOIN salgrade AS s ON e.salary BETWEEN s.losal AND s.hisal
-- 6.查询研发部所有的员工信息以及工资等级
SELECT
e.*,
s.grade
FROM
emp AS e
INNER JOIN dept AS d ON e.dept_id = d.id
INNER JOIN salgrade AS s ON e.salary BETWEEN s.losal
AND s.hisal
WHERE
d.NAME = "研发部"
-- 7.查询研发部的平均工资
SELECT AVG(e.salary) FROM emp AS e INNER JOIN dept AS d ON e.dept_id = d.id WHERE d.name="研发部"
-- 8 查询比小王工资都高的员工信息 (标量子查询,子查询返回的结果是单个值(数字、字符串、日期等))
SELECT salary FROM emp WHERE name="小王"
SELECT * FROM emp WHERE salary > (SELECT salary FROM emp WHERE name="小王")
-- 9.查询比平均薪资高的员工信息 (标量子查询)
SELECT AVG(salary) FROM emp
SELECT * FROM emp WHERE salary>(SELECT AVG(salary) FROM emp)
-- 10. 查询低于本部门平均工资的员工信息
-- 查询某个部门的平均薪资
SELECT AVG(salary) FROM emp AS e1 WHERE e1.dept_id =1
SELECT * FROM emp AS e2 WHERE e2.salary < (SELECT AVG(salary) FROM emp AS e1 WHERE e1.dept_id = e2.dept_id )
-- 11.查询 所有的部门信息,并统计 部门员工人数
SELECT * FROM dept
SELECT COUNT(*) FROM emp WHERE dept_id =1
SELECT d.id,d.name,(SELECT COUNT(*) FROM emp AS e WHERE e.dept_id =d.id) AS "人数" FROM dept AS d
查询研发部所有的员工信息以及工资等级 三表查询讲解
从员工表
emp
中读取数据:
- 读取所有员工记录。
将员工表
e
与部门表d
进行内连接:
- 连接条件:
e.dept_id = d.id
- 生成一个临时表,包含员工和部门的信息。
将临时表与薪资等级表
s
进行内连接:
- 连接条件:
e.salary BETWEEN s.losal AND s.hisal
- 生成一个新的临时表,包含员工、部门和薪资等级的信息。
应用过滤条件:
- 过滤条件:
d.name = "研发部"
- 从临时表中筛选出部门名称为“研发部”的记录。
选择最终需要的列:
- 选择员工表
e
中的所有列和薪资等级表s
中的grade
列。
为什么
AVG(e.salary)
不能直接与e.*
一起使用?当你在
SELECT
子句中同时使用e.*
和AVG(e.salary)
时,会出现以下问题:
数据不一致:
e.*
会返回每一行员工的详细信息。AVG(e.salary)
会返回所有员工薪资的平均值,这是一个单一的值。- 如果不使用
GROUP BY
子句,数据库无法确定如何将这个单一的平均值与每一行员工信息结合起来。分组不明确:
- 聚合函数需要明确的分组条件,以确定如何对数据进行汇总。
- 如果不使用
GROUP BY
子句,数据库不知道如何处理每一行数据,因为每一行数据都是独立的,而聚合函数需要对一组数据进行计算。
查询低于本部门平均工资的员工信息
查询步骤
子查询:
(SELECT AVG(salary) FROM emp AS e1 WHERE e1.dept_id = e2.dept_id)
- 这个子查询计算每个部门的平均薪资。对于每个员工
e2
,它会找到与e2
同部门的员工e1
,并计算这些员工的平均薪资。主查询:
SELECT * FROM emp AS e2
- 这个主查询选择员工表
emp
中的所有列,并将结果集别名为e2
。WHERE e2.salary < (SELECT AVG(salary) FROM emp AS e1 WHERE e1.dept_id = e2.dept_id)
- 过滤条件,只选择那些薪资低于其所在部门平均薪资的员工。
执行顺序
从员工表
emp
中读取数据:
- 读取所有员工记录。
对每个员工
e2
执行子查询:
- 对于每个员工
e2
,找到与e2
同部门的员工e1
,并计算这些员工的平均薪资。应用过滤条件:
- 只选择那些薪资低于其所在部门平均薪资的员工。
总结
一、多表查询基础
- 基本概念:通过JOIN子句等将多个表连接,创建包含所需数据的结果集。
- 多表关系类型:包括一对多(多对一)、多对多、一对一三种。
二、多表查询语法及类型
- 基本语法:SELECT 列名 FROM 表1 JOIN 表2 ON 表1.列 = 表2.列;
- 合并查询(笛卡尔积):select * from 表1, 表2; 通过添加WHERE条件可消除无效笛卡尔积。
- 内连接查询:
隐式内连接SELECT 字段列表 FROM 表1, 表2 WHERE 条件 ...;
显式内连接SELECT 字段列表 FROM 表1 [INNER] JOIN 表2 ON 连接条件 ...; - 外连接查询:
左外连接SELECT 字段列表 FROM 表1 LEFT [OUTER] JOIN 表2 ON 条件 ...;
右外连接SELECT 字段列表 FROM 表1 RIGHT [OUTER] JOIN 表2 ON 条件 ...; - 自连接查询:SELECT 字段列表 FROM 表A 别名A JOIN 表A 别名B ON 条件 ...;
- 联合查询:SELECT 字段列表 FROM 表A ... UNION [ALL] SELECT 字段列表 FROM 表B ...;
三、子查询
- 定义:SQL语句中嵌套SELECT语句,外部语句可为INSERT/UPDATE/DELETE/SELECT。
- 分类:
- 标量子查询:返回单个值,常用操作符有=、<、>等。
- 列子查询:返回一列,常用操作符有IN、NOT IN、ANY、SOME、ALL。
- 行子查询:返回一行,常用操作符有=、<、>、IN、NOT IN。
- 表子查询:返回多行多列,常用操作符有IN。
四、DISTINCT去重
- 作用:去除查询结果中的重复行,确保每一行唯一。
- 用法:可对单个列或多列进行去重操作,但使用时需注意性能影响,尽量避免在大数据集上使用。
五、练习案例
- 涉及内连接、外连接、子查询等多种多表查询方式的应用,如查询员工信息及部门信息、查询特定条件的员工及其部门信息、查询员工工资等级、查询部门平均工资