四、 多表查询
1 什么是多表查询
多表查询:当查询的数据并不是来源一个表时,需要使用多表链接操作完成查询。根据 不同表中的数据之间的关系查询相关联的数据。
多表链接方式: 内连接:连接两个表,通过相等或不等判断链接列,称为内连接。在内连接中典型 的联接运算有 = 或 <> 之类的比较运算符。包括等值联接和自然联接 等值连接 非等值连接 自连接 SQL99:交叉链接(CROSS JOIN) SQL99:内连接(INNERJOIN) SQL99:自然链接(NATURALJOIN) 外连接:在两个表之间的连接,返回内连接的结果,同时还返回不匹配行的左(或
右)表的连接,称为左(或右)连接。返回内连接的结果, 同时还返回左和右连接, 称为全连接。 左外链接 右外链接 全外链接 子查询:当一个查询是另一个查询的条件时,称之为子查询。
2 笛卡尔乘积
2.1什么是笛卡尔乘积
笛卡尔乘积是指在数学中,两个集合 X 和 Y 的笛卡尓积(Cartesianproduct),又称直 积,表示为 X*Y,第一个对象是 X 的成员而第二个对象是 Y 的所有可能有序对的其中一 个成员。
2.2如何避免出现笛卡尔乘积
当一个连接条件无效或被遗漏时,其结果是一个笛卡尔乘积 (Cartesian product),其中 所有行的组合都被显示。第一个表中的所有行连接到第二个表中的所有行。一个笛卡尔乘积 会产生大量的行,其结果没有什么用。应该在 WHERE 子句中始终包含一个有效的连接条 件。
2.3示例 select * from employees,departments;
3 多表连接语法
3.1语法结构
使用一个连接从多个表中查询数据。
3.2定义连接
当数据从多表中查询时,要使用连接 (join) 条件。一个表中的行按照存在于相应列中
的值被连接到另一个表中的行。
3.3原则
• 在写一个连接表的 SELECT 语句时,在列名前面用表名或者表别名可以使语义清楚, 并且加快数据库访问。 • 为了连接 n 个表在一起,你最少需要 n-1 个连接条件。例如,为了连接 4 个表, 最少需要 3 个连接条件。
4 等值连接
4.1什么等值连接
等值连接也被称为简单连接 (simple joins) 或内连接 (inner joins)。是通过等号来判断连 接条件中的数据值是否相匹配。
4.2抉择矩阵 (decision matrix)
是通过行与列来分析一个查询的方式。 例如,如果你想显示同一个部门中所有名字为 Taylor 的雇员的名字和部门名称,可以 写出下面的决策矩阵: 投影列 源表 条件 last_name employees last_name=‘Taylor’ department_name departments employees.department_id = departments.department_id
4.2.1示例一
查询所有雇员名字以及他们所在的部门名称。
select last_name,department_name from employees , departments where employees.department_id = departments.department_id;
4.3使用 AND 操作符附加搜索条件
除连接之外,还可以要求用 WHERE 子句在连接中限制一个或多个表中的行。
4.3.1示例二
显示同一个部门中所有名字为 Taylor 的雇员的名字和部门号。select last_name,department_name from employees , departments where employees.department_id = departments.department_id and last_name = 'Taylor';
4.4使用表别名
使用表别名简化查询语句的长度。
4.4.1表别名
可以使用表别名代替表名。就象列别名给列另一个名字一样。表别名有助于保持 SQL 代码较小,因此使用的存储器也少。
4.4.2使用表别名原则
• 表别名最多可以有 30 个字符,但短一些更好。 • 如果在 FROM 子句中表别名被用于指定的表,那么在整个 SELECT 语句中都可以 使用表别名。 • 表别名应该是有意义的。 • 表别名只对当前的 SELECT 语句有效。
4.4.3示例
使用表别名方式改写显示同一个部门中所有名字为 Taylor 的雇员的名字和部门号。 select em.last_name, de.department_name from employees em,departments de where em.department_id = de.department_id and em.last_name = 'Taylor';
4.5多于两个表的连接
为了连接 n 个表,你最少需要 n-1 个连接条件。例如,为了连接 3 个表,最少需要 两个连接。
4.5.1示例一
查询每个雇员的 last name、department name 和 city(city 来源于 locations 表)。 select em.last_name,de.department_name ,lo.city - Registered at Name cheap.com from employees em,departments de,locations lo where em.department_id = de.department_id and de.location_id = lo.location_id;
4.5.2示例二
查询 Taylor 的雇员 ID、部门名称、和工作的城市。 select em.employee_id, em.last_name,de.department_name ,lo.city - Registered at Namecheap.com fromemployees em,departments de,locations lo where em.department_id = de.department_id and de.location_id = lo.location_id and em.last_name = 'Taylor';
5 非等值连接
5.1非等值连接
一个非等值连接是一种不使用相等(=)作为连接条件的查询。如!=、>、<、>=、<=、 BETWEEN AND 等都是非等值链接的条件判断。
5.2创建案例表 create table JOB_GRADES ( lowest_sal NUMBER, highest_sal NUMBER, gra VARCHAR2(10) )
5.3示例
查询所有雇员的薪水级别。
select em.last_name,em.salary,gr.gra from employees em ,job_grades gr where em.salary between gr.lowest_sal and gr.highest_sal;
6 自连接
6.1什么是自连接
使用一个表连接它自身的操作。
6.2示例
查询每个雇员的经理的名字以及雇员的名字。
select worker.last_name,manager.last_name from employees worker,employees manager where worker.manager_id = manager.employee_id;
7 外连接(OUTER JOIN)
7.1什么是外连接
外连接是指查询出符合连接条件的数据同时还包含孤儿数据。左外链接包含左表的孤儿 数据,右外连接包含右表的孤儿数据,全外连接包含两个表中的孤儿数据。
7.2孤儿数据(Orphan Data)
孤儿数据是指被连接的列的值为空的数据。
7.3外连接类型
左外(LEFT OUTER JOIN):包含左表的孤儿数据。 右外(RIGHT OUTER JOIN):包含右表的孤儿数据。 全外(FULL OUTER JOIN):包含两个表中的孤儿数据。
7.4 SQL 99 中的外连接
SQL 99 外连接语法格式: 用 LEFT OUTER JOIN | RIGHT OUTER JOIN | FULL OUTER JOIN 定义连接类型,用 ON 子句创建连接条件。
7.4.1左外链接(LEFT OUTER JOIN)
7.4.1.1 示例
用左外链接查询雇员名字以及他们所在的部门名称,包含那些没有部门的雇员。 selecte.last_name,d.department_namefrom employees e left outer join departments d on e.department_id = d.department_id;
7.4.2右外链接(RIGHT OUTER JOIN)
7.4.2.1 示例
用右外链接查询雇员名字以及他们所在的部门名称,包含那些没有雇员的部门。 selecte.last_name,d.department_namefrom employees e right outer join departments d on e.department_id = d.department_id;
7.4.3全外链接(FULL OUTER JOIN)
7.4.3.1 示例
查询所有雇员和部门,包含那些没有雇员的部门以及没有部门的雇员。 selecte.last_name,d.department_name from employees e full outer join departments d on e.department_id = d.department_id;
7.5 Oracle 扩展的外连接
在 Oracle 数据库中对外连接中的左外与右外连接做了扩展,可以简化外连接的语法。 通过在连接条件的后侧使用(+)来表示是否显示孤儿数据,有(+)表示不显示孤儿数据而另一 侧则显示孤儿数据。但是该种写法仅能在 Oracle 数据库中使用。
7.5.1示例一
查询雇员名字以及他们所在的部门名称,包含那些没有雇员的部门。 selecte.last_name,d.department_name from employees e ,departments d where e.department_id(+) = d.department_id;
7.5.2示例二
查询雇员名字以及他们所在的部门名称,包含那些没有部门的雇员。
selecte.last_name,d.department_name from employees e ,departments d where e.department_id = d.department_id(+);
8 SQL 99 中的交叉连接
•CROSS JOIN 子句导致两个表的交叉乘积 • 该连接和两个表之间的笛卡尔乘积是一样的
8.1示例
查询 Employees 表与 Departments 表的笛卡尔乘积。 select * from employees cross join departments;
9 SQL 99 中的自然连接(NATURAL JOIN)
•NATURAL JOIN 子句基于两个表之间有相同名字的所有列。 • 它从两个表中选择在所有的匹配列中有相等值的行。 • 如果有相同名字的列的数据类型不同,返回一个错误。
9.1使用自然连接需要注意
1.如果做自然连接的两个表的有多个字段都满足有相同名称个类型,那么他们会被作为 自然连接的条件。
2.如果自然连接的两个表仅是字段名称相同,但数据类型不同,那么将会返回一个错误。
3.由于 oracle 中可以进行这种非常简单的 natural join,我们在设计表时对具有相同含 义的字段需要考虑到使用相同的名字和数据类型。
9.2示例
查询部门 ID,部门名称以及他们所在的城市。 select
d.department_id,d.department_name,l.city from departments d natural join locations l;
9.3用 USING 子句创建连接
• 当有多个列匹配时,用 USING 子句匹配唯一的列。 • 如果某列在 USING 中使用,那么在引用该列时不要使用表名或者别名。 •NATURAL JOIN 和 USING 子句是相互排斥的。
9.3.1示例
查询 location_id 为 1800 的部门名称以及他们所在的城市名称,指定 location_id 为连接
列。
select d.department_name,l.city from departments d join locations l using(location_id) where location_id = 1800;
10 SQL 99 中的内连接(INNER JOIN)
内连接(INNER JOIN): 内连接通过 INNER JOIN 来建立两个表的连接。在内连接中使 用 INNER JOIN 作为表的连接,用 ON 子句给定连接条件。INNER JOIN 语句在性能上其他 语句没有性能优势。
10.1示例
查询雇员 id 为 202 的雇员名字,部门名称,以及工作的城市。
等值连接: select e.last_name,d.department_name,l.city from employees e,departments d ,locations l where e.department_id = d.department_id and d.location_id = l.location_id and e.employee_id = 202;
内连接: select e.last_name,d.department_name,l.city from employees e inner join departments d on e.department_id = d.department_id inner join locations l on d.location_id = l.location_id where e.employee_id = 202;
在内连接中使用 USING 子句定义等值连接
select e.last_name,d.department_name,l.city from employees e inner join departments d using(department_id) inner join locations l using(location_id) where e.employee_id = 202;
11 多表查询小节练习
11.1写一个查询显示所有雇员的
last name、department number、and department_name。
答案(等值): select e.last_name,e.department_id,d.department_na me from employees e ,departments d where e.department_id = d.department_id;
答案(内连接): select e.last_name,e.department_id,d.department_name from employees e inner join departments d on(e.department_id = d.department_id);
11.2查询部门编号 80 中的所有工作岗位的唯一列表,在输出中包括部门编号、地点编号。
答案: select distinct e.job_id,d.location_id from employees e,departments d where e.department_id = d.department_id and e.department_id = 80;
11.3写 一 个 查 询 显 示 所 有 有 佣 金 的 雇 员 的 last name、 department name、location ID 和城市。
答案: select e.last_name,d.department_name,l.location_id, l.city from employees e,departments d,locations l where e.department_id = d.department_id and d.location_id = l.location_id and e.commission_pct is not null;
11.4显示所有在其 last names 中有一个小写 a 的雇员的 last name 和 department name。
答案: selecte.last_name,d.department_name from employees e,departments d where
e.department_id = d.department_id and e.last_name like '%a%';
11.5使用内连接写一个查询显示那些工作在 Toronto 的所有雇 员 的 last name 、 job 、 department number 和 department name。
答案: select e.last_name,e.job_id,e.department_id,d.depatment_name from employees e inner join departments d on(e.department_id = d.department_id) inner join locations l on(d.location_id = l.location_id) where lower(l.city) ='toronto';
11.6显示雇员的 lastname 和 employeenumber 连同他们的 经理的 last name 和 manager number。列标签分别为 Employee、Emp#、Manager 和 Mgr#。
答案: select emp.last_name "Employee",emp.employee_id "Emp#",manager.last_name "Manager" ,manager.employee_id "Mar#" from employees emp ,employees manager where
emp.manager_id = manager.employee_id;
11.7查询所有雇员的经理包括 King,他没有经理。显示雇员的 名字、雇员 ID、经理名、经理 ID、用雇员号排序结果。
答案: select emp.last_name "Employee",emp.employee_id "Emp#",manager.last_name "Manager" ,manager.employee_id "Mar#" from employees emp left outer join employees manager on(emp.manager_id = manager.employee_id);
11.8创建一个查询显示所有与被指定雇员工作在同一部门的雇员 (同事) 的 last names、department numbers。给每列一个适当的标签。
答案: select e.last_name, e.department_id from employees e , employees c where c.department_id = c.department_id and e.employee_id <> c.employee_id;
11.9显示 JOB_GRADES 表的结构。创建一个查询显示所有雇员 的 name、job、department name、salary 和 grade。
答案:
select e.last_name,e.job_id,d.department_name,e.sa lary,j.gra from employees e,departments d,job_grades j where e.department_id = d.department_id and e.salary between j.lowest_sal and j.highest_sal;
11.10 创建一个查询显示那些在雇员 Davies 之后入本公司工作 的雇员的 name 和 hiredate
答案: select e.last_name,e.hire_date from employees e ,employees d where d.last_name = 'Davies' and d.hire_date < e.hire_date;
11.11 显示所有雇员的 names 和 hiredates,他们在他们的经理之前进入本公司,连同他们的经理的名字和受雇日期一起显示。列标签分别为 Employee、Emp Hired、Manager 和 Mgr Hired。
答案: select e.last_name,e.hire_date,m.last_name,m.hire_ date from employees e,employees m where e.manager_id = m.employee_id and e.hire_date < m.hire_date;
五、 组函数(聚合函数)
1 组函数介绍
1.1什么是组函数
组函数操作行集,给出每组的结果。组函数不象单行函数,组函数对行的集合进行操作, 对每组给出一个结果。这些集合可能是整个表或者是表分成的组。
1.2组函数与单行函数区别
单行函数对查询到每个结果集做处理,而组函数只对分组数据做处理。 单行函数对每个结果集返回一个结果,而组函数对每个分组返回一个结果。
1.3组函数的类型
•AVG 平均值 •COUNT 计数 •MAX 最大值 •MIN 最小值 •SUM 合计
1.4组函数的语法
1.5使用组函数的原则
• 用于函数的参数的数据类型可以是 CHAR、VARCHAR2、NUMBER 或 DATE。 • 所有组函数忽略空值。为了用一个值代替空值,用 NVL、NVL2 或 COALESCE 函
数。
2 组函数的使用
2.1使用 AVG 和 SUM 函数
AVG(arg)函数:对分组数据做平均值运算。 arg:参数类型只能是数字类型。
SUM(arg)函数:对分组数据求和。 arg:参数类型只能是数字类型。
2.1.1示例
求雇员表中的的平均薪水与薪水总额。 select avg(salary) ,sum(salary) from employees;
2.2使用 MIN 和 MAX 函数
MIN(arg)函数:求分组中最小数据。 arg:参数类型可以是字符、数字、日期。
MAX(arg)函数:求分组中最大数据。 arg:参数类型可以是字符、数字、日期。
2.2.1示例
求雇员表中的最高薪水与最低薪水。 select min(salary),max(salary) from employees;
2.3使用 COUNT 函数
COUNT 函数:返回一个表中的行数。
COUNT 函数有三种格式: •COUNT(*) •COUNT(expr) •COUNT(DISTINCT expr)
2.3.1 COUNT(*)
返回表中满足 SELECT 语句标准的行数,包括重复行,包括有空值列的行。如果 WHERE 子句包括在 SELECT 语句中,COUNT(*) 返回满足 WHERE 子句条件的行数。
2.3.1.1 示例一
返回查询结果的总条数。 select count(*) from employees;
2.3.2 COUNT(expr)函数
返回在列中的由 expr 指定的非空值的数。
2.3.2.1 示例二 显示部门 80 中有佣金的雇员人数。 select count(commission_pct) from employees e where e.department_id = 80;
2.3.3 COUNT (DISTINCT expr):
使用 DISTINCT 关键字禁止计算在一列中的重复值。
2.3.3.1 示例三 显示 EMPLOYEES 表中不重复的部门数。 select count(distinct department_id) from employees ;
2.4组函数和 Null 值
所有组函数忽略列中的空值。 在组函数中使用 NVL 函数来处理空值。
2.4.1示例一
计算有佣金的员工的佣金平均值。 select avg(commission_pct) from employees;
2.4.2示例二
计算所有员工的佣金的平均值。 select avg(nvl (commission_pct,0)) from employees;
3 创建数据组(GROUP BY)
3.1什么是创建数据组
可以根据需要将查询到的结果集信息划分为较小的组,用 GROUP BY 子句实现。
3.2 GROUP BY 子句语法
3.3使用分组原则
• 如果在 SELECT 子句中包含了组函数,就不能选择单独的结果,除非单独的列出现 在 GROUP BY 子句中。如果未能在 GROUP BY 子句中包含一个字段列表,你会收到一个 错误信息。 • 使用 WHERE 子句,你可以在划分行成组以前过滤行。 • 在 GROUP BY 子句中必须包含列。 • 在 GROUP BY 子句中你不能用列别名。 • 默认情况下,行以包含在 GROUP BY 列表中的字段的升序排序。可以用 ORDER BY 子句覆盖这个默认值。
3.4 GROUP BY 子句的使用
我们可以根据自己的需要对数据进行分组,在分组时,只要将需要做分组的列的列名添 加到 GROUP BY 子句后侧就可以。GROUP BY 列不必在 SELECT 列表中。
3.4.1示例一
求每个部门的平均薪水。 select department_id , avg(salary) from employees e group by e.department_id;
3.5多于一个列的分组
3.5.1示例一
显示在每个部门中付给每个工作岗位的合计薪水的报告。 select department_id,job_id, sum(salary)from employees group by
department_id,job_id order by department_id;
3.6 GROUP BY 子句的执行顺序
先进行数据查询,在对数据进行分组,然后执行组函数。
3.7非法使用 Group 函数的查询
• 在 SELECT 列表中的任何列必须在 GROUP BY 子句中。 • 在 GROUP BY 子句中的列或表达式不必在 SELECT 列表中。
3.8约束分组结果
3.8.1什么是 HAVING 子句
HAVING 语句通常与 GROUP BY 语句联合使用,用来过滤由 GROUP BY 语句返回的记 录集。 HAVING 语句的存在弥补了 WHERE 关键字不能与聚合函数联合使用的不足。
3.8.2 HAVING 子句语法
3.8.3示例一
显示那些最高薪水大于 $10,000 的部门的部门号和最高薪水。 select e.department_id,max(e.salary)from employees e group by e.department_id having max(e.salary) > 10000; 3.8.4示例二
查询那些最高薪水大于 $10,000 的部门的部门号和平均薪水。 selecte.department_id,avg(e.salary)from employees e group by e.department_id having max(e.salary) > 10000;
3.9嵌套组函数
在使用组函数时我们也可以根据需要来做组函数的嵌套使用。
3.9.1示例
显示部门中的最大平均薪水。 select max(avg(e.salary)) from employees e group by e.department_id;
4 组函数小节练习
4.1组函数在多行上计算,对每个组产生一个结果。True/False
答案:True
4.2组函数在计算中包含空值。True/False
答案:False 组函数会忽略空值,如果需要空值参与计算,需要使用 nvl 函数处理空值。
4.3在分组计算中,WHERE 子句对行的限制在计算的前面。 True/False
答案:True
4.4显示所有雇员的最高、最低、合计和平均薪水,列标签分别为: Maximum、Minimum、Sum 和 Average。四舍五入结果为最近的整数。
答案: select max(salary),min(salary),sum(salary),avg(sal ary) from employees;
4.5修改上题显示每中工作类型的最低、最高、合计和平均薪水。
答案: select max(salary),min(salary),sum(salary),avg(sal ary) from employees group by job_id;
4.6写一个查询显示每一工作岗位的人数。
答案: select job_id, count(*) from employees group by job_id;
4.7确定经理人数,不需要列出他们,列标签是 Number of Managers。
答案: select count(distinct manager_id) from
employees ;
4.8写一个查询显示最高和最低薪水之间的差。列标签是 DIFFERENCE。
答案: select max(salary) - min(salary) from employees;
4.9显示经理号和经理付给雇员的最低薪水。排除那些经理未知的 人。排除最低薪水小于等于 $6,000 的组。按薪水降序排序
输出。
答案: select e.manager_id ,min(e.salary) from employees e where e.manager_id is not null group by e.manager_id having min(e.salary) > 6000 order by min(e.salary) desc;
4.10写一个查询显示每个部门的名字、地点、人数和部门中所有
雇员的平均薪水。四舍五入薪水到两位小数。
答案: select d.department_name,d.location_id,count(*) ,a vg(e.salary) from employees e ,departments d where e.department_id = d.department_id group by d.department_name ,d.location_id;
4.11创建一个查询显示雇员总数,和在 2001、2002、2003 和
受雇的雇员人数。创建适当的列标题。
答案: select count(*) total,sum(decode(to_char(hire_date,'yyyy'), '2000',1,0))"2000" ,sum(decode(to_char(hire _date,'yyyy'),'2001',1,0))"2001",sum(decode (to_char(hire_date,'yyyy'),'2002',1,0))"200 2",sum(decode(to_char(hire_date,'yyyy'),'20 03',1,0))"2003" from employees e;
4.12创建一个混合查询显示工作岗位和工作岗位的薪水合计,并 且合计部门 20、50、80 和 90 的工作岗位的薪水。给每
列一个恰当的列标题。
答案: select job_id,sum(salary),sum(decode(department_id, 20,salary))"Dep 20",sum(decode(department_id,50,salary))"Dep 50",sum(decode(department_id,80,salary))"Dep 80" ,sum(decode(department_id,90,salary))"Dep 90"from employees group by job_id;