实际开发中数据可能来自不同的表,就有可能需要进行多表查询,用一个简单的公司系统来模拟,一共包含三张表,emp员工表,dept部门表,salgrade工资等级表,内容如下:
可以发现表头部分起始是两个表拼接起来的,拼接的顺序和查询顺序一致,而表的内容就是第一个表的每一行分别和另一个表的每一行拼接,最终的行数就是两个表行数的乘积,这个结果被称为笛卡尔积。一般而言,多表查询都是笛卡尔积的子集。
在这个表的语义中,员工的depno部门编号应该和dept部门表的编号关联起来,员工表后面应该拼接上对应的部门表,方法为:
正好是14个员工,这就是笛卡尔集的一个子集。
例:显示部门号为10的部门名,员工名和工资
部门名在dept表中,而员工名和工资在员工表中,所以必须两个表联合起来查,先找出两个表的部门号相同的的拼接出的子集,否则没有意义,然后找出部门号为10的筛选出来,结果如下:
mysql> mysql> select emp.deptno,dname,ename,sal from emp,dept where emp.deptno=dept.deptno and emp.deptno=10;
+--------+------------+--------+---------+
| deptno | dname | ename | sal |
+--------+------------+--------+---------+
| 10 | ACCOUNTING | CLARK | 2450.00 |
| 10 | ACCOUNTING | KING | 5000.00 |
| 10 | ACCOUNTING | MILLER | 1300.00 |
+--------+------------+--------+---------+
3 rows in set (0.00 sec)
例:显示各个员工的姓名,工资以及工资级别。
员工姓名和工资可以在员工表中找到,工资级别则在工资表中,但是工资级别表示了一个范围,我们需要找到的子集就是满足工资在范围内的信息,方法如下:
mysql> select ename,sal,grade from emp,salgrade where sal>=losal and sal<=hisal;
+--------+---------+-------+
| ename | sal | grade |
+--------+---------+-------+
| SMITH | 800.00 | 1 |
| ALLEN | 1600.00 | 3 |
| WARD | 1250.00 | 2 |
| JONES | 2975.00 | 4 |
| MARTIN | 1250.00 | 2 |
| BLAKE | 2850.00 | 4 |
| CLARK | 2450.00 | 4 |
| SCOTT | 3000.00 | 4 |
| KING | 5000.00 | 5 |
| TURNER | 1500.00 | 3 |
| ADAMS | 1100.00 | 1 |
| JAMES | 950.00 | 1 |
| FORD | 3000.00 | 4 |
| MILLER | 1300.00 | 2 |
+--------+---------+-------+
14 rows in set (0.00 sec)
例:显示员工FORD的领导姓名和编号
首先员工领导的编号信息就在员工表里,可以先在员工表里查询出这个编号,然后再在员工表里找到员工编号为找到的领导编号的人就可以实现,可以使用子查询的方法,先找领导编号,然后根据查找,也可以使用自连接的方法,找出第一张表的领导编号和第二张表的员工编号相同的拼接信息。
mysql> select ename,empno from emp where empno=(select mgr from emp where ename='FORD');
+-------+--------+
| ename | empno |
+-------+--------+
| JONES | 007566 |
+-------+--------+
1 row in set (0.00 sec)
mysql> select emp.ename emp,ep.ename mgr,ep.empno mgrno from emp,emp as ep where emp.mgr=ep.empno and emp.ename='FORD';
+------+-------+--------+
| emp | mgr | mgrno |
+------+-------+--------+
| FORD | JONES | 007566 |
+------+-------+--------+
1 row in set (0.00 sec)
子查询是嵌入在其他sql语句中的select语句,也叫嵌套查询
单行子查询
嵌套的select查询出来的结果只有一行记录,一般只使用这一行中的某一列
多行子查询
查询出来的结果有多行,通常情况下也只使用一列,要配合关键字使用。
in关键字:如果需要查的条件在多条结果之中,就条件满足
例:查询和10号部门的工作岗位相同的雇员的名字,岗位,工资,部门号,不包括10号自己
先找到10号部门有哪些工作岗位,可以在emp表中找到,然后在emp表中找到job在刚刚筛选结果中的信息。
mysql> select distinct job from emp where deptno=10;
+-----------+
| job |
+-----------+
| MANAGER |
| PRESIDENT |
| CLERK |
+-----------+
3 rows in set (0.00 sec)
mysql> select ename,job,sal,deptno from emp where job in (select distinct job from emp where deptno=10);
+--------+-----------+---------+--------+
| ename | job | sal | deptno |
+--------+-----------+---------+--------+
| SMITH | CLERK | 800.00 | 20 |
| JONES | MANAGER | 2975.00 | 20 |
| BLAKE | MANAGER | 2850.00 | 30 |
| CLARK | MANAGER | 2450.00 | 10 |
| KING | PRESIDENT | 5000.00 | 10 |
| ADAMS | CLERK | 1100.00 | 20 |
| JAMES | CLERK | 950.00 | 30 |
| MILLER | CLERK | 1300.00 | 10 |
+--------+-----------+---------+--------+
8 rows in set (0.00 sec)
all关键字
可以配合比较运算符使用,表示比所有人都怎么样
例:显示工资比部门30的所有员工工资高的员工姓名,工资和部门号
先找到所有30部门的工资,然后显示工资大于他们中所有工资的信息
mysql> select ename,sal,deptno from emp where sal > all (select sal from emp where deptno=30);
+-------+---------+--------+
| ename | sal | deptno |
+-------+---------+--------+
| JONES | 2975.00 | 20 |
| SCOTT | 3000.00 | 20 |
| KING | 5000.00 | 10 |
| FORD | 3000.00 | 20 |
+-------+---------+--------+
4 rows in set (0.00 sec)
any关键字
配合比较运算符,表示比其中任意一个怎么样
对上面的例题,显示工资比部门30任意一个员工工资高的员工姓名,工资和部门号,把上面的all换成any即可
mysql> select ename,sal,deptno from emp where sal > any (select sal from emp where deptno=30);
+--------+---------+--------+
| ename | sal | deptno |
+--------+---------+--------+
| ALLEN | 1600.00 | 30 |
| WARD | 1250.00 | 30 |
| JONES | 2975.00 | 20 |
| MARTIN | 1250.00 | 30 |
| BLAKE | 2850.00 | 30 |
| CLARK | 2450.00 | 10 |
| SCOTT | 3000.00 | 20 |
| KING | 5000.00 | 10 |
| TURNER | 1500.00 | 30 |
| ADAMS | 1100.00 | 20 |
| FORD | 3000.00 | 20 |
| MILLER | 1300.00 | 10 |
+--------+---------+--------+
12 rows in set (0.00 sec)
多列子查询
上面的查询方法基本上都是针对单列的,而多列子查询比较时需要同时比较多个数据,因为是多列数据,一般都是比较是否相等,很少比较大小
例:查询和SMITH的部门和岗位完全相同的雇员,不包含SMITH本人
mysql> select * from emp where (deptno,job)=(select deptno,job from emp where ename='SMITH') and ename!='SMITH';
在from子句中使用子查询
上面的子查询都是在where后面进行的,凡是where筛选出来的结果,那就一定是我们需要的结果,如果我们子查询的结果只是我们最终查询结果的中间状态,就可以把子查询结果放在from后面,相当于把子查询的结果当作一个临时表使用。
例:显示每个高于自己部门平均工资的员工的姓名、部门、工资、平均工资
首先要计算出每个部门的平均工资,形成一个临时表,然后和emp表拼接,找到对应部门的信息,然后找出自己工资比平均工资高的显示出来
mysql> select ename,deptno,sal,format(avg,2) from emp,(select deptno dt,avg(sal) avg from emp group by dt) avg_tb where emp.deptno=avg_tb.dt and sal>avg;
+-------+--------+---------+---------------+
| ename | deptno | sal | format(avg,2) |
+-------+--------+---------+---------------+
| ALLEN | 30 | 1600.00 | 1,566.67 |
| JONES | 20 | 2975.00 | 2,175.00 |
| BLAKE | 30 | 2850.00 | 1,566.67 |
| SCOTT | 20 | 3000.00 | 2,175.00 |
| KING | 10 | 5000.00 | 2,916.67 |
| FORD | 20 | 3000.00 | 2,175.00 |
+-------+--------+---------+---------------+
6 rows in set (0.00 sec)
例:查找每个部门工资最高的人的姓名、工资、部门、最高工资
先分组查看出每个部门的最高工资,形成临时表,然后和emp表拼接,显示出工资等于最高工资的人。
mysql> select ename,deptno,sal,mx from emp,(select deptno dp,max(sal) mx from emp group by dp) max_tb where emp.deptno=max_tb.dp and sal=mx;
+-------+--------+---------+---------+
| ename | deptno | sal | mx |
+-------+--------+---------+---------+
| BLAKE | 30 | 2850.00 | 2850.00 |
| SCOTT | 20 | 3000.00 | 3000.00 |
| KING | 10 | 5000.00 | 5000.00 |
| FORD | 20 | 3000.00 | 3000.00 |
+-------+--------+---------+---------+
4 rows in set (0.00 sec)
例:显示每个部门的信息(部门名,编号,地址)和人员数量
首先在emp表分组统计出每一个部门的人数,然后把临时表和部门表拼接形成笛卡尔积,找出部门编号匹配的有效信息显示
mysql> select * from dept,(select deptno,count(*) from emp group by deptno) cnt where dept.deptno=cnt.deptno;
+--------+------------+----------+--------+----------+
| deptno | dname | loc | deptno | count(*) |
+--------+------------+----------+--------+----------+
| 10 | ACCOUNTING | NEW YORK | 10 | 3 |
| 20 | RESEARCH | DALLAS | 20 | 5 |
| 30 | SALES | CHICAGO | 30 | 6 |
+--------+------------+----------+--------+----------+
3 rows in set (0.00 sec)
总结:
子查询一般出现在两个地方,在where子句作为筛选条件使用,或者在from子句用来和特定的表做笛卡尔积
可以合并多个select的执行结果,集合操作符:union、union all
union
该操作符可以取得两个结果集的并集,使用该操作符会自动去重。
例:将工资大于2500或职位是MANAGER的人找出来
满足这两个条件的都复合要求,所有可以用两个select先找出这两类人,然后用union直接合并就可以
mysql> select ename,sal,job from emp where sal>2500
-> union
-> select ename,sal,job from emp where job='MANAGER';
+-------+---------+-----------+
| ename | sal | job |
+-------+---------+-----------+
| JONES | 2975.00 | MANAGER |
| BLAKE | 2850.00 | MANAGER |
| SCOTT | 3000.00 | ANALYST |
| KING | 5000.00 | PRESIDENT |
| FORD | 3000.00 | ANALYST |
| CLARK | 2450.00 | MANAGER |
+-------+---------+-----------+
6 rows in set (0.00 sec)
注意:合并查询在语义上要保证被合并的两个列属性是一样的,如果不一样,可能会出错或者不让联合。
union all
union all和union唯一的差别就是不做去重
mysql> select ename,sal,job from emp where sal>2500
-> union all
-> select ename,saal,job from emp where job='MANAGER';
+-------+---------+-----------+
| ename | sal | job |
+-------+---------+-----------+
| JONES | 2975.00 | MANAGER |
| BLAKE | 2850.00 | MANAGER |
| SCOTT | 3000.00 | ANALYST |
| KING | 5000.00 | PRESIDENT |
| FORD | 3000.00 | ANALYST |
| JONES | 2975.00 | MANAGER |
| BLAKE | 2850.00 | MANAGER |
| CLARK | 2450.00 | MANAGER |
+-------+---------+-----------+
8 rows in set (0.00 sec)
多表查询就是把多张表“合并”成一张表,转换成一张表的查询。自连接,from子查询,多表笛卡尔积,内外链接的本质都是回答了如何完成多张表的合并工作。
我们前面写的用逗号连接两个表的合并形成笛卡尔积的方法就属于内连接。内连接实际上就是利用where子句对两种表形成的笛卡儿积进行筛选,这也是在开发过程中使用的最多的连接查询。
语法:
select 字段 from 表1 inner join 表2 on 连接条件 and 其他条件;
inner join和逗号合并表时没有差别,inner join后面加关键字on就相当于where,提前对笛卡尔积进行处理,然后再使用where的话就可以显得逻辑更加清晰
例:显示SMITH的名字和部门名称
就可以先筛选出合理的笛卡尔积,然后再用where找出SMITH的信息
mysql> mysql> select ename,dname from emp inner join dept on emp.deptno=dept.deptno where emp.ename='SMITH';
+-------+----------+
| ename | dname |
+-------+----------+
| SMITH | RESEARCH |
+-------+----------+
1 row in set (0.00 sec)
外连接分为两种情况,在联合查询时,左侧的表完全显示就称为左外连接,右侧的表完全显示就称为右外连接。
左外连接
语法:
select 字段名 from 表1 left join 表2 on 连接条件;
左外连接的含义是两个表合并时,如果能够符合条件就显示出来,不符合条件时左侧表的信息也不能去掉,也要保留,对应的右侧表的信息就是null
右外连接
语法:
select 字段 from 表1 right join 表2 on 连接条件;
和左外连接类似,如果符合条件能够连接就显示,条件不符合也带把右侧表结构保留,左边没有匹配的就用null填充。
内连接:保留两个表都匹配的,匹配不上的就丢弃
左外连接:保留左表的结构,右表能匹配就匹配,匹配不上就填null
右外连接:保留右表的结构,左表能匹配就匹配,匹配不上就填null