大学数据库的设计模式:
【学院】department ( dept_name, building, budget )
【教师】instructor ( id, name, dept_name, salary )
【课程段】section ( course_id, sec_id, semester, year, building, room_number, time_slot_id )
【教师授课安排】teaches ( id, course_id, sec_id, semester, year )
【学生】student ( id, name, dept_name, tot_cred )
【学生课表】takes ( id, course_id, sec_id, semester, year, grade )
【课室】classroom ( building, room_number, capacity )
【上课时段】time_slot ( time_slot_id, day, start_time, end_time )
【课程】course ( course_id, title, dept_name, credits )
【先决条件(课程)】prereq ( course_id, prereq_id )
1、SQL 查询的基本结构
SQL 查询的基本结构由三个子句构成:select、from 和 where。查询的输入是在 from 子句中列出的关系,在这些关系上进行 where 和 select 子句中指定的运算,然后产生一个关系作为结果。
(1)单关系查询
1、找出所有教师的名字
select name
from instructor;
2、找出所有老师所在系名
select dept_name
from instructor;
// 因为不同老师可能来自同个系,所以会得到重复的结果,为了去重,SQL 改为
select distinct dept_name
from instructor; // 每个系最多只出现一次
// 如果想显式指定使用关键字 all
select all dept_name
from instructor;
- select 子句可带含有 +、-、、/ 运算符的算术表达式*
3、显示给每位教师增长 10% 的工资的结果。
select id, name, dept_name, salary * 1.1
from instructor;
- where 子句允只选出在 from 子句的结果关系中满足特定谓词的元组。
4、找出所有在 Computer Science 系并且工资超过 70000 美元的教师的姓名。
select name
from instructor
where dept_name = 'Comp.Sci.' and salary > 70000;
(2)多关系查询
笛卡尔积、自然连接
5、找出所有教师的姓名,以及他们所在系的名字和系所在建筑的名称。
// 笛卡尔积
select name, dept_name, building
from instructor, department
where instructor.dept_name = department.dept_name;
// 自然连接
select name, dept_name, building
from instructor nature join department;
6、对于大学中所有讲授课程的老师,找出他们的名字以及所讲述的所有课程标识。
// 笛卡尔积
select name, course_id
from instructor, teaches
where instructor.id = teaches.id;
// 自然连接
select name, course_id
from instructor nature join teaches;
7、列出教师的名字以及他们所讲授课程的名称。
select name, title
from (instructor nature join teaches) join course using(course_id);
select name, title
from instructor nature join teaches, course
where teaches.course_id = course.course_id;
2、附加的基本运算
(1)更名运算
使用 as 关键字,常用于属性更名、表简称等。
// 未使用更名前的 sql
select name, course_id
from instructor, teaches
where instructor.id = teaches.id;
// 为属性更名,以更好地表达其含义
select name as instructor_name, course_id
from instructor, teaches
where instrcutor.id = teaches.id;
// 为表更名,方便书写 sql
select T.name, S.course_id
from instructor as T, teaches as S
where T.id = S.id;
有时需要把一个关系跟它自身进行笛卡尔积运算,重命名适合书写这类 SQL
8、找出满足下面条件的所有教师的姓名,他们的工资至少比 Biology 系某一个教师的工资要高。
select distinct T.name
from instructor as T, instructor as S
where S.dept_name = 'Biology' and T.salary > S.salary;
(2)字符串运算
比较、串联(使用 "||")、提取子串、计算字符串长度、大小写转换(upper(s)、lower(s))、去掉字符串后面的空格(trim(s))。
模糊匹配(使用 like 关键字):
- 百分号(%):匹配任意子串。
- 下划线(_):匹配任意一个字符。
9、找出所在建筑名称中包含子串 ‘Waston’ 的所有系名。
select dept_name
from department
where dept_name like '%Waston%';
(3)排列元组的显示次序
使用 order by 关键字(asc 升序,desc 降序),让查询结果中元组按排列顺序显示。
10、按字母顺序列出在 Physics 系的所有教师。
select name
from instructor
where dept_name = 'Physics'
order by name;
11、按工资降序,名字字母升序输出所有教师的信息。
select *
from instructor
order by salary desc, name asc;
(4)where 子句谓词
区间条件查询,使用 between ... and ... 关键字
12、找出工资在 90000 美元和 100000 美元之间的教师的姓名。
select name
from instructor
where salary between 90000 and 100000;
select name
from instructor
where salary >= 90000 and salary <= 100000;
13、查找 Biology 系讲授了课程的所有教师的姓名和他们所讲授的课程。
select name, course_id
from instructor, teaches
where dept_name = 'Biology' and instructor.id = teaches.id;
select name, course_id
from instructor, teaches
where (instructor.id, dept_name) = (teaches.id, 'Biology');
// 使用自然连接
select name, course_id
from instructor nature join teaches
where dept_name = 'Biology';
3、集合运算
并集:使用 union 关键字
交集:使用 intersect 关键字
差集:使用except 关键字
* 在2009年秋季学期开设的所有课程的集合。
select course_id
from section
where semester = 'Fall' and year = 2009;
* 在2010年春季学期开设的所有课程的集合。
select course_id
from section
where semester = 'Spring' and year = 2010;
(1)并运算
14、找出在2009年秋季开课,或者在2010年春季开课或两学期都开课的所有课程。
// 使用集合运算的写法
(select course_id
from section
where semester = 'Fall' and year = 2009)
union
(select course_id
from section
where semester = 'Spring' and year = 2010);
Ps.union 会自动去重,如果想保留所有重复的 course_id,比如有的课程在两个学期里开设了不止一次,就要使用 union all 关键字。
(2)交运算
15、找出在2009年秋季开课,并且也在2010年春季开课的所有课程。
(select course_id
from section
where semester = 'Fall' and year = 2009)
intersect
(select course_id
from section
where semester = 'Spring' and year = 2010);
Ps.同样保留重复要使用 intersect all。
(3)差运算
16、找出在2009年秋季开课,但不在2010年春季开课的所有课程。
(select course_id
from section
where semester = 'Fall' and year = 2009)
except
(select course_id
from section
where semester = 'Spring' and year = 2010);
Ps.同样保留重复要使用 except all。
4、聚合函数
常见通用的聚合函数有:
-
平均值:avg。
-
最小值:min。
-
最大值:max。
-
总和:sum。
-
计数:count。
sum 和 avg 的输入必须是数字集,聚合函数默认计算前不去重。
一般使用 group by 关键字来指定分组,如果未显式指定,则默认为关系中的所有元组。
(1)基本聚集
17、找出 Computer Science 系教师的平均工资。
select avg(salary)
from instructor
where dept_name = 'Comp.Sci.';
18、找出在 2010 年春季学期讲授一门课程的教师总数。
select count(distinct id)
from teaches
where semester = 'Spring' and year = 2010;
(2)分组聚集
19、找出每个系的平均工资。
// 使用 group by 关键字来构造分组
select dept_name, avg(salary) as avg_salary
from instructor
group by dept_name;
// 未指定则作用在所有元组上
找出所有教师的平均工资
select avg(salary)
from instructor;
20、找出每个系在 2010 年春季学期讲授一门课程的教师人数
select dept_name, count(distinct id) as instr_count
from instructor nature join teaches
where semester = 'Spring' and year = 2010
group by dept_name;
(3)having 子句
其实就相当于对每个分组分别进行 where 限定。
21、教师平均工资超过 42000 美元的系。
select dept_name, avg(salary) as avg_salary
from instructor
group by dept_name
having avg(salary) > 42000;
22、对于在 2009 年讲授的每个课程段,如果该课程段有至少 2 名学生选课,选出选修该课程段的所有学生的总学分(tot_cred)的平均值。
select course_id, sec_id, semester, year, avg(tot_cred) as avg_tot_cred
from student nature join takes
where year = 2009
group by course_id, sec_id, semester, year
having count(id) >= 2;
5、嵌套子查询
子查询是嵌套在另一个查询中的 select-from-where 表达式。子查询嵌套在 where 子句中,通常用于对集合的成员资格、集合的比较以及集合的基数进行检查。
(1)集合成员资格
使用 in/not in 关键字,测试元组是否是集合中的成员,集合是由 select 子句产生的一组值构成的。
23、找出在 2009 年秋季和 2010 年春季学期同时开课的所有课程。(同15,使用嵌套子查询的写法)
select course_id
from section
where semester = 'Fall' and year = 2009
and course_id in (select course_id
from section
where semester = 'Spring' and year = 2010);
24、找出所有在 2009 年秋季学期开课,但不在 2010 年春季学期开课的课程(同16,使用嵌套子查询写法)
select course_id
from section
where semester = 'Fall' and year = 2009
and course_id not in (select course_id
from section
where semester = 'Spring' and year = 2010);
25、查询找出既不叫 “Mozart” 也不叫 “Einstein” 的教师的姓名。
select name
from instructor
where name not in ('Mozart', 'Einstein');
(2)集合的比较
嵌套子查询能够对集合进行比较,使用下面的关键字:
- some 关键字:某些、某一个。
- all 关键字:所有。
26、找出满足下面条件的所有教师的姓名,他们的工资至少比 Biology 系某一个教师的工资要高。(同16,使用嵌套子查询)
select name
from instructor
where salary > some(select salary
from instructor
where dept_name = 'Biology');
27、找出满足下面条件的所有教师的姓名,他们的工资值比 Biology 系每个教师的工资都高。
select name
from instructor
where salary > all(select salary
from instructor
where dept_name = 'Biology');
28、找出平均工资最高的系。
select dept_name
from (select dept_name, avg(salary) as avg_salary
from instructor
group by dept_name)
where avg_salary = max(avg_salary);
select dept_name
from instructor
group by dept_name
having avg(salary) >= all(select avg(salary)
from instructor
group by dept_name);
(3)空关系测试
使用 exists/not exists 关键字测试一个子查询结果中是否存在元组。
29、找出在 2009 年秋季学期和 2010 年春季学期同时开课的所有课程。
select course_id
from section as S
where semester = 'Fall' and year = 2009 and
exists (select *
from section as T
where semester = 'Spring' and year = 2010 and
S.course_id = T.course_id);
相关子查询:使用外层查询的一个相关名称(上述查询中的 S)可以用在 where 子句的子查询中,使用了来自外层查询相关名称的子查询被称作相关子查询。
30、找出选修了 Biology 系开设的所有课程的学生。
select S.id, S.name
from student as S
where not exists ((select course_id
from course
where dept_name = 'Biology')
except
(select T.course_id
from takes as T
where S.id = T.id));
(4)重复元组存在性测试
使用 unique 关键字测试作为参数的子查询结果中是否有重复的元组。
31、找出所有在 2009 年最多开设一次的课程。
// 使用 unique 关键字
select T.course_id
from course as T
where unique (select R.course_id
from section as R
where T.course_id = R.course_id
and R.year = 2009);
// 使用聚合函数
select T.course_id
from course as T
where 1 >= (select count(R.course_id)
from section as R
where T.course_id = R.course_id
and R.year = 2009);
32、找出所有在 2009 年最少开设两次的课程。
// 使用 not unique 关键字,表示从集合中筛选出相同的 course_id 至少出现两次的数据
select T.course_id
from course as T
where not unique (select R.course_id
from section as R
where T.course_id = R.course_id
and R.year = 2009);
select T.course_id
// 使用聚合函数
from course as T
where 2 =< (select count(R.course_id)
from section as R
where T.course_id = R.course_id
and R.year = 2009);
(5)from 子句的子查询
任何 select-from-where 表达式返回的结果都是关系,因而可以被插入到另一个 select-from-where 中任何关系可以出现的位置。
33、找出系平均工资超过 42000 美元的那些系中教师的平均工资。(同 21,使用子查询写法)
// 使用子查询
select dept_name, avg_salary
from (select dept_name, avg(salary) as avg_salary
from instructor
group by dept_name)
where avg_salary > 42000;
// 使用 having 关键字
select dept_name, avg(salary) as avg_salary
from instructor
group by dept_name
having avg_salary > 42000;
34、找出在所有系中工资总额最大的系。
select dept_name, max(tot_salary)
from (select dept_name, sum(salary) as tot_salary
from instructor
group by dept_name);
(6)with 子句
使用 with 关键字,定义临时关系。
35、找出具有最大预算值的系。
with max_budget(value) as
(select max(budget)
from instructor)
select budget
from department, max_budget
where department.budget = max_budget.budget;
36、查出所有工资总额大于所有系平均工资总额的系。
with dept_total(dept_name, value) as
(select dept_name, sum(salary)
from instructor
group by dept_name),
dept_total_avg(value) as
(select avg(value)
from dept_total);
select dept_name
from dept_total, dept_total_avg
where dept_total.value >= dept_total_avg.value;
(7)标量子查询
标量子查询:该子查询值返回包含单个属性的单个元组。
37、找出所有的系及他们拥有的教师数。
// 使用自然连接和分组聚合的写法
select dept_name, count(*) as ins_num
from instructor nature join department
group by dept_name;
// 使用标量子查询的写法
select dept_name, (select count(*)
from instructor
where department.dept_name = instructor.dept_name)
from department;
6、数据库的修改
(1)删除
38、从 instructor 关系中删除与 Finance 系老师相关的所有元素。
delete from instructor where dept_name = 'Finance';
39、删除所有工资在 13000 美元到 15000 美元之间的教师。
delete from instructor where salary between 13000 and 15000;
40、从 instructor 关系中删除所有这样的教师元组,他们在位于 Watson 大楼的系办公。
delete from instructor, department
where instructor.dept_name = department.dept_name
and department.building = 'Watson';
delete from instructor
where dept_name in (select dept_name
from department
where building = 'Watson');
41、删除工资低于大学平均工资的教师记录。
delete from instructor
where salary < (select avg(salary) from instructor);
(2)插入
42、插入 Computer Science 系开设的名为 “Database Systems” 的课程 CS-437,它有 4 个学分。
// 元组属性值的排列顺序和关系模式中属性排列的顺序一致,可不显示指定属性
insert into course values('CS-437', 'Database System', 'Comp.Sci.', 4);
// 显式指定属性
insert into course(course_id, title, dept_name, credits)
values ('CS-437', 'Database System', 'Comp.Sci.', 4);
- 用 select 选出一个元组集合,并将其插入到关系中。
43、让 Music 系每个修满 144 学分的学生成为 Music 系的教师,其工资为 18000 美元。
insert into instructor
select id, name, department, 18000
from student
where dept_name = 'Music' and tot_cred > 144;
(3)更新
44、所有教师的工资增长 5%。
update instructor
set salary = salary * 1.05;
45、只给那些工资低于 70000 美元的教师涨工资。
update instructor
set salary = salary * 1.05
where salary < 70000;
46、对工资低于平均数的教师涨 5% 的工资。
update instructor
set salary = salary * 1.05
where salary < (select avg(salary)
from instructor);
47、给工资超过 100000 美元的教师涨 3% 的工资,其余教师涨 5%。
update instructor
set salary = salary * 1.03
where salary > 100000;
update instructor
set salary = salary * 1.05
where salary <= 100000;
// 使用 case 结构
update instructor
set salary = case
when salary <= 100000 then salary * 1.05
else salary * 1.03
end
48、如果一个学生在某门课程上的成绩既不是 ‘F’ 也不是空,那么他成功学完了这门课程。
update student S
set tot_cred = (
select sum(credits)
from takes nature join course
where S.id = takes.id
and takes.grade <> 'F'
and takes.grade is not null);
// 更好地处理成绩为 null 的情况
update student S
set tot_cred = (
select case
when sum(credits) is not null then sum(credits)
else 0
end
from takes nature join course
where S.id = takes.id
and takes.grade <> 'F'
and takes.grade is not null);
【习题】
3.1 使用大学模式,用 SQL 写出如下查询。
a. 找出 Comp.Sci. 系开设的具有 3 个学分的课程名称。
b. 找出名叫 Einstein 的教师所教的所有学生的标识,保证结果中没有重复。
c. 找出教师的最高工资。
d. 找出工资最高的所有教师(可能不止一位教师具有相同的工资)。
e. 找出 2009 年秋季开设的每个课程段的选课人数。
f. 从 2009 年秋季开设的所有课程段中,找出最多的选课人数。
g. 找出在 2009 年秋季拥有最多选课人数的课程段。
a. select title
from course
where dept_name = 'Comp.Sci.' and credits = 3;
b. select distinct(takes.id)
from (student join takes using(id))
join
(instructor join teaches using(id))
using(course_id, sec_id, semester, year)
where instructor.name = 'Einstein';
c. select max(salary) from instructor;
d. select name
from instructor
where salary = (select max(salary) from instructor);
e. select course_id, sec_id, count(id)
from section nature join takes
where semester = 'Autumn' and year = 2009
group by course_id, sec_id;
f. select max(elect_num)
from (select count(id) as elect_num
from section nature join takes
where semester = 'Autumn' and year = 2009
group by course_id, sec_id);
g. with sec_enrollment as
(select course_id, sec_id, count(id) as enrollment
from section nature join takes
where semester = 'Autumn' and year = 2009
group by course_id, sec_id);
select course_id, sec_id
from sec_enrollment
where enrollment = (select max(enrollment) from sec_enrollment);
3.2 假设给你一个关系 grade_points ( grade_e, points ),它提供从 takes 关系中用字母表示的成绩等级到数字表示的得分之间的转换。例如,“A” 等级可指定为对应于 4 分,“A-” 对应于 3.7 分,“B+” 对应于 3.3 分,“B” 对应于 3 分,等等。学生在某门课程(课程段)上所获得的等级分值被定义为该课程段的学分乘以该生得到的成绩等级所对应的数字表示的得分。
给定上述关系和我们的大学模式,用 SQL 写出下面的每个查询。为简单起见,可以假设没有任何 takes 元组在 grade 上取 null 值。
a. 根据 ID 为 12345 的学生所选修的所有课程,找出该生所获得的等级分值的总和。
b. 找出上述学生等级分值的平均值(GPA),即用等级分值的总和除以相关课程学分的总和。
c. 找出每个学生的 ID 和等级分值的平均值。
a. select sum(credits * points)
from takes nature join course, grade_points
where id = 12345 and takes.grade = grade_points.grade_e;
b. select sum(credits * points)/sum(credits) as GPA
from takes nature join course, grade_points
where id = 12345 and takes.grade = grade_points.grade_e;
c. select id, sum(credits * points)/sum(credits) as GPA
from takes nature join course, grade_points
where takes.grade = grade_points.grade_e
group by id;
3.3 使用大学模式,用 SQL 写出如下插入、删除和更新语句
a. 给 Comp.Sci. 系的每位老师涨 10% 的工资。
b. 删除所有未开设过(即没有出现在 section 关系中)的课程。
c. 把每个在 tot_cred 属性上取值超过 100 的学生作为同系的教师插入,工资为 10000 美元。
a. update instructor
set salary = salary * 1.1
where dept_name = 'Comp.Sci.';
b. delete from course
where course_id not in
(select distinct(course_id) from section);
c. insert into instructor
select id, name, department, 10000
from student
where tot_cred > 100;
3.4 考虑图 3-18 中保险公司数据库,其中加下划线的是主码。为这个关系数据库构造如下 SQL 查询:
a. 找出 2009 年其车辆出过交通事故的人员总数。
b. 向数据库中增加一个新的事故,对每个必需的属性可以设定任意值。
c. 删除 “John Smith” 拥有的马自达车(Mazda)保险公司数据库(PS.加粗为主键):
person ( driver_id, name, address )
car ( license, model, year )
accident ( report_number, date, location )
owns( driver_id, license )
participated( report_number, license, driver_id, damage_amount )
a. select count(distinct driver_id)
from accident nature join participated
where date between '2009-01-01' and '2009-12-31';
b.
c. delete from car
where model = 'Mazda' and license in (select license
from person nature join owns
where person.name = 'John Smith');
3.5 假设有关系 marks(ID, score),我们希望基于如下标准为学生评定等级:如果 score < 40 得 F;如果 40 <= score < 60 得 C;如果 60 <= score < 80 得 B;如果 80 <= score 得 A。写出 SQL 查询完成下列操作:
a. 基于 marks 关系显式每个学生的等级。
b. 找出各等级的学生数。
a. select ID,
case
when score < 40 then 'F'
when score < 60 then 'C'
when score < 80 then 'B'
else 'A'
end
from marks;
b. with grades as
(select ID,
case
when score < 40 then 'F'
when score < 60 then 'C'
when score < 80 then 'B'
else 'A'
end as grade
from marks);
select grade, count(ID)
from grades
group by grade;
3.6 SQL 的 like 运算符是大小写敏感的,但字符串上的 lower() 函数可用来实现大小写不敏感的匹配。为了说明是怎么用的,写出一个查询:找出名称中包含了 “sci” 子串的系,忽略大小写。
select dept_name
from department
where lower(dept_name) like '%sci%';
3.7 考虑下面的银行数据库,其中加下划线的是主码。为这个关系数据库构造出如下 SQL 查询:
a. 找出银行中所有有账户但无贷款的客户。
b. 找出与 “Smith” 居住在同一个城市、同一个街道的所有客户的名字。
c. 找出所有支行的名称,在这些支行中都有居住在 “Harrison” 的客户所开设的账户。银行数据库:
branch ( branch_name, branch_city, assets )
customer ( customer_name, customer_street, customer_city )
loan ( loan_number, branch_name, amount )
borrower ( customer_name, load_number )
account ( account_number, branch_name, balance )
depositor ( customer_name, account_number )
a. (select customer_name from depositor)
except
(select customer_name from borrower);
b. select A.customer_name
from customer as A, customer as B
where B.customer_name = 'Smith'
and A.customer_city = B.customer_city
and A.customer_street = B.customer_street;
select F.customer_name
from customer F join customer F using(customer_city, customer_street)
and S.customer_name = 'Smith';
c. select distinct branch_name
from account nature join depositor nature join customer
where customer.customer_city = 'Harrison';
3.8 考虑下面的雇员数据库,其中加下划线的是主码。为下面每个查询写出 SQL 表达式:
a. 找出所有为 "First Bank Corporation" 工作的雇员名字及其居住城市。
b. 找出所有为 "First Bank Corporation" 工作且薪金超过 10000 美元的雇员名字、居住街道和城市。
c. 找出数据库中所有不为 "First Bank Corporation" 工作的雇员。
d. 找出数据库中工资高于 "Small Bank Corporation" 的每个雇员的所有雇员。
e. 假设一个公司可以在好几个城市有分部。找出位于 "Small Bank Corporation" 所有所在城市的所有公司。(有点拗口,就是找到在这家公司开设了分部的所有城市都有开设分部的公司)
f. 找出雇员最多的公司。
g. 找出平均工资高于 "First Bank Corporation" 平均工资的那些公司。
h. 修改数据库使 "Jones" 现在居住在 "Newtown" 市。
i. 为 "First Bank Corporation" 所有工资不超过 100 000 美元的经理增长 10% 的工资,对工资超过 100 000 美元的只增长 3%。雇员数据库:
employee ( employee_name, street, city )
works ( employee_name, company_name, salary )
company ( company_name, city )
managers ( employee_name, manager_name )
a. select employee_name, city
from employee nature join works
where works.company_name = 'First Bank Corporation';
b. select employee_name, street, city
from employee nature join works
where works.company_name = 'First Bank Corporation'
and works.salary > 10000;
c. select employee_name
from works
where company_name != 'First Bank Corporation';
d. select employee_name
from works > all(select salary
from works
where company_name = 'Small Bank Corporation');
e. select S.company_name
from company S
where not exists ((select city
from company
where company_name = 'Small Bank Corporation')
except
(select city
from company T
where S.company_name = T.company_name)
);
f. with employee_counts as (select company_name, count(distinct employee_name) as employee_count
from works
group by company_name);
select company_name
from employee_counts
where employee_count = max(employee_count);
select company_name
from works
group by company_name
having count(distinct employee_name) > all(select count(distinct employee_name)
from works
group by company_name);
g. with comp_avg_salary as
(select company_name, avg(salary) as avg_salary
from works
group b company_name);
select S.company_name
from comp_avg_salary S, comp_avg_salary T
where T.company_name = 'First Bank Corporation'
and S.avg_salary > T.avg_salary;
select company_name
from works
group by company_name
having avg(salary) > (select avg(salary)
from works
where company_name = 'First Bank Corporation');
h. update employee set city = 'Newtown' where employee_name = 'Jones';
i. update employee
set salary = salary *
(case
when salary < 100000 then 1.1
else 1.03
end)
where employee_name in (select employee_name
from managers nature join works
and works.company_name = 'First Bank Corporation');
update employee
set salary = salary *
(case
when salary < 100000 then 1.1
else 1.03
end)
where employee_name in (select employee_name
from managers);
网友评论