多表查询-概述&内连接&外连接&子查询

一.数据准备:

1.部门表:

代码:

sql 复制代码
-- 部门管理
create table tb_dept
(
    id          int unsigned primary key auto_increment comment '主键ID',
    name        varchar(10) not null unique comment '部门名称',
    create_time datetime    not null comment '创建时间',
    update_time datetime    not null comment '修改时间'
) comment '部门表';
​
insert into tb_dept (id, name, create_time, update_time)
values (1, '学工部', now(), now()),
       (2, '教研部', now(), now()),
       (3, '咨询部', now(), now()),
       (4, '就业部', now(), now()),
       (5, '人事部', now(), now());

2.员工表:

代码:

sql 复制代码
-- 员工管理
create table tb_emp
(
    id          int unsigned primary key auto_increment comment 'ID',
    username    varchar(20)      not null unique comment '用户名',
    password    varchar(32) default '123456' comment '密码',
    name        varchar(10)      not null comment '姓名',
    gender      tinyint unsigned not null comment '性别, 说明: 1 男, 2 女',
    image       varchar(300) comment '图像',
    job         tinyint unsigned comment '职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管,5.咨询师',
    entrydate   date comment '入职时间',
    dept_id     int unsigned comment '部门ID', -- 属于外键字段,关联部门表的主键ID
    create_time datetime         not null comment '创建时间',
    update_time datetime         not null comment '修改时间'
) comment '员工表';
​
insert into tb_emp
(id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time)
values (1, 'jinyong', '123456', '金庸', 1, '1.jpg', 4, '2000-01-01', 2, now(), now()),
       (2, 'zhangwuji', '123456', '张无忌', 1, '2.jpg', 2, '2015-01-01', 2, now(), now()),
       (3, 'yangxiao', '123456', '杨逍', 1, '3.jpg', 2, '2008-05-01', 2, now(), now()),
       (4, 'weiyixiao', '123456', '韦一笑', 1, '4.jpg', 2, '2007-01-01', 2, now(), now()),
       (5, 'changyuchun', '123456', '常遇春', 1, '5.jpg', 2, '2012-12-05', 2, now(), now()),
       (6, 'xiaozhao', '123456', '小昭', 2, '6.jpg', 3, '2013-09-05', 1, now(), now()),
       (7, 'jixiaofu', '123456', '纪晓芙', 2, '7.jpg', 1, '2005-08-01', 1, now(), now()),
       (8, 'zhouzhiruo', '123456', '周芷若', 2, '8.jpg', 1, '2014-11-09', 1, now(), now()),
       (9, 'dingminjun', '123456', '丁敏君', 2, '9.jpg', 1, '2011-03-11', 1, now(), now()),
       (10, 'zhaomin', '123456', '赵敏', 2, '10.jpg', 1, '2013-09-05', 1, now(), now()),
       (11, 'luzhangke', '123456', '鹿杖客', 1, '11.jpg', 1, '2007-02-01', 1, now(), now()),
       (12, 'hebiweng', '123456', '鹤笔翁', 1, '12.jpg', 1, '2008-08-18', 1, now(), now()),
       (13, 'fangdongbai', '123456', '方东白', 1, '13.jpg', 2, '2012-11-01', 2, now(), now()),
       (14, 'zhangsanfeng', '123456', '张三丰', 1, '14.jpg', 2, '2002-08-01', 2, now(), now()),
       (15, 'yulianzhou', '123456', '俞莲舟', 1, '15.jpg', 2, '2011-05-01', 2, now(), now()),
       (16, 'songyuanqiao', '123456', '宋远桥', 1, '16.jpg', 2, '2010-01-01', 2, now(), now()),
       (17, 'chenyouliang', '123456', '陈友谅', 1, '17.jpg', NULL, '2015-03-21', NULL, now(), now());

二.多表查询的SQL语句:

1.代码:多张表之间用逗号分隔

sql 复制代码
-- 多表查询
select * from tb_dept,tb_emp; -- 同时查询部门表和员工表的所有字段
2.运行结果:

但运行结果中有许多条查询结果,超出了员工表的17条数据和部门表的5条数据,所以这是为什么呢?

观察结果可以发现,金庸和其他员工都各自出现了5次,而且一个员工和5个部门分别进行了匹配,

所以就是将17位员工依次和5个部门匹配,也就是查询结果共有5 * 17 = 85条数据:

这个现象就叫笛卡尔积

以金庸这个员工为例,金庸实际只归属于2号部门,因此就需要加入一个连接条件来去除掉无效部门,

本例中就需要员工所在的部门id等于部门id:

sql 复制代码
-- 多表查询
select * from tb_dept,tb_emp where tb_emp.dept_id = tb_dept.id;

运行结果为:

只有16个数据是因为有一个员工的dept_id为null


三.多表查询概述及笛卡尔积:

笛卡尔积(Cartesian product)是指在数学中,两个集合X和Y的笛卡尔积表示为X×Y,其中第一个对象是X的成员,而第二个对象是Y的所有可能有序对的其中一个成员‌。‌

笛卡尔积的计算公式可以表示为:A×B = {(x,y)|x∈A∧y∈B},其中A和B是两个集合。

在SQL中,笛卡尔积可以通过简单的多表查询实现,例如:

sql 复制代码
-- 多表查询
select * from tb_dept,tb_emp; -- 同时查询部门表和员工表的所有字段

这条SQL语句会返回tb_dept表和tb_emp表的所有可能组合,即使这些组合在实际中并不存在任何关联。

为了清除掉无效数据,就需要增加条件语句:


四.多表查询分类:


五.内连接语法:

(隐式内连接的SQL语句中from后可操作不止两张表;显式内连接的SQL语句中inner可省)

a.隐式内连接演示:

1.代码:
sql 复制代码
-- ==============================内连接==============================
-- A.查询员工的姓名,及所属的部门名称(用隐式内连接实现)
select tb_emp.name,tb_dept.name from tb_emp,tb_dept where tb_emp.dept_id = tb_dept.id;
/*本例中是利用tb_emp.dept_id和tb_dept.id进行关联了员工和部门,员工表的dept_id字段代表部门id,也就关联了部门表的id*/
/*select name,name from tb_emp,tb_dept where tb_emp.dept_id = tb_dept.id;这样是错的,因为这么写的话数据库就
无法得知两个name字段分别是哪个表的(注:两张表都有name字段),因此就需要都注明name字段是哪张表的*/
2.运行结果:

运行结果有16条数据,而测试数据有17条数据,这是因为第17条数据的dept_id为null即没有分配部门,因此这条记录就和部门表没有关系,而内连接查询的是两张表交集部分的数据,所以第17条数据也就查询不出来:

b.显式内连接演示:

1.代码:
sql 复制代码
-- B.查询员工的姓名,及所属的部门名称(用显式内连接实现)
select tb_emp.name,tb_dept.name from tb_emp inner join tb_dept on tb_emp.dept_id = tb_dept.id;
/*注:这里的字段和表的先后要一一对应*/
2.运行结果:

六.多表连接查询时需要指定字段的表名,如果表名比较长会导致繁琐,因此可以为表起别名,给表起别名的语法(字段也可以起别名):

1.语法:原名+空格+别名

注:一旦给表起了别名,那么在该SQL语句中后面指定字段名时就需要用表的别名,不能用表的原名;

2.举例:

sql 复制代码
-- 起别名:字段和表都可以起别名,起别名的语法就是 原名+空格+别名
select e.name,d.name from tb_emp e , tb_dept d where e.dept_id = d.id;
/*e为tb_emp表的别名,d为tb_dept表的别名*/
/*如果给表指定了别名,那么之后该SQL语句中用表时就必须用表的别名,用原名会报错*/

运行结果:


七.外连接语法:

(outer关键字可省略;表1为左表,表2为右表)

a.左外连接演示:

1.代码:
sql 复制代码
-- ==============================外连接==============================
-- A.查询员工表 所有 员工的姓名,和对应的部门名称(左外连接)
select tb_emp.name,tb_dept.name from tb_emp left outer join tb_dept on tb_emp.dept_id = tb_dept.id;
/*此时可以查询出所有员工的信息,因为虽然第17号员工没有部门id即和部门表没有交集,但它在员工表(左表)中,是可以查到的
  (左外连接操作是把左表,左表和右表的交集一起查询)*/
/*员工表中员工的姓名字段为name,部门表中部门名称字段为name*/

tb_emp .dept_id = tb_dept.id是用来关联两张表的;

2.运行结果:

此时可以查询出所有员工的信息,因为虽然第17号员工没有部门id即和部门表没有交集,但它在员工表(左表)中,是可以查到的 (左外连接操作是把左表,左表和右表的交集一起查询)

b.右外连接演示:

1.代码:
sql 复制代码
-- B.查询部门表 所有 部门的名称,和对应的员工名称(右外连接)
select tb_emp.name,tb_dept.name from tb_emp right outer join tb_dept on tb_emp.dept_id = tb_dept.id;
/*此时能查询出19条数据,因为右外连接操作是把右表,左表和右表的交集一起查询*/
/*部门表中有5个部门,员工表中一共关联了2个部门,但根据右外连接的查询规则,剩下的3个部门也会查询出来,尽管员工表中没有关联这些部门*/
2.运行结果:

此时能查询出19条数据,因为右外连接操作是把右表,左表和右表的交集一起查询。部门表中有5个部门,员工表中一共关联了2个部门,但根据右外连接的查询规则,剩下的3个部门也会查询出来,尽管员工表中没有关联这些部门。

第17号员工在员工表中,而第17号员工没有部门id即和部门表没有交集,它只在员工表(左表)中,右外连接中是查询不到的。

拓展:

把左外连接改为右外连接或者把右外连接改为左外连接,只需要把表1和表2互换位置即可:

比如左外连接改为右外连接:

sql 复制代码
-- B.查询部门表 所有 部门的名称,和对应的员工名称
select tb_emp.name,tb_dept.name from tb_dept left outer join tb_emp on tb_emp.dept_id = tb_dept.id;
/*
select tb_emp.name,tb_dept.name from tb_emp right outer join tb_dept on tb_emp.dept_id = tb_dept.id;
*/
/*此时SQL语句中用到了left outer join,但查询结果和select tb_emp.name,tb_dept.name from tb_emp right outer join tb_dept on tb_emp.dept_id = tb_dept.id;该SQL语句结果一样,因为这是把左外连接改为了右外连接*/
/*左外连接包含左表的所有数据以及两张表交集的数据,意味着left左边就包含了tb_dept表的所有数据,以及两张表交集的数据*/

运行结果为:

项目开发中左外连接用的较多,右外连接用的少,因为右外连接可以替换为左外连接。


八.子查询语法:

(列子查询的结果可以是多行或一行,但必须是一列;行子查询的结果可以是多列或一列,但必须是一行)

a.标量子查询:子返回的结果是单行单列

例1:
代码:
sql 复制代码
-- ==============================子查询==============================
-- 标量子查询
-- A.查询"教研部"的所有员工信息
/*分析:首先要查询员工的信息,也就是最终要查询员工表的信息,但员工表中只有部门id,并没有部门名称,而现在的限制条件是部门名称"教研部"
    因此可以把条件进行分解*/
-- 1.查询 教研部 的部门ID:查询部门表即可
select id from tb_dept where name = '教研部';

-- 2.再查询该部门ID下的员工信息:查询员工信息就是查询员工表
select * from tb_emp where dept_id = 2;
/*2号部门是教研部,但通常情况下部门名称的id无法得知,此时就需要嵌套查询*/

-- 嵌套
select * from tb_emp where dept_id = (select id from tb_dept where name = '教研部');
/*注:嵌套要加括号,括号里的相当于子,本例中子查询的结果为2*/
2.运行结果:
例2:
1.代码:
sql 复制代码
-- B.查询在"方东白"入职之后的员工信息
-- 1.查询方东白的入职时间
select entrydate from tb_emp where name = '方东白'; /*相当于子,结果是单个值*/

-- 2.查询在"方东白"入职之后的员工信息
select * from tb_emp where entrydate > '2012-11-01';

-- 嵌套
select * from tb_emp where entrydate > (select entrydate from tb_emp where name = '方东白');
2.运行结果:

b.列子查询:

例如:
1.代码:
sql 复制代码
-- 列子查询
-- A.查询"学工部"和"教研部"的所有员工信息
/*分析:最终要的是员工信息即员工表的内容,但员工表中只有部门id,没有部门名称,因此就需要先查询出部门名称对应的部门id*/
-- 1.查询"学工部"和"教研部"的部门id,这是在部门表下
select id from tb_dept where name = '学工部' or name = '教研部';
/*select id from tb_dept where name = '学工部' or '教研部';
这条SQL语句有错误,查询的是两个部门name即"学工部"和"教研部"的id,而条件中只限制了一个name,最终可能无法查询完整*/
/*select id from tb_dept where name in ('学工部','教研部');
也是正确的,也可以查询出"学工部"和"教研部"对应的部门id*/


-- 2.根据部门id,查询该部门下的员工信息即查询员工表
select * from tb_emp where dept_id in (1,2);
/*select * from tb_emp where dept_id = 1 or dept_id = 2;也对*/

-- 嵌套:嵌套的时候SQL语句的条件限制中就无法使用逻辑运算符or了
select * from tb_emp where dept_id in (select id from tb_dept where name = '学工部' or name = '教研部');
/*select * from tb_emp where dept_id = (select id from tb_dept where name = '学工部' or name = '教研部');
这条SQL语句不正确,因为=运算符的右边只能是一个确切的值,而select id from tb_dept where name = '学工部' or name = '教研部'的结果
可能不止一个值*/
2.运行结果:

c.行子查询:

例如:
1.代码:
sql 复制代码
-- 行子查询
-- A.查询与"韦一笑"的入职日期 及 职位都相同的员工信息
-- 1.查询"韦一笑"的入职日期 及 职位
select entrydate,job from tb_emp where name = '韦一笑'; -- 这是子,结果只有1行,但有多列

-- 2.查询与"韦一笑"的入职日期 及 职位都相同的员工信息,最终要的是员工的完整信息
select * from tb_emp where entrydate = '2007-01-01' and job = 2;


-- 嵌套合并
/*此时条件语句中有两个参数即entrydate和job,嵌套时就需要把参数分开写*/
select * from tb_emp where entrydate = (select entrydate from tb_emp where name = '韦一笑')
                       and job = (select job from tb_emp where name = '韦一笑');
/*entrydate就专门查entrydate,job就专门查job
  但嵌套后的SQL语句较繁琐,因此要优化*/

-- =============================================优化=============================================
-- select * from tb_emp where entrydate = '2007-01-01' and job = 2;等效于
select * from tb_emp where (entrydate,job) = ('2007-01-01',2);
/*注:括号里的内容是一一对应的*/

-- 最终优化效果为
select * from tb_emp where (entrydate,job) = (select entrydate,job from tb_emp where name = '韦一笑');
/*少写了一个select语句*/
2.运行结果:

d.表子查询:

例如:
1.代码:
sql 复制代码
-- 表子查询
-- A.查询入职日期是"2006-01-01"之后的员工信息,及其部门名称
-- 1.查询入职日期是"2006-01-01"之后的员工信息
select * from tb_emp where entrydate > '2006-01-01';

-- 2.查询这部分员工信息及其部门名称
/*分析:其中要查询部门名称,必然要用到部门表,查询到的这部分员工信息可作为临时表进行查询*/
/*这条SQL语句较繁琐,可以给表起别名来简化代码*/
select e.* , d.name from (select * from tb_emp where entrydate > '2006-01-01') e ,tb_dept d where e.dept_id = d.id;
/*e为员工临时表的别名,d为部门表的别名,e.*代表员工临时表的所有信息,d.name代表部门表的部门名称*/
/*e.dept_id = d.id用来关联两张表*/
2.运行结果:

相关推荐
dddaidai12316 分钟前
Redis解析
数据库·redis·缓存
数据库幼崽22 分钟前
MySQL 8.0 OCP 1Z0-908 121-130题
数据库·mysql·ocp
Amctwd38 分钟前
【SQL】如何在 SQL 中统计结构化字符串的特征频率
数据库·sql
betazhou1 小时前
基于Linux环境实现Oracle goldengate远程抽取MySQL同步数据到MySQL
linux·数据库·mysql·oracle·ogg
lyrhhhhhhhh2 小时前
Spring 框架 JDBC 模板技术详解
java·数据库·spring
喝醉的小喵3 小时前
【mysql】并发 Insert 的死锁问题 第二弹
数据库·后端·mysql·死锁
付出不多3 小时前
Linux——mysql主从复制与读写分离
数据库·mysql
初次见面我叫泰隆4 小时前
MySQL——1、数据库基础
数据库·adb
Chasing__Dreams4 小时前
Redis--基础知识点--26--过期删除策略 与 淘汰策略
数据库·redis·缓存
源码云商4 小时前
【带文档】网上点餐系统 springboot + vue 全栈项目实战(源码+数据库+万字说明文档)
数据库·vue.js·spring boot