一、数据库系统概述
数据库管理系统【DataBase-Management System,DBMS】由一个互相关联的数据集合和一组用以访问这些数据的程序组成,这个数据集合通常称为数据库【DataBase,DB】。设计数据库系统的目的是为了管理大量信息。
1.1 数据库系统
数据库是电子化信息的集合,形成电子信息库,以便利用计算机对这些信息进行快速有效的存储、检索、统计和管理,起源于规范化表的处理。
表是以按行或按列形式组织及展现的数据。一般的,每个表有其表名,包含一种特定类型的记录,每种记录类型定义固定述目的字段或属性,表的每一列对应记录类型的属性。表的行也称为元组或记录,而每一列又称为字段或数据项,列又分为列名与列值。
表的表名与表格式称为表的关系模式,而表的关系模式与表的值称为表的关系。那么互相有关联关系的若干个表的集合即是数据库。
数据库系统是一些相互关联的数据以及一组使得用户可以访问和修改这些数据的程序的集合。数据库系统为数据库工作提供环境,包含以下要素:
-数据库,相互有关联关系的数据的集合;
-数据库管理系统,用于管理数据库的系统软件;
-数据库应用,为用户提供完成某些功能的应用程序;
-数据库管理员,使用并维护数据库管理系统;
-计算机基本系统,包括计算机系统,计算机网络等。
1.2 数据抽象
数据库系统的一个主要目的是给用户提供数据的抽象视图,即系统隐藏关于数据存储和维护的某些细节。
数据库系统一般通过如下几个层次上的抽象来对用户屏蔽复杂性,以简化用户和系统的交互:
-物理层,最低层次的抽象,描述数据的存储与复杂的底层数据结构;
-逻辑层,稍高于物理层的抽象,描述数据的含义与数据之间的关系;
-视图层,最高层次的抽象,描述数据库的局部,提供用户与系统的简单交互。
数据库的总体设计称为模式,根据不同的层次,数据库系统可以分为物理模式、逻辑模式与视图模式,也称为内模式、概念模式与外模式。
在不同模式之间映射,从而实现模式之间的转换,称为映像。E-C映像是指外模式映射为概念模式,便于用户观察和使用;C-I映像是指概念模式映射为内模式,便于计算机的存储与处理。
当概念模式发生变化时,可以不改变外模式,从而无需改变应用程序,体现了逻辑数据的独立性;当内部模式变化时,可以不改变概念模式,从而无需改变外部模式,体现为物理数据独立性。数据的独立性保证了只需改变映像,就可以保证应用程序的有效性。
数据库结构的基础是数据模型,规定了模式统一描述方式的模型,包括数据结构、数据操作与数据约束。典型的,关系模型是指所有模式都可以抽象为表的形式,且这种表形式的数据具有一定的操作和约束;此外还有基于树的层次模型、基于图的网状模型,它们已经很少被使用了。
二、关系模型
关系模型从表及表的处理方式中抽象出来,是对传统表及其操作进行数学化严格定义基础上,引入集合理论与逻辑学理论提出的。关系模型已经成为当今重要的数据模型,是数据库的三大经典数据模型之一,标准数据库语言SQL就是建立在关系模型基础之上的。
形象地说,一个关系就是一个表。关系模型用于处理表,由三部分组成:
-描述数据库中数据的基本结构形式;
-描述表及其之间所可能发生的各种操作;
-描述关系运算所遵循的约束条件。
2.1 关系数据库结构
关系数据库由表的集合构成。表可以被严格的定义为:表的列的取值范围,称为域,为一组值的集合,且这组值具有相同的数据类型,且集合中元素的个数称为域的基数;表的行,即元组的每一个值叫做分量,元组是从每个域任取一个值所形成的一种组合,这些域的笛卡尔积形成了所有可能的元组的集合,笛卡尔积的基数是所有域的基数的积。
关系是一组域的笛卡尔积的子集,即笛卡尔积中具有意义的某些元组。关系模式定义为
R
(
A
1
:
D
1
,
A
2
:
D
2
,
.
.
.
,
A
n
:
D
n
)
R(A_1:D_1, A_2:D_2, ..., A_n:D_n)
R(A1:D1,A2:D2,...,An:Dn)其中
R
R
R是关系的名字,
A
i
A_i
Ai是属性,
D
i
D_i
Di是属性对应的域,
n
n
n是关系的度,描述了数据项数,即列数。例如下表:
可以描述为
F
a
m
i
l
y
(
H
u
s
b
a
n
d
:
M
a
l
e
,
W
i
f
e
:
F
e
m
a
l
e
,
C
h
i
l
d
:
K
i
d
)
Family(Husband:Male, Wife:Female, Child: Kid)
Family(Husband:Male,Wife:Female,Child:Kid) 在关系模式中,
R
R
R中的属性向域的映像也可以说明为属性的类型、长度等,例如
S
t
u
d
e
n
t
(
S
n
a
m
e
c
h
a
r
(
10
)
,
S
s
e
x
c
h
a
r
(
2
)
,
S
a
g
e
i
n
t
e
g
e
r
)
Student(Sname\ char(10), Ssex\ char(2), Sage\ integer)
Student(Sname char(10),Ssex char(2),Sage integer)关系模式是关系的结构,而关系是关系模式在某一时刻的数据。
2.2 码
关系中有一个属性组,其值能够唯一的标识一个元组,若从该属性组中去掉任何一个属性,其就不具有这一属性,这样的属性组称为候选码。例如:
S
t
u
d
e
n
t
(
S
#
,
S
n
a
m
e
,
S
a
g
e
,
S
c
l
a
s
s
)
Student(S\#,\ Sname,\ Sage,\ Sclass)
Student(S#, Sname, Sage, Sclass)其中,S#,即学号是一个候选码,在此关系中,任何两个元组的学号是一定不同的,而不同学号的名字、年龄、班级都可能相同,所以S#是候选码,唯一标识了一个元组。
有时,关系中有很多组候选码,例如某种员工表,没有两个员工的员工ID和电话号码是相同的,这两属性均是候选码。当有多个候选码时,需要选择一个作为主码,或者称为主键【Primary Key】。DBMS以主码为主要线索管理关系中的各个元组。
包含在候选码中的属性称为主属性,其他属性称为非主属性。当所有属性均构成候选码时,称为全码。
如果存在关系R与S,R的某个属性组不是R的主码,但与S的主码相对应,则称该属性组为R的外码,或者称为外键【Foreign Key】。两个关系通常靠外码连接。
2.3 关系模型完整性
空值表示该值无意义、不存在或未知,当数据库中有了空值,会影响许多方面,如影响聚集函数运算的正确性,不能参与算术、比较和逻辑运算等,有空值的时候需要注意进行特殊处理。
关系模型具有实体完整性,关系的主码中的属性值不能为空值。主码唯一的标识了实体的元组,若主码为空,则意味着出现了不可标识的实体,这是不容许的。
关系模型具有参照完整性,如果关系R的外码F与关系S的主码P相对应,则R的每一个元组的F值或者等于S的某个元组的P值,或者为空。其意义在于R的某个元组t参照了S的某个元组u,则u必须存在。
关系模型具有用户自定义完整性,用户针对具体的应用环境定义的完整性约束条件,典型的,学生学号需要是一定位数的整数,其中某四位是年度,需要与当前年度的差距在一定范围内。
DBMS系统自动支持实体完整性与参照完整性,且通常提供用户自定义完整性约束条件,并在更新操作时,DBMS将自动按照完整性约束条件检验更新操作的正确性。
2.4 关系代数
关系代数是一种基于集合,提供了一系列的关系代数操作,是一种集合思维的操作语言。关系代数通常以一或多个关系为输入,以一个新的关系为输出。其是一种抽象的语言,是SQL等的基础。
某些关系代数操作,需要满足并相容性,关系R与关系S存在并相容性,当且仅当:
-R与S的属性数目相同;
-R与S的任意对应的属性的域相同。
假设关系R和关系S是并向容的,则R与S的并运算结果是一个关系,记作
R
∪
S
=
{
t
∣
t
∈
R
∨
t
∈
S
}
R \cup S = \{t|t \in R \vee t\in S\}
R∪S={t∣t∈R∨t∈S}其中,t是元组。并运算将两个关系的元组合并成一个关系,并去掉重复的元组。
假设关系R和关系S是并向容的,则R与S的差运算结果是一个关系,记作
R
−
S
=
{
t
∣
t
∈
R
∧
t
∉
S
}
R - S = \{t|t \in R \wedge t\notin S\}
R−S={t∣t∈R∧t∈/S}其中,t是元组。要注意
R
−
S
R-S
R−S与
S
−
R
S-R
S−R是不等价的。
R与S的笛卡尔积【Cartesian Product】结果是一个关系,记作
R
×
S
=
{
⟨
a
1
,
a
2
,
.
.
.
,
a
n
,
b
1
,
b
2
,
.
.
.
,
b
m
⟩
∣
⟨
a
1
,
.
.
.
,
a
n
⟩
∈
R
∧
⟨
b
1
,
.
.
.
,
b
m
⟩
∈
S
}
R \times S = \{\lang a_1, a_2, ..., a_n, b_1, b_2, ..., b_m\rang | \lang a_1,..., a_n\rang \in R \wedge \lang b_1,..., b_m\rang \in S\}
R×S={⟨a1,a2,...,an,b1,b2,...,bm⟩∣⟨a1,...,an⟩∈R∧⟨b1,...,bm⟩∈S}考虑R与S分别为
那么其笛卡尔积为
给定一个关系R,同时给出选择条件con,那么选择运算的结果是一个关系,记作
σ
c
o
n
(
R
)
=
{
t
∣
t
∈
R
∧
c
o
n
(
t
)
=
T
}
\sigma_{con}(R) = \{t|t \in R \wedge con(t) = T\}
σcon(R)={t∣t∈R∧con(t)=T}其从关系R中选择出满足给定条件的元组构成。其中con由逻辑运算符连接比较表达式组成。
给定一个关系R,同时给出需要的属性A,投影运算的结果是一个关系,记作
Π
A
i
,
A
j
,
.
.
.
(
R
)
=
{
⟨
t
[
A
i
]
,
t
[
A
j
]
,
.
.
.
⟩
∣
t
∈
R
}
\Pi_{A_i, A_j, ...}(R) = \{\lang t[A_i], t[A_j] , ...\rang|t\in R\}
ΠAi,Aj,...(R)={⟨t[Ai],t[Aj],...⟩∣t∈R}其从关系R中选择出某些属性的列构成。要注意的是,当投影后出现重复的元组时,需要去除。
通过上述基本运算,可以拓展出某些扩展操作。
假设关系R和关系S是并向容的,则R与S的交运算结果是一个关系,记作
R
∩
S
=
{
t
∣
t
∈
R
∧
t
∈
S
}
R
∩
S
=
R
−
(
R
−
S
)
R \cap S = \{t|t \in R \wedge t\in S\} \\ R \cap S = R-(R-S)
R∩S={t∣t∈R∧t∈S}R∩S=R−(R−S) 投影和选择操作只是对单个关系进行操作,当设计多个表之间的操作时,需要进行θ-连接操作。给定关系R与S,R与S的θ-连接操作运算结果是一个关系,记作
R
⋈
A
θ
B
S
=
σ
t
[
A
]
θ
s
[
B
]
(
R
×
S
)
R \Join_{A\theta B}S = \sigma_{t[A]\theta s[B]}(R \times S)
R⋈AθBS=σt[A]θs[B](R×S)其从关系R和关系S的笛卡尔积中,选取R中属性A与S中属性B之间满足θ条件的元组构成。考虑R和S分别为
则对于条件
B
<
H
B<H
B<H的θ-连接为
此外,自然连接是一种特殊的θ-连接,给定关系R与S,R与S的自然连接的运算结果是一个关系,记作
R
⋈
S
=
σ
t
[
B
]
=
s
[
B
]
(
R
×
S
)
R \Join S = \sigma_{t[B] = s[B]}(R \times S)
R⋈S=σt[B]=s[B](R×S)其由R和S的笛卡尔积中选取相同属性组B上值相等的元组构成,要求R与S由相同的属性组,且值必须相等。考虑R和S分别为
其自然连接为
除了上述操作以外,还有一些复杂的扩展操作。
给定关系
R
(
A
1
,
A
2
,
.
.
.
,
A
n
)
R(A_1, A_2, ..., A_n)
R(A1,A2,...,An)为n度关系,关系
S
(
B
1
,
B
2
,
.
.
.
,
B
m
)
S(B_1, B_2, ..., B_m)
S(B1,B2,...,Bm)为m度关系。那么R与S的除运算当且仅当属性集
{
B
1
,
B
2
,
.
.
.
,
B
m
}
\{B_1, B_2, ..., B_m\}
{B1,B2,...,Bm}是属性集
{
A
1
,
A
2
,
.
.
.
,
A
n
}
\{A_1, A_2, ..., A_n\}
{A1,A2,...,An}的真子集,即
m
<
n
m < n
m<n。那么有
R
÷
S
=
{
t
∣
t
∈
Π
R
−
S
(
R
)
∧
∀
u
∈
S
,
t
u
∈
R
}
R \div S = \{t|t \in \Pi_{R-S}(R) \wedge \forall u \in S, tu \in R \}
R÷S={t∣t∈ΠR−S(R)∧∀u∈S,tu∈R}考虑R和S分别为
那么R与S的除运算为
外连接连接不同关系内的所有相关信息。对于关系R与S进行自然连接,那么如果某个关系的元组在另一个元组中找不到相匹配的元组,则为了避免元组信息丢失,从而将该元组假定存在且且全为空值的元组形成连接,放置在结果关系中。考虑R和S分别为
其外连接为
外连接可以分为左外连接、右外连接、全外链接,分别代表自然连接与左侧、右侧或两侧失配的元组。
2.5 关系演算
关系演算以数理逻辑中的谓词演算为基础,是描述关系运算的另一种思维方式。按照谓词变量的不同,分别关系元组演算和关系域演算。
关系元组演算的基本形式为 { t ∣ P ( t ) } \{t|P(t)\} {t∣P(t)}其表示了所有使 P P P为真的元组 t t t的集合。其中, P P P具有三种形式的原子公式: s ∈ R s [ A ] θ c s [ A ] θ u [ B ] \begin{aligned}&s \in R \\&s[A]\ \theta\ c \\ &s[A]\ \theta\ u[B] \end{aligned} s∈Rs[A] θ cs[A] θ u[B]且P的逻辑运算 P ∨ Q P \vee Q P∨Q、 P ∧ Q P \wedge Q P∧Q、 ¬ P \neg P ¬P也是一个演算公式。且对于量词 ∀ \forall ∀, ∃ \exists ∃,有 ∀ ( t ∈ R ) ( P ( t ) ) \forall(t \in R)(P(t)) ∀(t∈R)(P(t))也是一个演算公式。
关系域演算的基本形式为 { ⟨ x 1 , x 2 , . . . , x n ⟩ ∣ P ( x 1 , x 2 , . . . , x n ) } \{\lang x_1, x_2, ..., x_n \rang|P(x_1, x_2, ..., x_n)\} {⟨x1,x2,...,xn⟩∣P(x1,x2,...,xn)}其中, x i x_i xi代表域变量, P P P具有三种形式的原子公式: ⟨ x 1 , x 2 , . . . , x n ⟩ ∈ R x θ c x θ y \begin{aligned} &\lang x_1, x_2, ..., x_n \rang \in R \\ &x\ \theta\ c \\ &x\ \theta\ y \end{aligned} ⟨x1,x2,...,xn⟩∈Rx θ cx θ y且P的逻辑运算 P ∨ Q P \vee Q P∨Q、 P ∧ Q P \wedge Q P∧Q、 ¬ P \neg P ¬P也是一个演算公式。且有 ∀ ( t ∈ R ) ( P ( t ) ) \forall(t \in R)(P(t)) ∀(t∈R)(P(t))也是一个演算公式。
不产生无限关系和无穷验证的运算被称为是安全的,关系代数是一种集合运算,是安全的;但关系演算不一定是安全的,考虑有限集合 R ( t ) R(t) R(t),那么 { t ∣ ¬ ( R ( t ) ) } \{ t| \neg(R(t)) \} {t∣¬(R(t))}可能表示无限关系,因为 R ( t ) R(t) R(t)是有限的,但不在 R ( t ) R(t) R(t)中的元素就可能是无限的,从而产生无限关系;再例如 ( ∀ u ) ( w ( u ) ) (\forall u)(w(u)) (∀u)(w(u))验证所有元素是否都使得 w ( u ) w(u) w(u)为真。检验所有元素可能导致无穷验证。
取安全约束有限集合,使得
D
O
M
(
ψ
)
DOM(\psi)
DOM(ψ)是一个有限集合,其中每个符号要么是
ψ
\psi
ψ中明显出现的符号,要么是某个关系R中某个元组的分量,用于约束关系演算的计算范围。那么
{
t
∣
w
(
t
)
}
\{t|w(t)\}
{t∣w(t)}是安全表达式需要满足下列条件:
-
{
t
∣
w
(
t
)
}
\{t|w(t)\}
{t∣w(t)}中的
t
t
t取值只能是
D
O
M
DOM
DOM中的有限个值;
-对于有效的表达式
u
(
w
)
u(w)
u(w),
u
u
u的每个分量都是
D
O
M
DOM
DOM中的成员。
三、SQL
SQL是使用最为广泛的查询语言,且支持定义数据结构、修改数据库以及说明安全性约束条件等。SQL语言有一下几个部分:
-数据定义语言【Data-Definition Language,DDL】,SQL DDL提供定义关系模式、删除关系以及修改关系模式的命令;
-数据操纵语言【Data-Manipulation Language,DML】,SQL DML提供从数据库中查询信息,以及在数据库中插入元组、删除元组、修该元组;
-数据控制语言【Data Control Language,DCL】,用于安全性控制,如授权、撤销授权。
上述语言可以表述丰富的数据库操作。
3.1 数据库的查询
数据库的建立包括定义数据库与关系模式,并向关系中追加元组。SQL DDL的创建关键字是create,创建数据库的语法形式为
create database database_name;
在创建数据库后,需要向数据库内添加表,创建表的语法形式为
create table table_name(
col_name data_type integrity_constraint
...
);
例如一个典型的学生表的定义:
create table Student (
SNo char(8) not null,
Sname char(10),
Ssex integer,
Sage integer,
DNo char(2),
Sclass char(6)
);
当表的建立完成后,需要向表中追加元组。SQL DML的插入关键字为Insert,通常由用户或应用程序员使用,基本格式为
insert into table_name (
col_name, ...
) values (
tuple_value, ...
);
例如在上述表中插入一个学生的元组:
insert into Student (
SNo, Sname, Ssex, Sage, Dno, Sclass
) values (
"21030102", "Li", 0, 20, "03", "210301"
);
当所有的定义的数据项顺序与插入值的顺序一致,那么可以省略数据项名,如
insert into Student values (
"21030102", "Li", 0, 20, "03", "210301"
)
SQL提供了结构形式一致但功能多样化的检索语句select,其形式如下:
select col_name, ...
from table_name, ...
where condition;
该语句从表名所给的关系中,查询满足检索条件的元组,并按给定的数据项命及顺序进行投影,其等价于 Π c o l _ n a m e ( σ c o n d i t i o n ( t a b l e _ n a m e ) ) \Pi_{col\_name}(\sigma_{condition}(table\_name)) Πcol_name(σcondition(table_name))例如检索上述学生表中所有学生的信息,使用
select * from Student; // project all cols by using *
若要检索所有学生的姓名及年龄,使用
select Sname, Sage
from Student;
而要检索年龄不大于19岁的学生的年龄及姓名,则使用
select Sage, Sname
from Student
where Sage <= 19;
要注意的是,检索条件的逻辑运算使用and,or,not进行标识,表达式使用<、>、<=、>=、=、<>。
为了在投影后得到无重复的元组,可以使用关键字distinct来保证结果的唯一性,形如
select distinct col_name, ...
from table_name, ...
where condition;
select语句还可以通过order关键字进行排序,形如
select col_name, ...
from table_name, ...
where condition;
order by col_name order_model;
其中,排序模式默认为asc,即升序;或指明为desc,为降序。例如在某表SC中,检索002号课大于80分的所有同学的学号并按成绩由高到低的顺序排列,其SQL语句为
select SNo
from SC
where CNo = "002" and Score > 80
order by Score desc;
在查询过程中,可能需要模糊查询,使用关键字like来表示条件语句,形如
col_name like dst_value
col_name not like dst_value
其中,dst_value可以包括:
-%,用于匹配零个或单个字符;
-_,用于匹配单个字符;
-\,转义字符,如用%以匹配%。
例如检索张姓学生的学号和姓名,形如
select SNo, Sname
from Student
where Sname like "%Zhang"
要注意的是,中文的每个字占据两个字符的长度。
当select语句的参数中存在多个表时,表示多个表进行笛卡尔积后的选择操作。当需要对表进行连接操作时,需要在条件语句中指明,例如
select Sname
from Student, SC
where Student.SNo = SC.SNo and SC.CNo = "001"
order by Score desc;
在多表连接时,使用点符号进行连接,以限定属性的所属表。上述语句中,SC表不包含姓名属性,而Student表不包含课程属性,为了查询按001号课程按从高到低顺序的名字,通过对学号的自然连接完成。
当不同表连接时,若出现两个表的属性重名,则使用as关键字命相关名称,即别名进行区分,形如
select col_name as col_ano_name, ...
from table_name as table_ano_name, ...
where condition
例如存在某薪水表,数据项为姓名与薪资,那么若要比较薪水有差额的任意两位教师,则需要进行明别名连接,形如
select T1.Tname as Tname1, T2.Tname as Tname2
from Teacher as T1, Teacher as T2
where T1.Salary > T2.Salary;
此外,当表名、数据项名过长时,也可以使用别名进行简化。
3.2 数据库的修改
元组的新增指令有两种形式,包括单一元组的新增指令
insert into table_name (
col_name, ...
) values (
tuple_value, ...
);
以及批数据新增指令
insert into table_name (
col_name, ...
) subselect;
其待插入的元组由子查询给出。例如将符合条件的同学新增到表中
insert into ST (
SNo, Sname
) select SNo, Sname
from Student
where Sname like "%Zhang";
若在新增元组时,DBMS检查到不符合完整性约束条件,则不会执行新增动作。
元组的删除指令使用关键字delete,用于删除某些满足条件的元组,形如
delete from table_name
where condition;
当省略where关键字时,表示将表中的所有元组删除。
元组的更新指令使用关键字update,用指定要求的值更新表中的元组的指定列的值,形如
update table_name
set coL_name = value, ...
where contion;
当省略where关键字时,表示将表中的所有元组更新。考虑在教师薪资表中,为02系教师的薪资上调5%,形如
update Teacher
set Salary = Salary * 1.05;
当数据库的某些属性需要发生更改时,就需要修正数据库,主要是修正表的定义,使用关键字alter,形如
alter table table_name
add col_name datatype, ...
drop integrity_constraint(col_name);
其中,add用于增加数据项,也可以替换为modify,用于修改数据项定义,而drop用于删除完整性约束。例如为Student表增加数据项Saddr与PID,形如
alter table Student
add Saddr char[40], PID char[18];
或删除学生姓名的唯一约束
alter table Student
drop unique(Sname);
也可以使用drop撤销表,形如
drop table table_name;
用于彻底删除数据库中的表及其结构。同样,也可以撤销数据库,形如
drop database database_name;
有些DBMS提供操作多个数据库的能力,此时需要指定待操作数据和关闭数据库的功能,形如
use database_name;
close database_name;
3.3 数据库的子查询
SQL提供嵌套子查询机制,其是嵌套在另一个查询中的查询表达式。子查询通常嵌套在where关键字的子句中,通常对集合的成员资格、集合的比较以及集合的基数进行检查。
当出现嵌套子查询时,可能会出现嵌套的select关键字结构,此时会产生外层查询和内层查询。若内层查询独立进行,且不涉及任何外层查询的相关信息,那么该子查询称为非相关子查询;反之,产生依靠的子查询称为相关子查询,如
select Sname
from Student as Student2
where SNo in (
select SNo
from SC
where SNo = Student2.SNo and CNo = "001"
);
且相关子查询只能从外层向内层传递参数,称为变量的作用域原则。
子查询包括多种类型,其中考察集合成员的资格使用in关键字,用于子查询的语句为
select col_name
from table_name
where expression in (subselect);
典型的,查询Student表中名字为张、王的元组集合:
select *
from Student
where Sname in ("Zhang", "Wang");
或列出选修了001号课程的学生的学号和姓名:
select SNo, Sname
from Student
where SNo in (
select SNo
from SC
where CNo = "001"
);
可以在in关键字前加上not以表示否定包含关系。
另一种子查询的形式用于集合之间的比较,使用关键字some与all,some子查询的格式形如
select col_name
from table_name
where expression θ some (subselect);
如果表达式的值至少与子查询结果的一个值相比较满足θ关系,则some子查询的表达式便为真;而all子查询的格式形如
select col_name
from table_name
where expression θ all (subselect);
如果表达式的值与所有子查询结果的值相比较都满足θ关系,则all子查询的表达式便为真。
例如找出工资最低的教师姓名,形如
select Tname
from Teacher
where Salary <= all (
select Salary
from Teacher
);
再例如001号课程成绩不是最高的所有学生的学号,形如
select SNo
from SC
where CNo = "001" and Score < some (
select Score
from SC
where CNo = "001"
);
还有一种测试集合基数,使用exists关键字,形式为
select col_name
from table_name
where exists (subselect);
当exists的表达式为非空时,返回true。例如检索学过001号教师主讲的所有课程的所有同学的姓名,形如
select Sname
from Student
where not exists (
select *
from Course
where Course.TNo = "001" and
not exists (
select *
from SC
where SNo = Student.SNo and
CNo = Course.CNo
)
);
当某同学没有学习任何课程时,有
not exists (
select *
from SC
where SNo = Student.SNo and
CNo = Course.CNo
)
为false。而当某同学学习了课程但课程的教师不是001时,有
not exists (
select *
from Course
where Course.TNo = "001" and
not exists (
select *
from SC
where SNo = Student.SNo and
CNo = Course.CNo
)
);
为false。因此所有学过001教师的课程的所有同学的姓名即为上述式。
3.4 数据库的聚集函数
聚集函数是以值的一个集合为输入,返回单个值的函数。聚集函数可以在select语句中,用于对查询结果进行运算,如求有差额的任意两位教师的薪水差额,形如
select T1.Tname as TR1, T2.Tname as TR2, T1.Salary - T2.Salary
from Teacher T1, Teacher T2
where T1.Salary > T2.Salary;
此时select语句的结果得到的便是教师的薪水差额,而不是Teacher中的某些元组数据项。
SQL提供了五个作用在简单数据项集合上的内置聚集函数,分别是:平均值avg、最小值min、最大值max、总和sum以及计数count。例如求教师的薪水综合,形如
select sum(Salary)
from Teacher;
有时不仅希望讲聚集函数作用在单个元组集的某个数据项上,也希望作用到多个元组集上,使用group by关键字进行分组聚集,形如
select col_name
from table_name
where condition
group by condition;
例如求每个学生的平均成绩,形如
select SNo, avg(Score)
from SC
group by SNo;
其对每一个SNo相同的所有Score数据项求平均值。而求每一门课程的平均成绩,形如
select CNo, avg(Score)
from SC
group by CNo;
其对每一个CNo相同的所有Score数据项求平均值。
聚集函数不允许出现在where子句中,因为where是对元组进行条件检索,而聚集函数是对集合进行条件检索。要对集合进行条件检索,需要使用having关键字,需要group by关键字支持。例如求不及格课程超过两门的同学的学号,形如
select SNo
from SC
where Score < 60
group by SNo having Count(*) > 2;
3.5 SQL的关系代数
SQL的并运算、交运算与差运算分别为
subselect1 union subselect2
subselect1 intersect subselect2
subselect1 except subselect2
且使用all关键字保留重复元组,如
subselect1 union all subselect2
例如检索学过002号课或003号课程的同学学号,可以是
select SNo
from SC
where CNo = "002" union
select SNo
from SC
where CNo = "003";
或者等价于
select SNo
from SC
where CNo = "002" or CNo = "003";
当数据库中出现空值,会影响聚集函数运算的正确性,影响算术、逻辑运算等。空值使用null关键字来标记,并且通过语句
subselect is null;
subselect is not null;
来判断,例如检索年龄值为空的学生姓名,形如
select Sname
from Student
where Sage is null;
现行的DBMS的空值处理标准如下:
-空值不满足除了is null以外任何的查找条件;
-空值参与算术运算的结果为null;
-空值参与逻辑运算的结果为false;
-空值参与聚集运算时,除了count()函数外均忽略,且count()认为null是一条有效记录。
SQL引入了连接运算,作用于from关键字,包括连接类型与连接条件。连接类型包括θ-连接inner join、外连接left outer join、right outer join及full outer join,其形式为
select col_name
from table_name1 join table_name2 on condition using cols
其中,在join关键字前使用关键字natural,则进行表的自然连接;如果使用on关键字,则指定θ-连接的条件;如果使用using关键字,则要求using后跟的所有数据项均相等时方可连接元组。
3.6 视图
对应概念模式在SQL称为表,而对应外模式的数据称为视图。视图不仅包括外模式,还包括E-C映像。让用户看到逻辑模型是不合适的,SQL允许通过查询来定义虚关系,在概念上包含查询的结果。
SQL通过create view定义视图,形如
create view view_name
as subselect
例如定义一个视图CompStud为03系的学生,通过该视图将其他系的学生屏蔽掉,形如
create view CompStud
as (
select *
from Student
where DNo in (
select DNo
from Dept
where Dname = "03"
)
);
当视图产生后,就可以与表一样,在SQL语句中使用。
要注意的是,视图不保存数据,对视图的更新可能并不能反映到对表的更新上,且有时视图定义的映射是不可逆的。
3.7 完整性约束
数据库的完整性是指DBMS应该保证DB在任何情况下的正确性、有效性和一致性,完整性约束保证授权用户对数据库所做的修改不会破坏数据的一致性。
静态约束是一种数据库的完整性约束,要求DB在任意时候均应满足的约束。例如Sage属性应该满足大于0而小于150,其语义为学生的年龄应该在0到150之间。create table可以用于定义完整性约束,形如
create table table_name (
col_name data_type col_integrity_constraint
..., table_integrity_constraint
);
其中,列约束定义于每列之后,包括:
-not null,声明禁止在该属性上插入空值;
-unique,声明属性形成候选码;
-primary key,声明属性是主键;
-check(condition),要求列值满足条件时才可以作为列值。
以及可选项:
-constraint constraint_name,用于命名约束以供操作;
-references table_name col_name,声明属性是外键,并声明外表的主键;
-on delete cascade,外表主键被删除时,该表的外键删除;
-on delete set null,外表主键被删除时,该表的外键置空;
一个典型的例子为
create table Student (
SNo char(8) not null unique,
Sname char(10),
Ssex char(1) constraint ctSsex check(Ssex = 'M' or Ssex = 'F'),
Sage integer check(Sage >= 1 and Sage < 150),
DNo char(2) references Dept(Dno) on delete cascade,
Sclass char(6)
);
而表约束定义于所有列之后,包括:
-unique,声明属性形成候选码;
-primary key,声明属性是主键;
-check(condition),要求列值满足条件时才可以作为列值;
-foreign key,声明属性是外键。
以及可选项:
-constraint constraint_name,用于命名约束以供操作;
-references table_name col_name,声明属性是外键,并声明外表的主键;
-on delete cascade,外表主键被删除时,该表的外键删除;
-on delete set null,外表主键被删除时,该表的外键置空;
一个典型的例子为
create table Student (
SNo char(8) not null unique,
Sname char(10),
Ssex char(1) constraint ctSsex check(Ssex = 'M' or Ssex = 'F'),
Sage integer check(Sage >= 1 and Sage < 150),
DNo char(2) references Dept(Dno) on delete cascade,
Sclass char(6),
primary key(SNo) // table constraint
);
其中,check中的条件可以是子查询。
create table定义中的约束可以根据需要,通过alter table进行撤销或者追加约束,关键字为add增加约束,modify修改约束,drop删除约束。
表约束和列约束是一种特殊的断言,断言表达了希望数据库总能满足的条件,有一定的表示形式,但目前较少使用。
动态约束要求DB的状态变更满足一定的约束。动态约束使用触发器用于满足过程完整性约束。触发器的基本形式为
create trigger trigger_name
after event
of col_name
on table_name
referencing new new_name, old old_name
for object
when (condition)
begin
statement
end;
其语义为当事件发生时,对事件产生的结果进行检查,当条件为真时执行声明的语句。具体细节通过下述例子说明:
create trigger teacher_chgsal
before update
of salary
on Teacher
referencing new x, old y
for each row
when (x.salary < y.salary)
begin
raise_app_error(-20003, "invalid salary on update");
// error handle function of oracle
end;
其语义为在Teacher表的salary数据项更新时,对于所有元组,若新的值小于旧的值,则在更新前报错,并且不进行更新。再考虑例子
create trigger sumc
after insert
on SC
referencing new row newi
for each row
begin
update student
set SumCourse = SumCourse + 1
where SNo = newi.SNo;
end;
其语义为在SC表发生插入时,对于所有元组,学号对应的学号的SumCourse增1,并在插入之后完成增加,插入发生。
3.8 数据库安全性
数据库的安全性是指DBMS应该保证数据库免受非法、非授权用户的使用、泄露、更改或破坏的特性。
自主安全性是一种DBMS的安全机制,其通过权限在用户之间传递,使用户自主管理数据库的安全性。自主安全性通常使用授权机制实现。
自主安全性的一种实现方式是存储矩阵,其一个维度是数据对象,另一个维度是主体,矩阵数据是权限,那么该矩阵的语义为主题对数据对象的访问权限。
另一种实现方式是视图,限制用户对关系中某些数据项的存取,通过选择与投影为特定的主体提供相应的数据。
根据权限,用户可以划分为超级用户、程序员用户与普通用户,而操作权限可以分为创建级权限、更新级权限与读取级权限。上述权限的操作通过grant关键字操纵,形如
grant priviledge
on table_name
to usr
with grant option;
其中,各数据段可以是:
-privilege,包括select、insert、update、delete或all priviledges;
-table_name,可以是表名,也可以是视图名;
-usr,可以是public给予所有有效用户,或特定的usr,是由DBA创建的一个合法账户;
-with grant option,当该可选项存在时,允许被授权者传播这些权利。
同样的,收回授权使用revoke关键字,基本命令相似,形如
revoke priviledge
on table_name
from usr;
强制安全性是另一种DBMS的安全机制,通过对数据和用户的强制分类,使得不同类别的用户能够访问不同类别的数据。
强制安全性分为绝密【TS】、机密【S】、可信【C】、无分类【U】。其访问规则为高级别用户可以读取低级别数据,而低级别用户可以写高级别数据,这是因为高级别用户写数据后,会将数据的级别提升为高。
关系中的每个元组都扩展为带有安全分级的元组,用于定义用户与数据的强制安全性等级。
四、嵌入式SQL
特别复杂的检索结果可能难以用一条交互式SQL完成,可能需要结合高级语言中经常使用的控制来解决。高级语言嵌入SQL既继承了高级语言的过程控制性,又结合SQL的复杂结果集操作的非过程性,同时为数据库操作者提供安全可靠的应用程序操作方式。
在C中,通过exec sql语句嵌入SQL,使SQL语句提供给C编译器,以便对SQL语句预编译成C编译器可识别的语句。
4.1 数据库连接
开放数据库互联【Open DataBase Connectivity,ODBC】标准定义了一个接口,应用程序用它来打开一个数据库连接。
在嵌入式SQL程序执行之前,需要与数据库进行连接,并且对于不同的DBMS,具体连接语句的语法略有差别,C对SQL标准的连接语法为
exec sql connect
to target_server
as connect_name
user usr_name;
或存在数据库默认值,则语法为
exec sql connect
to default;
在嵌入式SQL程序执行之后,需要与数据库断开连接,形如
exec sql disconnect connect_name;
或
exec sql disconnect current;
在SQL语句执行过程中,需要提交与撤销语句确认操作结果,形如
exec sql commit work;
exec sql rollback work;
用于保证在断开连接之前,用户确认提交或撤销先前的工作。从程序员的角度讲,一个存取或改变数据库内容的程序的一次执行被看作一个事务。事务一般由程序员提出,包括一系列的SQL操作,形如
begin transaction
exec sql ...
...
exec sql ...
exec sql commit work
end transaction
在提交时,上述SQL操作有效,而撤销时,上述SQL操作无效。事务保证了数据库管理系统的一致性状态转换。事务有如下特性:
-原子性,事务的一组操作是不可分的,要么全都执行,要么全都不执行;
-一致性,事务的操作状态是正确的;
-隔离性,并发执行的多个事务之间互不影响;
-持久性,事务的提交是持久的,事务的撤销是可恢复的。
事务的处理是数据库的核心技术。
4.2 嵌入式SQL变量声明
当高级语言需要将变量传递给SQL,为了区分高级语言的变量与SQL的数据项,在C中进行特殊的声明,形如
exec sql begin declare section;
datatype para_name;
...
exec sql end declare section;
并在SQL语句中,通过into关键字及:para_name进行赋值,形如
exec sql select col_name
into :para_name
from table_name
where condition;
例如声明
exec sql begin declare section;
char vSname[10], specName[10] = 'Zhang';
int vSage;
exec sql end declare section;
并在C中使用exec sql嵌入SQL:
exec sql select Sname, Sage
into :vSname, :vSage
from Student
where Sname = :specName;
上述语句会将Student表中Sname数据项与C的specName变量相等的元组的Sname与Sage赋值给C的vSname与vSage变量。
要注意的是,高级语言可能与SQL的类型之间可能存在差异,例如C的字符串变量的长度应该比SQL字符型字段的长度多1,这是因为C的字符串尾部存在终止符\0,而SQL不存在。
通过上述的声明,可以在C中赋值,然后传递给SQL语句的where语句,使得通过C的赋值,SQL可以按照可变的指定要求进行检索,而无须改变SQL语句。
4.3 嵌入式SQL数据处理
当SQL检索结果是单个元组时,可以将结果直接传送到高级程序的变量中。然而,当检索结果时多个元组时,需要使用游标,其是指向某个检索元组的指针,通过指针的移动,逐个处理检索的结果。
使用游标首先要进行定义,形如
exec sql declare cur_name for select col_name
from table_name
where condition;
用于定义某个表对于某条件检索的多元组的游标,并在打开后执行,打开语句形如
exec sql open cur_name
当执行游标后,可以进行操作语句,形如
exec sql fetch cur_name into :para_name
每执行一次fetch指令,游标会向下移动一个元组。在使用后关闭游标,形如
exec sql close cur_name
游标在定义一次之后,可以多次执行关闭。
标准的游标始终是自开始向结束方向移动的,一条记录只能访问一次,需要再次访问时只能关闭游标后重新打开。
4.4 嵌入式SQL操作数据库
嵌入式SQL的删除包括查找删除,其与交互式SQL原理与语句一致,形如
exec sql delete
from table_name
where condition
或使用游标进行定位删除,形如
exec sql delete
from table_name
where current of cur_name
考虑如下例子
exec sql delete
from customers
where c.city = 'H' and
not exists (
select * from orders
where orders.cid = customers.cid
)
该语句选取了H城市的在orders表中不存在的客户。其等价于
exec sql declare delcust cursor for select cid
from customers
where c.city = 'H' and
not exists (
select * from orders
where orders.cid = customers.cid
)
for update of cid;
exec sql open delcust;
while (True) {
exec sql fetch delcust into :cust_id;
exec sql delete
from customers
where current of delcust;
}
相似的,嵌入式SQL的更新包括查找更新与定位更新,查找更新形如
exec sql update table_name
set col_name = value
where current of cur_name;
嵌入式SQL的插入仅有一种类型,因为游标与插入无关。插入形如
exec sql insert into table_name (
col_name, ...
) values (
tuple_value, ...
);
4.5 异常状态捕获机制
高级语言需要明确嵌入式SQL的执行状态,尤其是一些异常状态,并要给出状态的处理。
SQL通信区域【SQL Communication Area,SQLCA】是一个已被声明过的C结构形式的内存信息区,其成员变量用来记录SQL语句执行的状态,是DBMS与高级语言之间交流的桥梁之一,一般在嵌入式SQL程序的头部声明设置,形如
exec sql include sqlca;
在声明SQLCA后,可以使用whenever语句设置陷阱,会对其后的所有由exec sql所引起的数据库系统的调用自动检查条件,形如
exec sql whenever condition action;
其中,检查条件可以是:
-sqlerror,检查SQL语句的错误;
-not found,检查SQL检索的结果记录是否未出现;
-sqlwarning,检查应该引起注意的条件。
而当满足上述检查条件时,可以采取如下动作:
-continue,忽略错误或警告;
-goto hanle_fuc,转移到处理标记指示的语句;
-stop,终止程序,撤销工作,断开数据库连接;
-do,调用高级语言的函数进行处理,也可以使用call,函数返回后从引发condition的语句之后执行。
whenever的作用范围是其后的所有exec sql语句,直到程序中出现相同检查条件的whenever语句,并覆盖掉原来的动作。要注意的是,状态捕获语句可能会引起死循环,例如
int main() {
exec sql whenever sqlerror goto handle_error; // error
// some codes
handle_error:
exec sql drop customers; //error
// some codes
}
当执行主函数的语句引起错误时,进入错误处理语句,但错误处理语句位于whenever转移动作的作用范围内,当错误处理语句本身会出错时,会再次跳转到错误处理语句,从而进入死循环。为了避免这种情况,在错误处理语句中应更改whenever的动作,形如
int main() {
exec sql whenever sqlerror goto handle_error; // error
// some codes
handle_error:
exec sql whenever sqlerror continue;
exec sql drop customers; //error
// some codes
}
DBMS提供sqlcode变量记录sql语句的状态,SQLCA也存在sqlca.sqlcode记录sql语句的状态,而某些DBMS则使用sqlstate或sqlca.sqlstate变量。例如
exec sql whenever sqlerror continue;
exec sql create table custs (
cid char(4) not null,
cname varchar(13),
// some codes
)
if (strcmp(sqlcode, "82100") == 0)
// handle function 82100
4.6 动态SQL
静态SQL在高级语言程序中已经按照需求嵌入SQL代码,只需要把一些参数通过变量传入嵌入式SQL即可;而动态SQL在高级语言程序中动态的构造SQL语句,通过构造包含SQL语句的字符串并交付DBMS执行,并传递变量。
动态SQL首先需要在高级语言程序中定义字符串,用于保存SQL语句,例如定义删除语句
char sqltext[] = "delete from customers where";
并通过程序赋予字符串相应的SQL语句,通过exec sql语句执行,形如
exec sql execute immediate :sqltext;
该语句在运行时编译并立即执行。
动态SQL还可以使用
exec sql prepare sql_temp from :host-var;
进行预编译,再在运行时将动态参数值传送给编译的SQL语句,形如
exec sql execute sql_temp using :cond-var;
例如
exec sql begin declare section;
char cust_id[5];
char sqltext[256];
exec sql end declare section;
strcpy(sqltext, "delete from customers where cid = :precust_cid");
// some codes
exec sql prepare delcust from sqltext;
exec sql execute delcust using :cust_id
其中,:precust_cid所在语句仅被编译,from sqltext将会准备SQL语句,再执行using :cust_id,使得SQL语句中未被赋值的:precust_cid将会被:cust_id赋值。
由于动态SQL依据条件动态构造SQL语句,但是对于应用层面,需要访问的表名、字段都是未知的,就需要使用数据字典,是系统维护的表或视图的集合,存储了数据库中各类对象的定义信息。数据字典同样是一个关系,可以通过SQL语句访问。为了访问数据字典,需要访问SQL描述符区域【SQL Descriptor Area,SQLDA】,其是一个内存数据结构,用于装载关系模式的定义信息。