文章目录
- 多表查询是什么?
- 一、笛卡尔积(或交叉连接)
- 二、多表查询分类
- 分类1:等值连接 vs 非等值连接
- 等值连接
- 非等值连接
- 分类2:自连接 vs 非自连接
- 分类2:内连接 vs 外连接
- 三、SQL99
- SQL99 实现 内连接
- SQL99 实现 外连接(OUTER JOIN)
- 左外连接(LEFT OUTER JOIN)
- 右外连接(RIGHT OUTER JOIN)
- 满外连接(FULL OUTER JOIN)
- UNION 和 UNION ALL的使用
- 四、7种SQL JOINS的实现
- 1. 内连接
- 2. 左外连接
- 3. 右外连接
- 4.左外连接 但查NULL(A - A∩B)
- 5. 右外联接 但查Null(B - A∩B)
- 6.满外连接 A∪B
- 7. 满外连接 但是查的是Null -> (A - A∩B) ∪ (B - A∩B)
- 五、SQL99 语法新特性
- 自然连接 NATURAL
- USING连接
- 总结
- 综合练习
多表查询是什么?
也称为关联查询,指两个或更多个表一起完成查询操作。
前提条件:
这些一起查询的表之间是有关系的(一对一、一对多),它们之间一定是有关联字段,这个关联字段可能建立了外键,也可能没有建立外键。
比如:员工表和部门表,这两个表依靠“部门编号”进行关联。
以下错误的实现方式:
#案例:查询员工的姓名及其部门名称
SELECT last_name, department_name
FROM employees, departments;
总共查出:2889条数据
但是:
107 * 27 == 2889 也就是 每个员工和每一个部门都匹配了 一次
以上为:笛卡尔积的错误
一、笛卡尔积(或交叉连接)
笛卡尔积也称为
交叉连接
,英文是CROSS JOIN
作用就是可以把任意表进行连接,即使这两张表不相关。
针对以上错误案例的分析: 为了避免笛卡尔积, 可以在 WHERE 加入有效的连接条件。
语法变为:
SELECT table1.column, table2.column
FROM table1, table2
WHERE table1.column1 = table2.column2; #连接条件
正确案例:
SELECT last_name,department_name
FROM employees,departments
WHERE employees.department_id = departments.department_id;
二、多表查询分类
分类1:等值连接 vs 非等值连接
等值连接
1.列名加前缀
在表中有相同列时,在列名之前加上表名前缀(也就是把两张表的ID关联起来)
如果查询语句中出现了多个表中都存在的字段,则必须指明此字段所在的表。
SELECT employees.last_name,departments.department_name,departments.department_id
FROM employees,departments
WHERE employees.department_id = departments.department_id;
建议:从sql优化的角度,建议多表查询时,每个字段前都指明其所在的表。
2.表起别名
也可以给表起别名,如果给表起了别名,一旦在SELECT或WHERE中使用表名的话,则必须使用表的别名,而不能再使用表的原名。
SELECT e.last_name,d.department_name,d.department_id
FROM employees e,departments d
WHERE e.department_id = d.department_id;
3.连接多个表 用AND
如果有n个表实现多表的查询,则需要至少n-1个连接条件
#练习:查询员工的employee_id,last_name,department_name,city
SELECT e.last_name,d.department_name,l.city,e.department_id,l.location_id
FROM employees e,departments d,locations l
WHERE e.department_id = d.department_id
AND d.location_id = l.location_id;
非等值连接
#标出员工表中的员工工资 在工作等级表中对应的薪资等级
SELECT e.employee_id,e.last_name,e.salary,j.grade_level
FROM employees e,job_grades j
where e.salary BETWEEN j.lowest_sal AND j.highest_sal;
分类2:自连接 vs 非自连接
当table1和table2本质上是同一张表,只是用取别名的方式虚拟成两张表以代表不同的意义。
然后两个表再进行内连接,外连接等查询。
(同一张表,不同的查询条件)
emp表的管理员ID 对应 员工ID 所以 使用mgr表中的员工与其关联!
# 员工表员工ID 与 员工表管理ID 是同一个 通过where 关联ID
-- CONCAT 合并函数 --
SELECT CONCAT(e.last_name,' work for ',m.last_name)
FROM employees e,employees m
WHERE e.manager_id = m.employee_id;
SELECT emp.employee_id AS "员工ID",emp.last_name AS "员工名字",mgr.manager_id AS "管理ID",mgr.last_name AS "管理名字"
FROM employees emp,employees mgr
WHERE emp.manager_id = mgr.employee_id;
#练习:查询出last_name为 ‘Chen’ 的员工的 manager 的信息。
SELECT emp.employee_id,emp.last_name AS "员工名字",mgr.last_name as "管理员名字"
FROM employees emp,employees mgr
WHERE emp.manager_id = mgr.employee_id
AND emp.last_name like '%Chen%';
分类2:内连接 vs 外连接
概念:
- 内连接: 合并具有同一列的两个以上的表的行, 结果集中不包含一个表与另一个表不匹配的行
- 外连接: 两个表在连接过程中除了返回满足连接条件的行以外还返回左(或右)表中不满足条件的行 ,这种连接称为左(或右) 外连接。没有匹配的行时, 结果表中相应的列为空(NULL)。
内连接: 查出条件匹配的全部数据,那么有不匹配的数据,就不会显示出来了
举例:
SELECT employee_id,department_name
FROM employees e,departments d
WHERE e.`department_id` = d.department_id; -- 查出106条数据 --
可是 员工数据 有107条,也就是说 还有一个人没有对应的部门(也就是没有匹配到数据)
如果我们需要显示这个不匹配的数据,就是用外连接,让不匹配的显示为空(Null)
外连接分为:
- 左外连接
- 右外连接
- 满外连接
SQL92 实现外连接:
-
在 SQL92 中采用(+)代表从表所在的位置。即左或右外连接中,(+) 表示哪个是从表。
-
Oracle 对 SQL92 支持较好,而 MySQL 则不支持 SQL92 的外连接。
#左外连接 SELECT last_name,department_name FROM employees ,departments WHERE employees.department_id = departments.department_id(+);#右外连接 SELECT last_name,department_name FROM employees ,departments WHERE employees.department_id(+) = departments.department_id;
-
而且在 SQL92 中,只有左外连接和右外连接,没有满(或全)外连接。
三、SQL99
以上代码都使用的SQL92
SQL99使用 JOIN …ON 的方式实现多表的查询
SQL99 实现 内连接
语法:
SELECT table1.column, table2.column,table3.column
FROM table1JOIN table2 ON table1 和 table2 的连接条件JOIN table3 ON table2 和 table3 的连接条件
# 查询员工ID与其对应的部门名字
SELECT e.department_id,d.department_name
FROM employees e JOIN departments d
ON e.department_id = d.department_id;
# 查询员工名字,部门,城市
SELECT e.first_name,d.department_name,l.city
FROM employees e JOIN departments d ON e.department_id = d.department_id JOIN locations l ON d.location_id = l.location_id;
SQL99 实现 外连接(OUTER JOIN)
左外连接(LEFT OUTER JOIN)
语法:
#实现查询结果是A
SELECT 字段列表
FROM A表 LEFT JOIN B表
ON 关联条件
WHERE 等其他子句;
左外连接,以左边的表(A表)为主,完整的显示A表(包括不匹配的数据(为空数据也显示))
举例:
# 显示员工名字与对应部门
SELECT e.first_name,d.department_name
FROM employees e
LEFT JOIN departments d
ON e.department_id = d.department_id;
员工有107条数据,包括为Null的,所以使用左外连接实现全显示
右外连接(RIGHT OUTER JOIN)
语法:
#实现查询结果是B
SELECT 字段列表
FROM A表 RIGHT JOIN B表
ON 关联条件
WHERE 等其他子句;
举例:
SELECT e.first_name,d.department_name
FROM employees e
RIGHT JOIN departments d
ON e.department_id = d.department_id;
部门表中,还有很多部门没有员工,即为空
需要注意的是,LEFT JOIN 和 RIGHT JOIN 只存在于 SQL99 及以后的标准中,在 SQL92 中不存在,只能用 (+) 表示。
满外连接(FULL OUTER JOIN)
- 满外连接的结果 = 左右表匹配的数据 + 左表没有匹配到的数据 + 右表没有匹配到的数据。
- SQL99是支持满外连接的。使用FULL JOIN 或 FULL OUTER JOIN来实现。
- 需要注意的是,MySQL不支持FULL JOIN,但是可以用 LEFT JOIN UNION RIGHT join代替。
UNION 和 UNION ALL的使用
UNION
:会执行去重操作
UNION ALL
:不会执行去重操作
结论:如果明确知道合并数据后的结果数据不存在重复数据,或者不需要去除重复的数据,
则尽量使用UNION ALL
语句,以提高数据查询的效率。
语法:
SELECT column,... FROM table1
UNION [ALL]
SELECT column,... FROM table2
举例:
# 查询部门编号>90或邮箱包含a的员工信息
-- 方式一 --
SELECT *
FROM employees
WHERE department_id > 90
OR email LIKE '%a%';
-- 方式二 --
SELECT * FROM employees WHERE email LIKE '%a%'
UNION
SELECT * FROM employees WHERE department_id > 90 ;
四、7种SQL JOINS的实现
1. 内连接
# 举例:查询员工ID名字,部门名字
SELECT e.employee_id,e.last_name,d.department_name
FROM employees e JOIN departments d ON e.department_id = d.department_id;
# 语法格式
SELECT 列名
FROM 表名A
JOIN 要连接的表B
ON AB表关联字段;
2. 左外连接
SELECT e.employee_id,e.last_name,d.department_name
FROM employees e LEFT JOIN departments d ON e.department_id = d.department_id;
#语法格式:
SELECT 列名
FROM 表名A
LEFT JOIN 表名B
ON AB关联字段;
3. 右外连接
# 右外连接
# 举例:查询员工ID名字,部门名字
SELECT e.employee_id,e.last_name,d.department_name
FROM employees e RIGHT JOIN departments d ON e.department_id = d.department_id;
#语法格式:
SELECT 列名
FROM 表名A
RIGHT JOIN 表名B
ON AB关联字段;
4.左外连接 但查NULL(A - A∩B)
A 减去 A 交 B
# 左外连接但是查的是为Null的数值(也就是把不匹配的数据打印)
# 举例:查询员工ID名字,部门名字 为Null的
SELECT e.employee_id,e.last_name,d.department_name
FROM employees e LEFT JOIN departments d ON e.department_id = d.department_id
WHERE d.department_id IS NULL;
#语法格式:
SELECT 列名
FROM 表名A
LEFT JOIN 表名B
ON AB关联字段
WHERE B.关联字段 IS NULL; -- 过滤条件:把B表为空的显示 --
5. 右外联接 但查Null(B - A∩B)
SELECT e.employee_id,e.last_name,d.department_name
FROM employees e RIGHT JOIN departments d ON e.department_id = d.department_id
WHERE ISNULL(e.department_id);
#语法格式:
SELECT 列名
FROM 表名A
RIGHT JOIN 表名B
ON AB关联字段
WHERE A.关联字段 IS NULL;-- A表为空的显示 --
6.满外连接 A∪B
左外连接 + 右外连接
SELECT e.employee_id,e.last_name,d.department_name
FROM employees e LEFT JOIN departments d ON e.department_id = d.department_id
UNION ALL -- 不去重 --
SELECT e.employee_id,e.last_name,d.department_name
FROM employees e RIGHT JOIN departments d ON e.department_id = d.department_id;
# 语法格式:
SELECT 列名 FROM A表 LEFT JOIN B表 ON AB关联字段
UNION (或是UNION ALL)
SELECT 列名 FROM A表 RIGHT JOIN B表 ON AB关联字段;
7. 满外连接 但是查的是Null -> (A - A∩B) ∪ (B - A∩B)
左 + 右 但是 只要不匹配数据 即为空数据
SELECT e.employee_id,e.last_name,d.department_name
FROM employees e LEFT JOIN departments d ON e.department_id = d.department_id
WHERE d.department_id IS NULL
UNION ALL
SELECT e.employee_id,e.last_name,d.department_name
FROM employees e RIGHT JOIN departments d ON e.department_id = d.department_id
WHERE e.employee_id IS NULL;
# 语法格式:
SELECT 列名 FROM A表 LEFT JOIN B表 ON AB关联字段
WHERE B表关联字段 IS NULL;
UNION (或是UNION ALL)
SELECT 列名 FROM A表 RIGHT JOIN B表 ON AB关联字段;
WHERE A表关联字段 IS NULL;
关联字段的表为空的全打印,不为空的过滤
五、SQL99 语法新特性
自然连接 NATURAL
NATURAL JOIN
用来表示自然连接。我们可以把自然连接理解为 SQL92 中的等值连接。它会帮你自动查询两张连接表中所有相同的字段
,然后进行等值连接
。
#sql92
# 查找员工ID,名字,部门,名字
# 关联两表 中的 部门ID 与 管理ID
SELECT employee_id,last_name,department_name
FROM employees e JOIN departments d
ON e.`department_id` = d.`department_id`
AND e.`manager_id` = d.`manager_id`;# sql99 自然连接
# 即把两表中所有的相同字段都关联了
SELECT employee_id,last_name,department_name
FROM employees e NATURAL JOIN departments d;
USING连接
使用USING
指定数据表里的同名字段
进行等值连接。但是只能配合JOIN
一起使用。
简化了代码:
# sql92 中也是 简化where代码
SELECT employee_id,last_name,department_name
FROM employees e ,departments d
WHERE e.department_id = d.department_id;
JOIN...USING
与自然连接 NATURAL JOIN
不同的是,USING
指定了具体的相同的字段名称,你需要在 USING
的括号 () 中填入要指定的同名字段
SELECT employee_id,last_name,department_name
FROM employees e JOIN departments d
USING (department_id);
总结
表连接的约束条件可以有三种方式:WHERE
, ON
, USING
WHERE
:适用于所有关联查询ON
:只能和JOIN一起使用,只能写关联条件。虽然关联条件可以并到WHERE中和其他条件一起写,但分开写可读性更好。USING
:只能和JOIN一起使用,而且要求两个关联字段在关联表中名称一致,而且只能表示关联字段值相等
#关联条件
#把关联条件写在where后面
SELECT last_name,department_name
FROM employees,departments
WHERE employees.department_id = departments.department_id;#把关联条件写在on后面,只能和JOIN一起使用
SELECT last_name,department_name
FROM employees INNER JOIN departments
ON employees.department_id = departments.department_id;SELECT last_name,department_name
FROM employees CROSS JOIN departments
ON employees.department_id = departments.department_id;SELECT last_name,department_name
FROM employees JOIN departments
ON employees.department_id = departments.department_id;#把关联字段写在using()中,只能和JOIN一起使用
#而且两个表中的关联字段必须名称相同,而且只能表示=
#查询员工姓名与基本工资
SELECT last_name,job_title
FROM employees INNER JOIN jobs USING(job_id);#n张表关联,需要n-1个关联条件
#查询员工姓名,基本工资,部门名称
SELECT last_name,job_title,department_name FROM employees,departments,jobs
WHERE employees.department_id = departments.department_id
AND employees.job_id = jobs.job_id;SELECT last_name,job_title,department_name
FROM employees INNER JOIN departments INNER JOIN jobs
ON employees.department_id = departments.department_id
AND employees.job_id = jobs.job_id;
注意:
我们要控制连接表的数量
。多表连接就相当于嵌套 for 循环一样,非常消耗资源,会让 SQL 查询性能下降得很严重,因此不要连接不必要的表。在许多 DBMS 中,也都会有最大连接表的限制。
【强制】超过三个表禁止 join。需要 join 的字段,数据类型保持绝对一致;多表关联查询时, 保证被关联的字段需要有索引。
说明:即使双表 join 也要注意表索引、SQL 性能。
来源:阿里巴巴《Java开发手册》