MySQL_Ch_3
1.多表连接查询
-
查询员工姓名和员工所在部门的编号
select ename ,deptno from emp;
-
查询员工姓名, 所在部门名称
select dname from dept where deptno = 10;
-
多表连接的基础 ——
笛卡尔积
– 两个集合
A{a,b}、B{1,2,3}
– 将两个集合中的元素逐一匹配形成一个新的集合
–
A*B = {a1,a2,a3,b1,b2,b3}
select * from emp,dept; # 这种笛卡尔积的方式,会造成重复不必要数据
– 实际应用需要在原有的笛卡尔积的基础上,设置必要的连接条件
-
等值连接
– 两张或者多张表之间,使用等号连接意义相同的两个列
– 表.列名 哪张表下的哪一列
select * from emp,dept where emp.deptno = dept.deptno;
– 表设置别名(同名的表或者表名太长)
– 建议使用空格的方式设置别名
– 因为要适应于多种数据库;列名实际还是用as
–
as
在oracle
数据库中是不能用于标的别名的select * from emp e,dept d where e.deptno = d.deptno;
– 查询月薪高于1500的员工姓名和部门名称
select ename,dname,d.deptno # 如果列名是唯一,可以不用指明是哪一张表下的 # 如果列名是重复,需要指定具体是哪一张表 from emp e,dept d where e.deptno = d.deptno and sal >1500; -- 筛选条件
– 表示两张以上的
– 查询100号员工的姓名,所在部门名称,所在城市,所在国家,所在大区
select e.first_name,d.department_name, l.city, c.country_id, r.region_id from employees e, departments d, locations l, countrise c, regions r where e.department_id = d.department_id and d.location_id = l.location_id and l.country_id = c.country_id and c.region_id = r.region_id and employee_id = 100 ; -- 筛选条件
-
不等值连接
– 不使用等号进行连接
< > <= >= !=(<>)
– 查询员工姓名,月薪和工资的级别
select ename, sal, grade from emp e,salgrade s where e.sal between s.losal and s.hisal;
-
外连接
- 左外连接
- 右外连接
– 查询员工姓名和其所在部门的名称,包含没有部门的员工
错误写法
select e.ename, d.dname from emp e, dept d where e.deptno = d.deptno or e.deptno is null; -- 错误写法,因为上面那个条件没有对or后的条件进行限制.
– MySQL没有自己特有的外连接方式
– 必须按照
SQL99
标准写法实现– 表
A [left/right] [outer] join 表B on(...)
join
左边的表为左表,右边的表为右表– 查询员工姓名和其所在部门的名称,包含没有部门的员工
select e.ename, d.dname from emp e left join dept d #[员工:XXX],[部门:null] ==>用左外连接 on (e.deptno = d.deptno)
select e.ename, d.dname from dept d right join emp e #[部门:null],[员工:XXX], ==>用右外连接 on (e.deptno = d.deptno)
– 查询员工姓名和其所在部门的名称,包含没有员工的部门
select e.ename, d.dname from emp e right join dept d #[员工:null],[部门:XXX] ==>用右外连接 on (e.deptno = d.deptno)
select d.dname, e.ename from dept d left join emp e #[部门:XXX],[员工:null] ==>用左外连接 on (d.deptno = e.deptno)
– 你想让哪部分多余的显示出来(即不是null值的那一部分),则对应哪侧的外连接
-
自连接
– 适用场景: 国家的行政体系 公司的组织结构
– 层次关系
– 查询员工姓名和对应的经理姓名
–(姓名id和经理id是对应的,故可以自连接,第二张员工表的部分员工id可以看做经理id,因为经理也属于员工)
错误写法
select e1.ename as 员工姓名, e2.ename as 经理姓名 from emp e1, emp e2 -- e1 员工表 e2 经理表 where e1.empno = e2.mgr; -- 逻辑相反了,结果错误 -- 错误的原因是:e2表中的mgr和e1表中的empno相等时,e2.mgr对应的e1.empno的e1.ename就是经理姓名,所以e1.ename就是经理姓名,同理e2.ename就是员工表里的员工.
– 让员工的经理编号与经理的员工编号相等即可查询出来
正确写法
select e1.ename as 员工姓名, e2.ename as 经理姓名 from emp e1, emp e2 -- e1 员工表 e2 经理表 where e1.mgr = e2.empno; #员工表里的经理编号和经理表里的员工编号相等,逻辑才正确
– 查询入职日期比其经理入职日期早的员工姓名和经理姓名
select e1.ename as 员工姓名, e2.ename as 经理姓名 from emp e1, emp e2 -- e1 员工表 e2 经理表 where e1.mgr = e2.empno and e1.hiredate < e2.hiredate;
-
SQL99标准语法
-
–
join ... on()
-
查询月薪高于1500的员工姓名和部门名称
select e.ename, dname from emp e join dept d on(e.deptno = d.deptno) #多表的连接条件 where sal >1500; #筛选条件
-
查询员工姓名,月薪和工资的级别
select e.ename, sal, grade from emp e join salgrade s on(e.sal between s.losal and s.hisal);
-
–自连接(内连接)
select e1.ename,e2.ename from emp e1 inner join emp e2 on(e.mgr = e2.empno);
-
– 外连接(左外连接left outer和右外连接right outer)
–(employee表和departments表)
– 查询部门名称和其经理姓名,包含没有经理的部门
select d.department_name, e.first_name , e.last_name from departments d left join employees e #左外连接 on (e.employee_id = d.manager_id);
select d.department_name, e.first_name , e.last_name from employees e right join departments d on (e.employee_id = d.manager_id);
– 左外连接和右外连接其实是等价的
-
全连接(MySQL 不支持)
– full join … on (…)
– 左外连接和右外连接再做一次多表连接
-
-
-
-
2.子查询
-
在一个查询中嵌套的查询被称为子查询
-
where 后嵌套
-
from 后嵌套
-
having 后嵌套
-
select 后嵌套
-
按结构划分
-
单行单列子查询
-
– 查询和Scott同一个部门的其他员工信息
select deptno from emp where ename = 'scott'; # # select * from emp where deptno = 20 and ename <> 'scott';
组合查询
select * from emp where deptno = (select deptno from emp where ename = 'scott') and ename <> 'scott';
– 外层我们可以称为主查询或者外查询
– 内层我们成为内查询,或者子查询
– 查询与scoot同一个部门,同一个经理的其他员工信息
select * from emp where deptno = (select deptno from emp where ename = 'scott') and mgr = (select mgr from emp where ename = 'scott') and ename <> 'scott'; -- 三个查询,效率比较低
-
-
单行多列子查询
select * from emp where (deptno, mgr) = (select deptno,mgr from emp where ename = 'scott') and ename <> 'scott'; -- 注意 -- 1) 单行多列 where后面的多列一定要有括号 -- 2) where后的顺序要与子查询中查询出结果的顺序一致
-
多行子查询(多行单列子查询、多行多列子查询)
– 查询与Scott或King同一个部门的员工信息(多行单列子查询)
select * from emp where deptno in (select deptno from emp where ename = 'scott' or ename = 'King');
-
子查询可以在哪些语句中使用
– where(重要)
– from(重要) 万能查询
– 利用子查询的结果,将其当做一张表,
– 与其他的表或者查询结果再次做多表连接
外连接和等值连接区别(见最下总结)
-
from
后的嵌套 -
from
嵌套为万能嵌套 -
例子: 查询员工的姓名,部门名称,员工的工资和其所在部门的平均工资:
1)先查询部门名称和部门平均工资
select d.dname, avg(sal), d.deptno from emp e join dept d on(e.deptno = d.deptno) group by d.dname,d.deptno;
2)将上述结果想象成一张表,再与emp做关联
select e1.ename, t.dname, e1.sal, t.avgsal from emp e1 join (select d.dname, avg(sal) as avgsal, d.deptno from emp e join dept d on(e.deptno = d.deptno) group by d.dname,d.deptno) t on(e1.deptno = t.deptno);
#使用等值连接(内连接) select e1.ename,t.dname,e1.sal,t.avg_sal from emp e1 ,(select e.deptno,d.dname, avg(sal) as avg_sal from emp e,dept d group by e.deptno, d.dname) t where e1.deptno = t.deptno group by e1.ename,e1.sal; ####### select e.ename, d.dname, e.sal, t.avg_sal from emp e,dept d,(select deptno,avg(sal) as avg_sal from emp group by deptno) t where d.deptno = e.deptno group by e.ename,d.dname;
-
查询员工姓名,职位,月薪,及其同职位的最高和最低月薪
1)先查职位和同职位的最高、最低月薪
select e2.job, max(e2.sal), min(e2.sal) from emp e2 group by e2.job;
2)利用上述结果与emp关联,查询剩余的列
select e1.ename, e1.job, e1.sal, t.maxsal, t.minsal from emp e1 join (select e2.job, max(e2.sal) as maxsal, min(e2.sal) as minsal from emp e2 group by e2.job) t on(e1.job = t.job);
#####使用等值连接 select e.ename, e.job, e.sal, t.max_sal, t.min_sal from emp e,(select deptno, max(sal) as max_sal, min(sal) as min_sal from emp group by deptno) t where t.deptno = e.deptno group by e.ename,e.job,e.sal;
-
查询月薪比自己职位的平均月薪高的员工信息
–
select job,avg(sal) from emp group by job;
select * from emp e1 join (select job, avg(sal) as avgsal from emp group by job) t on(e1.job = t.job) where e1.sal > t.avgsal;
###使用等值连接 select e.* from emp e,(select deptno, avg(sal) as avg_sal from emp group by deptno) t where t.deptno = e.deptno and e.sal > t.avg_sal group by empno;
-
having
后的嵌套(使用较少)当
where
嵌套里涉及到分组函数时,一般使用having
嵌套,因为where后不跟分组函数,要用having
替换. -
查询部门平均工资高于MARTIN的部门编号
select deptno from emp group by deptno having avg(sal) > (select sal from emp where ename = 'MARTIN');
-
select
后的嵌套 (基本不用) -
查询部门编号,部门名称,部门所在地,各个部门的人数,各个部门的平均月薪
# 与相关子查询思考方式一致 select d.deptno, d.dname, d.loc, #(部门人数), (select count(*) from emp where emp.deptno = d.deptno group by deptno) , # (部门平均月薪) (select avg(sal) from emp where emp.deptno = d.deptno group by deptno) from dept d;
-
-
练习
-
查询与Neena同一个经理的其他员工信息
select * from employees where manager_id = (select manager_id from employees where first_name = 'Neena') and first_name <> 'Neena';
-
查询比Jennifer Whalen月薪高的员工信息
select * from employees where salary > (select salary from employees where first_name = 'Jennifer' and last_name = 'Whalen');
查询比Jennifer月薪高的员工信息
# ANY 和 ALL的使用 select * from employees where salary > ANY(select salary from employees where first_name = 'Jennifer' and last_name = 'Whalen');
-
查询员工姓名,月薪和其部门的平均月薪
select e.first_name, e.last_name, e.salary, t.asal from employees e join (select department_id, avg(salary) as asal from employees group by department_id) t on(e.department_id = t.department_id);
-
查询月薪高于其部门的平均月薪的员工信息
select e.* from employees e join (select department_id,avg(salary) as avg_sal from employees group by department_id) t on(e.department_id = t.department_id) where e.salary > t.avg_sal;
-
查询司龄高于其部门平均司龄的员工信息
select e.* from employees e join (select department_id, avg(year(curdate()) - year(hire_date)) as ayear from employees group by department_id) t on(e.department_id = t.department_id) where (year(curdate()) - year(hire_date)) > t.ayear;
外连接和内连接的区别
1、内联接(典型的联接运算,使用像 = 或 <> 之类的比较运算符)。
- 包括相等联接和自然联接。
- 内联接使用比较运算符根据每个表共有的列的值匹配两个表中的行。
- 例如,检索 students和courses表中学生标识号相同的所有行。
2、外联接。
- 外联接可以是左向外联接、右向外联接或完整外部联接。
- 在 FROM子句中指定外联接时,可以由下列几组关键字中的一组指定:
- 1)LEFT JOIN或LEFT OUTER JOIN
左向外联接的结果集包括 LEFT OUTER子句中指定的左表的所有行,而不仅仅是联接列所匹配的行。如果左表的某行在右表中没有匹配行,则在相关联的结果集行中右表的所有选择列表列均为空值。 - 2)RIGHT JOIN 或 RIGHT OUTER JOIN
右向外联接是左向外联接的反向联接。将返回右表的所有行。如果右表的某行在左表中没有匹配行,则将为左表返回空值。 - 3)FULL JOIN 或 FULL OUTER JOIN
完整外部联接返回左表和右表中的所有行。当某行在另一个表中没有匹配行时,则另一个表的选择列表列包含空值。如果表之间有匹配行,则整个结果集行包含基表的数据值。
- 1)LEFT JOIN或LEFT OUTER JOIN
3、交叉联接
交叉联接返回左表中的所有行,左表中的每一行与右表中的所有行组合。交叉联接也称作笛卡尔积。
FROM 子句中的表或视图可通过内联接或完整外部联接按任意顺序指定;但是,用左或右向外联接指定表或视图时,表或视图的顺序很重要。有关使用左或右向外联接排列表的更多信息,请参见使用外联接。
例子:
-------------------------------------------------
a表 id name b表 id job parent_id
1 张3 1 23 1
2 李四 2 34 2
3 王武 3 34 4
a.id同parent_id 存在关系
--------------------------------------------------
1) 内连接
select a.*,b.* from a inner join b on a.id=b.parent_id
结果是
1 张3 1 23 1
2 李四 2 34 2
2)左连接
select a.*,b.* from a left join b on a.id=b.parent_id
结果是
1 张3 1 23 1
2 李四 2 34 2
3 王武 null
3) 右连接
select a.*,b.* from a right join b on a.id=b.parent_id
结果是
1 张3 1 23 1
2 李四 2 34 2
null 3 34 4
4) 完全连接
select a.*,b.* from a full join b on a.id=b.parent_id
结果是
1 张3 1 23 1
2 李四 2 34 2
null 3 34 4
3 王武 null
一、交叉连接(CROSS JOIN)
交叉连接(CROSS JOIN):有两种,显式的和隐式的,不带ON子句,返回的是两表的乘积,也叫笛卡尔积。
例如:下面的语句1和语句2的结果是相同的。
语句1:隐式的交叉连接,没有CROSS JOIN。
SELECT O.ID, O.ORDER_NUMBER, C.ID, C.NAME
FROM ORDERS O , CUSTOMERS C
WHERE O.ID=1;
语句2:显式的交叉连接,使用CROSS JOIN。
SELECT O.ID,O.ORDER_NUMBER,C.ID,
C.NAME
FROM ORDERS O CROSS JOIN CUSTOMERS C
WHERE O.ID=1;
语句1和语句2的结果是相同的,查询结果如下:
二、内连接(INNER JOIN)
内连接(INNER JOIN):有两种,显式的和隐式的,返回连接表中符合连接条件和查询条件的数据行。(所谓的链接表就是数据库在做查询形成的中间表)。
例如:下面的语句3和语句4的结果是相同的。
语句3:隐式的内连接,没有INNER JOIN,形成的中间表为两个表的笛卡尔积。
SELECT O.ID,O.ORDER_NUMBER,C.ID,C.NAME
FROM CUSTOMERS C,ORDERS O
WHERE C.ID=O.CUSTOMER_ID;
语句4:显示的内连接,一般称为内连接,有INNER JOIN,形成的中间表为两个表经过ON条件过滤后的笛卡尔积。
SELECT O.ID,O.ORDER_NUMBER,C.ID,C.NAME
FROM CUSTOMERS C INNER JOIN ORDERS O ON C.ID=O.CUSTOMER_ID;
语句3和语句4的查询结果:
三、外连接(OUTER JOIN):外连不但返回符合连接和查询条件的数据行,还返回不符合条件的一些行。
- 外连接分三类:左外连接(LEFT OUTER JOIN)、右外连接(RIGHT OUTER JOIN)和全外连接(FULL OUTER JOIN)。
三者的共同点是都返回符合连接条件和查询条件(即:内连接)的数据行。不同点如下:- 左外连接还返回左表中不符合连接条件单符合查询条件的数据行。
- 右外连接还返回右表中不符合连接条件单符合查询条件的数据行。
- 全外连接还返回左表中不符合连接条件单符合查询条件的数据行,并且还返回右表中不符合连接条件单符合查询条件的数据行。全外连接实际是上左外连接和右外连接的数学合集(去掉重复),即“全外=左外 UNION 右外”。
- 说明:左表就是在“(LEFT OUTER JOIN)”关键字左边的表。右表当然就是右边的了。在三种类型的外连接中,OUTER 关键字是可省略的。
下面举例说明:
语句5:左外连接(LEFT OUTER JOIN)
SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME
FROM ORDERS O LEFT OUTER JOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID;
语句6:右外连接(RIGHT OUTER JOIN)
SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME
FROM ORDERS O RIGHT OUTER JOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID;
注意
:WHERE条件放在ON后面查询的结果是不一样的。例如:
语句7:WHERE条件独立。
SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME
FROM ORDERS O LEFT OUTER JOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID
WHERE O.ORDER_NUMBER<>'MIKE_ORDER001';
语句8:将语句7中的WHERE条件放到ON后面。
SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME
FROM ORDERS O LEFT OUTER JOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID AND O.ORDER_NUMBER<>'MIKE_ORDER001';
从语句7和语句8查询的结果来看,显然是不相同的,语句8显示的结果是难以理解的。因此,推荐在写连接查询的时候,ON后面只跟连接条件,而对中间表限制的条件都写到WHERE子句中。
语句9:全外连接(FULL OUTER JOIN)
SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME
FROM ORDERS O FULL OUTER JOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID;
注意:MySQL是不支持全外的连接的,这里给出的写法适合Oracle和DB2。但是可以通过左外和右外求合集来获取全外连接的查询结果。下图是上面SQL在Oracle下执行的结果:
语句10:左外和右外的合集,实际上查询结果和语句9是相同的。
SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME
FROM ORDERS O LEFT OUTER JOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID
UNION
SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME
FROM ORDERS O RIGHT OUTER JOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID;
语句9和语句10的查询结果是相同的,如下:
四、联合连接(UNION JOIN):这是一种很少见的连接方式。
Oracle、MySQL均不支持,其作用是:找出全外连接和内连接之间差异的所有行。这在数据分析中排错中比较常用。也可以利用数据库的集合操作来实现此功能。
语句11:联合查询(UNION JOIN)例句,还没有找到能执行的SQL环境。
SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME
FROM ORDERS O UNION JOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID
语句12:语句11在DB2下的等价实现。还不知道DB2是否支持语句11呢!
SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME
FROM ORDERS O FULL OUTER JOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID
EXCEPT
SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME
FROM ORDERS O INNER JOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID;
语句13:语句11在Oracle下的等价实现。
SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME
FROM ORDERS O FULL OUTER JOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID
MINUS
SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME
FROM ORDERS O INNER JOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID;
查询结果如下:
五、自然连接(NATURAL INNER JOIN):说真的,这种连接查询没有存在的价值,既然是SQL2标准中定义的,就给出个例子看看吧。
自然连接无需指定连接列,SQL会检查两个表中是否相同名称的列,且假设他们在连接条件中使用,并且在连接条件中仅包含一个连接列。不允许使用ON语句,不允许指定显示列,显示列只能用*表示(ORACLE环境下测试的)。对于每种连接类型(除了交叉连接外),均可指定NATURAL。
下面给出几个例子。
语句14:
SELECT *
FROM ORDERS O NATURAL INNER JOIN CUSTOMERS C;
语句15:
SELECT *
FROM ORDERS O NATURAL LEFT OUTER JOIN CUSTOMERS C;
语句16:
SELECT *
FROM ORDERS O NATURAL RIGHT OUTER JOIN CUSTOMERS C;
语句17:
SELECT *
FROM ORDERS O NATURAL FULL OUTER JOIN CUSTOMERS C;
六、SQL查询的基本原理:两种情况介绍。
第一、单表查询:根据WHERE条件过滤表中的记录,形成中间表(这个中间表对用户是不可见的);然后根据SELECT的选择列选择相应的列进行返回最终结果。
第二、两表连接查询:对两表求积(笛卡尔积)并用ON条件和连接连接类型进行过滤形成中间表;然后根据WHERE条件过滤中间表的记录,并根据SELECT指定的列返回查询结果。
第三、多表连接查询:先对第一个和第二个表按照两表连接做查询,然后用查询结果和第三个表做连接查询,以此类推,直到所有的表都连接上为止,最终形成一个中间的结果表,然后根据WHERE条件过滤中间表的记录,并根据SELECT指定的列返回查询结果。
理解SQL查询的过程是进行SQL优化的理论依据。
七、ON后面的条件(ON条件)和WHERE条件的区别:
ON条件:是过滤两个链接表笛卡尔积形成中间表的约束条件。
WHERE条件:在有ON条件的SELECT语句中是过滤中间表的约束条件。在没有ON的单表查询中,是限制物理表或者中间查询结果返回记录的约束。在两表或多表连接中是限制连接形成最终中间表的返回结果的约束。
从这里可以看出,将WHERE条件移入ON后面是不恰当的。推荐的做法是:
- ON只进行连接操作,WHERE只过滤中间表的记录。
八、总结
连接查询是SQL查询的核心,连接查询的连接类型选择依据实际需求。如果选择不当,非但不能提高查询效率,反而会带来一些逻辑错误或者性能低下。下面总结一下两表连接查询选择方式的依据:
- 查两表关联列相等的数据用内连接。
- Col_L是Col_R的子集时用右外连接。
- Col_R是Col_L的子集时用左外连接。
- Col_R和Col_L彼此有交集但彼此互不为子集时候用全外。
- 求差操作的时候用联合查询。
多个表查询的时候,这些不同的连接类型可以写到一块。例如:
SELECT T1.C1,T2.CX,T3.CY
FROM TAB1 T1
INNER JOIN TAB2 T2 ON (T1.C1=T2.C2)
INNER JOIN TAB3 T3 ON (T1.C1=T2.C3)
LEFT OUTER JOIN TAB4 ON(T2.C2=T3.C3);
WHERE T1.X >T3.Y;
上面这个SQL查询是多表连接的一个示范。
计算机中傅里叶变换
- 傅里叶变换: 表示能将满足一定条件的某个函数表示成三角函数(正弦和/或余弦函数)或者它们的积分的线性组合。