理论背景
SQL 即 Structured Query Language ,它是为查询和管理关系型数据库管理系统(RDBMS)中的数据而专门设计的一种标准语言。RDBMS 是一种基于关系模型的数据库管理系统,而关系模型则是一种用于表示数据的语义模型。Microsoft 提供的 T-SQL 是标准 SQL 的一种方言(dialect)或扩展,在它的 RDBMS(Microsoft SQL Server)上负责处理数据。
关系模型是独立于语言的,也就是说,除了 SQL 以外,还可以用其它语言来实现关系模型,如 C# 中的 类模型。
SQL
SQL 是基于关系模型的 ANSI 和 ISO 标准语言,专门设计用于查询和管理 RDBMS 中的数据。
早在 20 世纪 70 年代,IBM 就为其 RDBMS 产品(System R)开发了一种名为 SEQUEL(Structured English Query Language)的语言。后来因为商标纠纷,就把这种语言的名字从 SEQUEL 修改成了 SQL。SQL 最初与 1986 年成为一项 ANSI 标准,之后又于 1987 年成为了一项 ISO 标准。从 1986 年开始,ANSI 和 ISO 每隔几年就会发布对 SQL 标准的修订,到目前为止,总共发布过以下一系列标准:SQL-86(1986)、SQL-89(1989)、SQL-92(1992)、SQL:1999(1999)、SQL:2003(2003)、SQL:2006(2006)以及 SQL:2008(2008)。
有趣的是,SQL 与英语有些相像,也很讲究逻辑性。与许多其它编程语言不同的是,SQL 只需告诉它你想要得到什么,而不必告诉它如何得到这些东西。RDBMS 的任务就是计算出如何处理这一请求的物理实现机制。
SQL 有几种不同类型的语句,包括数据定义语言(DDL,Data Definition Language)、数据处理语言(DML,Data Manipulation Language)以及数据控制语言(DCL,Data Control Language)。
DDL 用于处理数据对象的定义,包括的语句有 CREATE、ALTER 以及 DROP。DML 用于查询和修改数据,包括的语句有SELECT、INSERT、UPDATE、DELETE 以及 MERGE。人们通常误以为 DML 只包括数据修改语句,但实际上它其实也包含 SELECT 语句在内。DCL 用于处理权限管理,包括的语句有 GRANT 和 REVOKE。
T-SQL 以标准 SQL 作为基础,同时也提供了一些非标准的(或专有的)扩展。在后续文章中第一次提及某个语言元素时,通常都会介绍它是否是一个标准元素。
DQL(数据查询语言):在某些地方可能会看到 DQL 这个字眼,实质上它是将 DML 的 SELECT 语句单独抽离出来作为 DQL。
集合论
集合论的创始人是数据家格奥·康托(Georg Cantor),关系模型就建立在这一数学分支的基础之上。康托对集合的定义如下:
所谓“集合”是把我们直观或思维中确定的、相互间有明确区别的那些对象 m 视为一个整体 M,这一整体 M 就称为集合(称 m 为集合 M 的元素)。
——Joseph W.Dauben, Georg Cantor(Princeton University Press,1990)
这个定义中的每个词都有一定的深度和重要内涵。但是这些晦涩的术语难免让人感到茫然,我们再来看看另一个非正式的定义:所谓集合就是把我们直观或思维确定的、不同的那些对象作为一个整体来考虑的结果,这些对象就是集合的元素或成员。
我们先考虑康托的集合定义中的一个词“整体”。应该将集合视为一个整体,重点关注的应该是一组对象,而不是组成集合的单独对象。
“不同的”这个词是指集合中的每一个元素必须是唯一的。在一个表中,可以通过定义键(key)约束条件来强制要求表中每行数据的唯一性。没有键,就不能唯一标识一行数据,数据表也就不能满足集合的要求。相反,这样的数据表将成为多集(multiset)或包(bag)。
“我们直观或思维中”表示集合的定义具有一定的主观性。设想在一个教室中,一个人可能认为看到的是一群人,而另一个人则可能认为看到的是一群学生和一群老师。因此在集合的定义上尚有很大的自由空间。当为数据库设计数据模型时,在设计过程中应该仔细考虑应用程序的主观需要,以便为涉及的实体定义合适的集合。
还有一些康托的集合定义中没有提到的内容同样很重要。康托的集合定义中没有提及集合元素之间的顺序关系,集合元素的排列顺序关系并不重要,用于列举集合元素的正式表示方法是用一对大括号如 {a,b,c} 来表示。因为元祖之间的顺序无关紧要,所以 {a,b,c} 和 {a,c,b} 是同一个集合的两种表现形式。很多程序员短时间内都很难接受查询数据表时,返回的集合元素之间没有一定顺序的事实。换句话说,对数据表的查询可以返回按任何顺序排列的数据行,除非你明确要求按照一定的表示目的对它们进行排序。
谓词逻辑(Predicate Logic)
谓词逻辑的起源最早可以追溯到古希腊,它也是关系模型基于的另一个数学分支。Dr.Edgar F.Codd 当初在创建关系模型时,就已经洞悉到了谓词逻辑与数据的管理和查询的关联。不严格地说,谓词就是用来刻画事务是否具有某种性质或满足某种表达式条件的一个词项,换句话说,也就是 true 或 false。在关系模型中,谓词用于维护数据的逻辑完整性和定义它的结构。用谓词来实施完整性的一个例子,可以在 Employees 表中定义一个约束(constraint),只有薪资大于 0 的雇员数据才可以保存到表中。这里使用的谓词就是“薪资(salary)大于 0”(T-SQL 表达式就是:salary > 0)。
谓词也可以用于对数据进行过滤以定义其子集等多种场合。例如,如果要查询 Employees 表,而且只要返回来自销售部门的雇员信息,就可以在查询过滤条件中使用谓词:“部门(department)为销售(sales)”(T-SQL 表达式就是:department = 'sales')。
在集合论中,可以用谓词来定义集合。这是有用的,因为不可能通过列举集合的全部元素来定义一个集合(例如无限集合)。有时为了简练,以集合元素的某个属性为基础来定义集合则更为方便。一个用谓词来定义无限集合的例子,可以使用以下谓词来定义所有质数的集合:“x 是一个大于 1 的正整数,而且只能被 1 和它本身整除”。对于给定的任意一个值,该谓词要么为 true,要么为 false。而所有质数的集合就是让这个谓词为 true 的所有元素的集合。作为一个用谓词来定义的有限集合的例子,集合 {0,1,2,3,4,5,6,7,8,9} 可以定义为让一下谓词为 true 的所有元素的集合:“x 是一个大于或等于 0 且小于或等于 9 的整数”。
关系模型(Relational Model)
关系模型是一个用于表示数据的语义模型,其理论基础是集合论和谓词逻辑。前面已经提到,关系模型最初是由 Dr.Edgar F.Codd 创建的,后来 Chris Date、Hugh Darwen 等人对这一模型进行了进一步的解释和演化。
关系模型的目标是要用最少的或完全无冗余的描述来表示一个持久化的完整数据,而且还要将数据完整性(强制的数据一致性)定义为模型的一部分。RDBMS 则可以实现这样的关系模型,并提供存储、管理、实施强制的数据完整性以及查询数据的手段。关系模型有着坚实的数学理论基础这一事实,可以保证对于给定的某个数据模型实例(以后将据此生成物理的数据库)能够明确地判断出其设计是否存在瑕疵,而不是仅靠直觉经验来得出结论。
命题、谓词和关系
人们通常认为“关系(型)(relational)”这一术语源于数据表之间的关系,但是这种观点是不正确的。“关系(型)”这一术语其实与数学术语“关系(relation)”有关。在集合论中,关系是集合的一种表示。在关系模型中,关系时相关信息的一个集合,在数据库的实现就是数据表。关系模型的一个关键要点就是:一个关系代表一个集合(例如:Customers)。很有意思的是,对关系进行操作的结果,得到的还是一个关系(例如两个关系之间的联接(join)运算)。
为数据库设计数据模型时,所有数据都是用关系(数据表)来表示的。首先要确定一个命题(proposition),用于表示数据库中要保存的信息。命题就是必须为真(true)或假(false)的一个断言或语句。例如:“员工 Jack 出生于 1971 年 2 月 12 日,隶属于 IT 部门”就是一个命题。如果这个命题为真,就表示它是 Employees 表中的一行;如果这个命题为假,就表示它不是 Employees 表中的一行。
接下来就对命题进行形式化(formalize)处理,也就是从命题中分析出具体的数据(关系本身),并定义其结构(关系的标题/列名)。例如,根据命题来创建它的谓词。而关系的标题则组成了属性(列)的集合(set)。在这里注意一下“集合”这一术语的使用。在关系模型中,属性是没有顺序的。属性(列)是没有顺序的。属性是由一个属性名称和一个域(或类型)名称来标识的。例如,Employees 关系的标题可能是由以下属性组成的(用属性名称和相应的类型名称对来表示):employeeod 整型、firstname 字符串型、lastname 字符串型、birthday 日期型、departmentid 整型。域或类型是最基础的关系型构建模块之一。一个域就是一个属性可能的(或有效的)一组取值的集合。例如,INT 域的范围就是在 -2147483648 到 2147483647 之间的所有证书。域是一种数据库中最简单形式的谓词,因为它用于限制属性允许的值。例如,数据库不会接受“员工的生日是 1971 年 2 月 31 日”这样的一个命题(更不用说生日是“abc”这样的命题了)。注意,域并不只限于整型或字符串这样的基本类型,例如,它可以是可能取值的枚举类型(enumeration),比如代表可能的工作职位的枚举值。获取,域的最佳处理方式就是把它看做一个类——封装了数据和支持它的行为。
缺少的值
关系模型中有一个方面一直引起很多争议——命题是否应该只限于使用二只谓词逻辑。当使用二值谓词逻辑时,一个命题要么为 true,要么就为 false;如果一个命题不是真的,那么它一定是假的。然而,如果考虑到现实中的一些不确定的情况,有人会说,还应该存在三值谓词逻辑(或者甚至是四值)。例如,以 Employees 关系的 cellphone(手机)属性为例。假设缺少某位员工的手机号,这时应该怎么在数据库中表示这个事实呢?如果用三值谓词逻辑来实现这个域,就可以为 cellphone 属性赋予一个表示缺少值这种状态的特殊值(如 NULL)。
约束(Constraint)
关系模型最大的优点就是将数据完整性定义为模型的一部分。完整性是通过规则(或约束)来实施的,它们在数据模型中定义,并由 RDBMS 实施。实施完整性最简单的方法就是设置属性的类型和是否允许控制(即是否支持 NULL),这也就是实施所谓的域完整性(Domain Integrity)。也可以通过模型本身来实施约束:例如,关系 Orders(orderid,orderdate,duedate,shipdate)允许每个订单具有三个不同的日期;而关系 Employees(empid)和 EmployeeChildren(empid,childname)则允许每个员工可以有零个到可数的无限多个孩子。
约束的其它例子还包括提供实体完整性(entity integrity)的候选键,以及提供引用完整性(reference integrity)的外键。候选键(candidate key)是指在关系中能够防止同一元组(数据行)多次出现的属性集(一个或多个属性)。基于候选键的谓词能够唯一地标识一行数据(例如一个员工)。在关系中可以定义多个候选键,例如,在 Employees 关系中,可以在 employeeid,ssn(social security number,社会保险号),以及其它属性上定义的候选键。可以任意选择一个候选键作为主键(primary key),以此作为标识一行的首选方法,而其它候选键则成为备用关键字(alternate key)。
外键(foreign key)用于实施引用完整性。外键是在关系(称为引用关系,referencing relation)中的一个或多个属性上定义的,通过它来引用另一个(也可能是同一个)关系中的候选键。这种约束要求引用关系中外键的属性值要与被引用关系(referenced relation)的候选键属性值相一致。例如,假设 Employees 关系具有一个定义在 departmentid 属性上的外键,引用了 Departments 关系的主键属性 departmentid,那么,这就意味着 Employees.departmentid 属性只能取 Departments。departmentid 属性中出现过的那些值。
规范化(Normalization)
关系模型同时也定义了规范化规则(也成为范式)。规范化是一种形式化的数学处理过程,以确保每个实体只由一个关系来表示。在规范化的数据库中,应该避免对数据的异常修改,而且在不牺牲完整性的前提下,将数据冗余降低到最小。如果严格遵循实体关系建模(ERM,Entity Relationship Modeling)方法来构造关系模型,并且表示出每个实体及其属性,可能就无须规范化;否则,应该用规范化原则来加强和保证模型的正确性。
第一范式 第一范式要求表中的行必须是唯一的,属性应该是原子的(atomic)。这个范式对关系的定义来说是冗余的,换句话说,如果一个表真可以表示一个关系,那么它一定符合第一范式。
第二范式 第二范式包括两条规则,首先数据必须满足第一范式,其次要求非键属性(nonkey attribute)和候选键属性之间必须满足一定的条件。对于每个候选键,每个非键属性都必须完全函数依赖于整个候选键。换句话说,一个非键属性不能只完全函数依赖于候选键的一部分。非正式的说,如果不要获得任何非键属性值,就必须提供同一行中某个候选键的所有属性值,如果知道了一个候选键的所有属性值,就能够找到任意行的任意属性值。
第三范式 第三范式也有两条规则。首先,数据必须满足第二范式。其次,所以非键属性必须非传递依赖于候选键。通俗地说,这条规则意味着所有非键属性都必须互相独立。换句话说,一个非键属性不能依赖于其它非键属性。
函数依赖指的是存在组合候选键字中的某些字段决定非关键字段的情况。