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

一.数据准备:

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.运行结果:

相关推荐
JhonKI几秒前
【MySQL】表的基本查询(下)
数据库·mysql·oracle
微风轻吟挽歌5 分钟前
Mysql唯一约束
数据库·mysql
AR_xsy5 分钟前
MySQL不能被其他地址访问,授权问题解决(8.x,,5.x)
数据库·mysql
黑蛋同志8 分钟前
安装 PostgreSQL
数据库·postgresql
niech_cn14 分钟前
Ai编程从零开始全栈开发一个后台管理系统之接口开发node+express+mysql(十五)
mysql·express·ai编程
圆周率的后六位34 分钟前
图数据库调研
数据库·图数据库
m0_7482565640 分钟前
MySQL数据库误删恢复_mysql 数据 误删
数据库·mysql·adb
落寞书生1 小时前
docker安装mysql 实现主从同步
运维·mysql·docker·主从同步·docker 安装mysql
O(1)的boot1 小时前
微服务的问题
java·数据库·微服务
骐骥12 小时前
PostgreSQL/PostGIS中提升空间查询(分析)性能(效率)的一些方法
数据库·postgresql·postgis·空间分析·空间查询