第06章 形式化关系查询语言
1. 基本运算
选择、投影和更名运算称为一元运算,因为它们对一个关系进行运算。并、集合差、笛卡儿积对两个关系进行运算,称为二元运算。
选择(select)运算
- 用σ表示选择,将谓词写作下标,参数关系在σ后的括号中,如:
σ
d
e
p
t
_
n
a
m
e
=
"
P
h
y
s
i
c
s
"
(
i
n
s
t
r
u
c
t
o
r
)
σ_{dept\_name="Physics"}(instructor)
σdept_name="Physics"(instructor)表示选择关系instructor中属于物理系的元组。
σ s a l a r y > 90000 ( i n s t r u c t o r ) σ_{salary>90000}(instructor) σsalary>90000(instructor)选择工资额大于90000美元的元组。 - 使用=,≠,<,≤,>和≥在谓词中进行比较。另外,也可以用连词and(∧)、or(∨)和not(¬)将多个谓词合并为一个较大的谓词。
σ d e p t _ n a m e = " P h y s i c s " ∧ s a l a r y > 90000 ( i n s t r u c t o r ) σ_{dept\_name="Physics"∧salary>90000}(instructor) σdept_name="Physics"∧salary>90000(instructor) - 选择谓词也可以包含两个属性的比较,以department关系为例,要找出系名与楼名相同的系:
σ d e p t _ n a m e = b u i l d i n g ( d e p a r t m e n t ) σ_{dept\_name=building}(department) σdept_name=building(department)
投影(project)运算
投影运算可以列出我们想要的属性,例如我们想要列出所有教师的ID、name和salary而不关心dept_name,则:
Π
i
d
,
n
a
m
e
,
s
a
l
a
r
y
(
i
n
s
t
r
u
c
t
o
r
)
Π_{id,name,salary}(instructor)
Πid,name,salary(instructor)
关系运算的组合
考虑一个更复杂的关系“找出物理系所有教师的名字”,写作:
Π
n
a
m
e
(
σ
d
e
p
t
_
n
a
m
e
=
"
P
h
y
s
i
c
s
"
(
i
n
s
t
r
u
c
t
o
r
)
)
Π_{name}(σ_{dept\_name="Physics"}(instructor))
Πname(σdept_name="Physics"(instructor))
关系运算的结果自身也是个关系
并运算
假设有一个查询,要找出在关系section中2009年秋季学期或者2010年春季学期开设或者两个学期都开设的课程。
- 2009秋季学期:
Π c o u r s e _ i d ( σ s e m e s t e r = " F a l l " ∧ y e a r = 2009 ( s e c t i o n ) ) Π_{course\_id}(σ_{semester="Fall"∧year=2009}(section)) Πcourse_id(σsemester="Fall"∧year=2009(section)) - 2010春季学期:
Π c o u r s e _ i d ( σ s e m e s t e r = " S p r i n g " ∧ y e a r = 2010 ( s e c t i o n ) ) Π_{course\_id}(σ_{semester="Spring"∧year=2010}(section)) Πcourse_id(σsemester="Spring"∧year=2010(section)) - 将这两个运算并(union)起来:
Π c o u r s e _ i d ( σ s e m e s t e r = " F a l l " ∧ y e a r = 2009 ( s e c t i o n ) ) ∪ Π c o u r s e _ i d ( σ s e m e s t e r = " S p r i n g " ∧ y e a r = 2010 ( s e c t i o n ) ) Π_{course\_id}(σ_{semester="Fall"∧year=2009}(section))∪Π_{course\_id}(σ_{semester="Spring"∧year=2010}(section)) Πcourse_id(σsemester="Fall"∧year=2009(section))∪Πcourse_id(σsemester="Spring"∧year=2010(section))
要使并运算 r ∪ s r∪s r∪s有意义,这两个关系必须是相容的,必须满足以下两个条件:
- 关系 r r r和 s s s必须是同元的,即它们的属性数目必须相同。
- 对所有的 i i i, r r r的第 i i i个属性的域必须和 s s s的第 i i i个属性的域相同。
集合差运算
集合差(set-difference)运算可以找出在一个关系中而不在另一个关系中的那些元组。表达式 r − s r-s r−s的结果为一个包含在所有 r r r中而不在 s s s中的元组的关系。
- 所有开设在2009年秋季学期但是在2010年春季学期不开设的课程: Π c o u r s e _ i d ( σ s e m e s t e r = " F a l l " ∧ y e a r = 2009 ( s e c t i o n ) ) − Π c o u r s e _ i d ( σ s e m e s t e r = " S p r i n g " ∧ y e a r = 2010 ( s e c t i o n ) ) Π_{course\_id}(σ_{semester="Fall"∧year=2009}(section))-Π_{course\_id}(σ_{semester="Spring"∧year=2010}(section)) Πcourse_id(σsemester="Fall"∧year=2009(section))−Πcourse_id(σsemester="Spring"∧year=2010(section))
集合差运算需要在相容的关系间进行,条件与并运算相同。
笛卡儿积(Cartesian-product)运算
-
将任意两个关系的信息组合在一起,记作 r 1 × r 2 r_1×r_2 r1×r2。
若r = instructor × teaches,instructor中有 n 1 n_1 n1个元组,teaches中有 n 2 n_2 n2个元组,那么r中有 n 1 × n 2 n_1×n_2 n1×n2个元组。对于r中的某些元组t来说,可能 t [ i n s t r u c t o r . I D ] ≠ t [ t e a c h e s . I D ] t[instructor.ID]≠t[teaches.ID] t[instructor.ID]=t[teaches.ID]。
-
因为笛卡儿积中保留了所有可能的有一个来自instructor的元组和一个来自teaches的元组构成的元组对,所以要找出物理系的老师以及他们所教的课程,需要进行如下运算:
Π n a m e , c o u r s e _ i d ( σ i n s t r u c t o r . I D = t e a c h e s . I D ( σ d e p t _ n a m e = " P h y s i c s " ( i n s t r u c t o r × t e a c h e s ) ) ) Π_{name,course\_id}(σ_{instructor.ID=teaches.ID}(σ_{dept\_name="Physics"}(instructor×teaches))) Πname,course_id(σinstructor.ID=teaches.ID(σdept_name="Physics"(instructor×teaches)))
参照前文的关系模型可知,虽然Gold属于物理系,但他并未教授课程,因此结果中不会出现他的名字。
更名(rename)运算
- 对给定关系代数表达式E,表达式 ρ x ( E ) ρ_x(E) ρx(E),返回表达式E的结果,并把名字x赋给了它。
- 以“查询最高工资”为例子演示关系的更名运算:
- 通过计算笛卡儿积
s
a
l
a
r
y
×
s
a
l
a
r
y
salary×salary
salary×salary并构造一个选择来比较任意两个出现在同一元组里的工资,改变其中一个关系的名称,这样可以避免歧义。
Π i n s t r u c t o r . s a l a r y ( σ i n s t r u c t o r . s a l a r y < d . s a l a r y ( i n s t r u c t o r × ρ d ( i n s t r u c t o r ) ) ) Π_{instructor.salary}(σ_{instructor.salary<d.salary}(instructor×ρ_d(instructor))) Πinstructor.salary(σinstructor.salary<d.salary(instructor×ρd(instructor)))
这样就得到了由非最高工资组成的一个临时关系。 - 查找最高工资可以写作
Π s a l a r y ( i n s t r u c t o r ) − Π i n s t r u c t o r . s a l a r y ( σ i n s t r u c t o r . s a l a r y < d . s a l a r y ( i n s t r u c t o r × ρ d ( i n s t r u c t o r ) ) ) Π_{salary}(instructor)-Π_{instructor.salary}(σ_{instructor.salary<d.salary}(instructor×ρ_d(instructor))) Πsalary(instructor)−Πinstructor.salary(σinstructor.salary<d.salary(instructor×ρd(instructor)))
- 通过计算笛卡儿积
s
a
l
a
r
y
×
s
a
l
a
r
y
salary×salary
salary×salary并构造一个选择来比较任意两个出现在同一元组里的工资,改变其中一个关系的名称,这样可以避免歧义。
2. 附加的关系代数运算
集合交(intersection)运算
- r ∩ s = r − ( r − s ) r∩s=r-(r-s) r∩s=r−(r−s)
- 找出在2009年秋季和2010年春季都开设的课程:
Π c o u r s e _ i d ( σ s e m e s t e r = " F a l l " ∧ y e a r = 2009 ( s e c t i o n ) ) ∩ Π c o u r s e _ i d ( σ s e m e s t e r = " S p r i n g " ∧ y e a r = 2010 ( s e c t i o n ) ) Π_{course\_id}(σ_{semester="Fall"∧year=2009}(section))∩Π_{course\_id}(σ_{semester="Spring"∧year=2010}(section)) Πcourse_id(σsemester="Fall"∧year=2009(section))∩Πcourse_id(σsemester="Spring"∧year=2010(section))
自然连接(natural join)运算
-
形式化定义: r ⋈ s = Π R ∪ S ( σ r . A 1 = s . A 1 ∧ r . A 2 = s . A 2 ∧ . . . ∧ r . A n = s . A n ( r × s ) ) r⋈s=Π_{R∪S}(σ_{r.A_1=s.A_1∧r.A_2=s.A_2∧...∧r.A_n=s.A_n}(r×s)) r⋈s=ΠR∪S(σr.A1=s.A1∧r.A2=s.A2∧...∧r.An=s.An(r×s))
其中 R ∩ S = { A 1 , A 2 , . . . , A n } R∩S=\{A_1,A_2,...,A_n\} R∩S={A1,A2,...,An}。如果关系r(R) 和s(S)不含有任何相同属性,即 R ∩ S = Ø R∩S=Ø R∩S=Ø,那么 r ⋈ s = r × s r⋈s=r×s r⋈s=r×s。
-
要想找出就计算机系的所有教师以及他们教授的所有的课程的名称,可以这样写:
Π n a m e , t i t l e ( σ d e p t _ n a m e = " C o m p . S c i . " ( i n s t r u c t o r ⋈ t e a c h e s ⋈ c o u r s e ) ) Π_{name,title}(σ_{dept\_name="Comp.Sci."}(instructor⋈teaches⋈course)) Πname,title(σdept_name="Comp.Sci."(instructor⋈teaches⋈course))上式中,没有在三个自然连接中添加括号,因为无论是否添加括号,二者都是等价的,也就是说,自然连接是可结合的(associative)。
赋值运算
赋值(assignment)运算用←表示,表示将右侧的表达式结果赋给左侧的关系变量。
外连接运算
外连接(outer-join)运算是连接运算的扩展,可以处理缺失的信息。
- 左外连接(left outer join):用⟕表示,取出左侧关系中所有与右侧关系的任意元组都不匹配的元组,用空值填充所有来自右侧关系的属性,再把产生的元组加到自然连接的结果中。
- 右外连接(right outer join):用⟖表示,与左外连接相对称,用空值填充来自右侧关系的所有与左侧关系的任意元组都不匹配的元组,将结果加到自然连接的结果中。
- 全外连接(full outer join):用⟗表示,既做左连接又做右连接。
外连接运算可以用基本关系代数运算表示。例如左连接运算r⟕s可以写成:
(
r
⋈
s
)
∪
(
r
−
Π
R
(
r
⋈
s
)
)
×
{
(
n
u
l
l
,
n
u
l
l
,
.
.
.
,
n
u
l
l
)
}
(r⋈s)∪(r-Π_R(r⋈s))×\{(null,null,...,null)\}
(r⋈s)∪(r−ΠR(r⋈s))×{(null,null,...,null)}
3. 扩展的关系代数运算
扩展的关系代数(extended relational-algebra)运算能实现一些不能用基本的关系代数来表达的查询。
广义投影
广义投影(generalized-projection)允许在投影列表中使用算术运算和字符串函数等来对投影进行扩展。例如:
Π
I
D
,
n
a
m
e
,
d
e
p
t
_
n
a
m
e
,
s
a
l
a
r
y
/
12
(
i
n
s
t
r
u
c
t
o
r
)
Π_{ID,name,dept\_name,salary/12}(instructor)
ΠID,name,dept_name,salary/12(instructor)可以得到每个教师的ID、name、dept_name以及每个月的工资。
聚集
聚集运算可以用来对值的集合使用聚集函数(aggregate function),例如计算最小值或者求平均值。
常用聚集函数:
- sum:返回这些值的和。
- avg:返回平均值。
- count:返回汇集中元素的个数。
- min:返回最小值。
- max:返回最大值。
使用聚集函数对其进行操作的汇集中,一个值可以出现多次,值出现的顺序是无关紧要的,这样的汇集称为多重集(multiset)。集合(set)是多重集的特例,其中每个值都只出现一次。
- 要找出所有教师的工资总和,查询的关系代数表达式为: G s u m ( s a l a r y ) ( i n s t r u c t o r ) G_{sum(salary)}(instructor) Gsum(salary)(instructor)。表达式的结果是一个单一属性的关系,是单独的一行,其值表示所有教师的工资总和。
- 有时,在计算聚集函数前需要去除重复值,如果要去除重复,将distinct附加在函数名后即可,例如:查询在2010年春季学期教课的教师数:
G
c
o
u
n
t
−
d
i
s
t
i
n
c
t
(
I
D
)
(
σ
s
e
m
e
s
t
e
r
=
"
S
p
r
i
n
g
"
∧
y
e
a
r
=
2010
(
t
e
a
c
h
e
s
)
)
G_{count-distinct(ID)}(σ_{semester="Spring"∧year=2010}(teaches))
Gcount−distinct(ID)(σsemester="Spring"∧year=2010(teaches))
这样,count-distinct函数就可以确保即使某位教师授课多于一门,也只被记一次数。 - 有时候需要对一组元组集合而不是单个元组执行集合函数。例如:求出每个系的平均工资:
d e p t _ n a m e G a v g ( s a l a r y ) ( i n s t r u c t o r ) _{dept\_name}G_{avg(salary)}(instructor) dept_nameGavg(salary)(instructor)
作为对比,求出所有教师的平均工资的表达式为 G a v g ( s a l a r y ) ( i n s t r u c t o r ) G_{avg(salary)}(instructor) Gavg(salary)(instructor),运算符的左边去掉了属性dept_name,这样一来整个关系就被当成单个组来执行聚集。