数据库系统概念(原书第6版.本科教学版)
基本信息
- 原书名:Database System Concepts,Sixth Edition
- 原出版社: McGraw-Hill Science
- 作者: (美)Abraham Silberschatz Henry .Korth (印)S.Sudarshan
- 译者: 杨冬青 李红燕 唐世渭
- 丛书名: 计算机科学丛书
- 出版社:机械工业出版社
- ISBN:9787111400851
- 上架时间:2012-12-7
- 出版日期:2013 年1月
- 开本:16开
- 页码:435
- 版次:1-1
- 所属分类:计算机 > 数据库 > 综合
教材

内容简介
作译者
Abraham Silberschatz 于纽约州立大学石溪分校获得博士学位,现为耶鲁大学计算机科学Sidney J. Weinberg教授,计算机科学系主任,曾任贝尔实验室信息科学研究中心副主任。他是ACM Fellow 和 IEEE Fellow,曾获得IEEE Taylor L. Booth 教育奖、 ACM Karl V. Karlstrom 杰出教育者奖、ACM SIGMOD 贡献奖和IEEE 计算机学会杰出论文奖。他的研究兴趣包括操作系统、数据库系统、存储系统、网络管理和分布式系统。
Henry F. Korth 于普林斯顿大学获得博士学位,现为利哈伊大学计算机科学与工程系Weiseman教授,曾任贝尔实验室数据库原理研究中心主任。他是ACM Fellow 和 IEEE Fellow,是VLDB 10年贡献奖的获得者。他的研究兴趣包括为现代计算架构(多核、多线程、多级缓存)设计的数据库算法、基于Web的大型数据仓储、实时数据库系统和并行系统。
S. Sudarshan 于威斯康星大学麦迪逊分校获得博士学位,现为印度理工学院计算机科学与工程系教授,曾为贝尔实验室数据库研究组技术人员。他的研究兴趣包括查询处理和优化、关系数据和图结构数据的关键字查询,以及构建和测试数据库应用系统的工具。
杨冬青 1969年毕业于北京大学数学力学系数学专业,现任北京大学信息科学技术学院教授,博士生导师,中国计算机学会数据库专委会委员。多年来承担并完成973、863、国家科技攻关、国家自然科学基金等多项国家重点科研项目,曾获国家科技进步二等奖、三等奖和多项省部级奖励,在国内外杂志及会议上发表论文百余篇,著译作十余部。目前主要研究方向为数据库系统实现技术、Web环境下的信息集成与共享、数据仓库和数据挖掘等。
李红燕 1999年毕业于西北工业大学计算机科学与工程系计算机应用专业,获工学博士学位,现任北京大学信息科学技术学院教授,博士生导师,中国计算机学会数据库专委会委员。多年来承担并完成多项国家自然科学基金课题以及医疗、移动通信等典型应用领域内的应用研究项目,在国内外学术期刊及会议上发表论文90余篇,出版学术专著和教材各1部。目前主要研究方向为数据库系统与智能信息系统、数据仓库与数据挖掘、业务流程控制、云数据管理等。
唐世渭 1964年毕业于北京大学数学力学系计算数学专业,毕业后留校任教至今,现为北京大学信息科学技术学院教授,博士生导师,中国计算机学会数据库专委会委员,中国软件行业协会数据库及应用软件分会理事长。多年来承担并完成973、863、国家科技攻关、国家自然科学基金等多项国家重点科研项目,曾获国家科技进步二等奖、三等奖各1项,省部级科技进步奖多项,在国内外杂志及会议上发表论文百余篇,著译作多部。目前主要研究方向为数据库系统、数据仓库和数据挖掘、Web环境下的信息集成与共享、典型应用领域的信息系统等。
杨冬青、李红燕、唐世渭组织并参加了本书第6版的翻译和审校工作,参加翻译工作的还有范红杰、程序、苗高杉、邹淼、陈巍、王婧、王林青、孟必平。
Henry F. Korth 于普林斯顿大学获得博士学位,现为利哈伊大学计算机科学与工程系Weiseman教授,曾任贝尔实验室数据库原理研究中心主任。他是ACM Fellow 和 IEEE Fellow,是VLDB 10年贡献奖的获得者。他的研究兴趣包括为现代计算架构(多核、多线程、多级缓存)设计的数据库算法、基于Web的大型数据仓储、实时数据库系统和并行系统。
S. Sudarshan 于威斯康星大学麦迪逊分校获得博士学位,现为印度理工学院计算机科学与工程系教授,曾为贝尔实验室数据库研究组技术人员。他的研究兴趣包括查询处理和优化、关系数据和图结构数据的关键字查询,以及构建和测试数据库应用系统的工具。
杨冬青 1969年毕业于北京大学数学力学系数学专业,现任北京大学信息科学技术学院教授,博士生导师,中国计算机学会数据库专委会委员。多年来承担并完成973、863、国家科技攻关、国家自然科学基金等多项国家重点科研项目,曾获国家科技进步二等奖、三等奖和多项省部级奖励,在国内外杂志及会议上发表论文百余篇,著译作十余部。目前主要研究方向为数据库系统实现技术、Web环境下的信息集成与共享、数据仓库和数据挖掘等。
李红燕 1999年毕业于西北工业大学计算机科学与工程系计算机应用专业,获工学博士学位,现任北京大学信息科学技术学院教授,博士生导师,中国计算机学会数据库专委会委员。多年来承担并完成多项国家自然科学基金课题以及医疗、移动通信等典型应用领域内的应用研究项目,在国内外学术期刊及会议上发表论文90余篇,出版学术专著和教材各1部。目前主要研究方向为数据库系统与智能信息系统、数据仓库与数据挖掘、业务流程控制、云数据管理等。
唐世渭 1964年毕业于北京大学数学力学系计算数学专业,毕业后留校任教至今,现为北京大学信息科学技术学院教授,博士生导师,中国计算机学会数据库专委会委员,中国软件行业协会数据库及应用软件分会理事长。多年来承担并完成973、863、国家科技攻关、国家自然科学基金等多项国家重点科研项目,曾获国家科技进步二等奖、三等奖各1项,省部级科技进步奖多项,在国内外杂志及会议上发表论文百余篇,著译作多部。目前主要研究方向为数据库系统、数据仓库和数据挖掘、Web环境下的信息集成与共享、典型应用领域的信息系统等。
杨冬青、李红燕、唐世渭组织并参加了本书第6版的翻译和审校工作,参加翻译工作的还有范红杰、程序、苗高杉、邹淼、陈巍、王婧、王林青、孟必平。
目录
《数据库系统概念(原书第6版.本科教学版)》
出版者的话
改编者序
译者简介
前言
作者简介
第1章引言
1.1数据库系统的应用
1.2数据库系统的目标
1.3数据视图
1.3.1数据抽象
1.3.2实例和模式
1.3.3数据模型
1.4数据库语言
1.4.1数据操纵语言
1.4.2数据定义语言
1.5关系数据库
1.5.1表
1.5.2数据操纵语言
1.5.3数据定义语言
出版者的话
改编者序
译者简介
前言
作者简介
第1章引言
1.1数据库系统的应用
1.2数据库系统的目标
1.3数据视图
1.3.1数据抽象
1.3.2实例和模式
1.3.3数据模型
1.4数据库语言
1.4.1数据操纵语言
1.4.2数据定义语言
1.5关系数据库
1.5.1表
1.5.2数据操纵语言
1.5.3数据定义语言
1.5.4来自应用程序的数据库访问
1.6数据库设计
1.6.1设计过程
1.6.2大学机构的数据库设计
1.6.3实体-联系模型
1.6.4规范化
1.7数据存储和查询
1.7.1存储管理器
1.7.2查询处理器
1.8事务管理
1.9数据库体系结构
1.9.1客户/服务器系统
1.9.2并行数据库系统
1.9.3分布式数据库系统
1.10数据挖掘与信息检索
1.11特种数据库
1.11.1基于对象的数据模型
1.11.2半结构化数据模型
1.12数据库用户和管理员
1.12.1数据库用户和用户界面
1.12.2数据库管理员
1.13数据库系统的历史
1.14总结
术语回顾
实践习题
习题
工具
文献注解
第一部分关系数据库
第2章关系模型介绍
2.1关系数据库的结构
2.2数据库模式
2.3码
2.4模式图
2.5关系查询语言
2.6关系运算
2.7总结
术语回顾
实践习题
习题
文献注解
第3章SQL
3.1SQL查询语言概览
3.2SQL数据定义
3.2.1基本类型
3.2.2基本模式定义
3.3SQL查询的基本结构
3.3.1单关系查询
3.3.2多关系查询
3.3.3自然连接
3.4附加的基本运算
3.4.1更名运算
3.4.2字符串运算
3.4.3select子句中的属性说明
3.4.4排列元组的显示次序
3.4.5where子句谓词
3.5集合运算
3.5.1并运算
3.5.2交运算
3.5.3差运算
3.6空值
3.7聚集函数
3.7.1基本聚集
3.7.2分组聚集
3.7.3having子句
3.7.4对空值和布尔值的聚集
3.8嵌套子查询
3.8.1集合成员资格
3.8.2集合的比较
3.8.3空关系测试
3.8.4重复元组存在性测试
3.8.5from子句中的子查询
3.8.6with子句
3.8.7标量子查询
3.9数据库的修改
3.9.1删除
3.9.2插入
3.9.3更新
3.10总结
术语回顾
实践习题
习题
工具
文献注解
第4章中级SQL
4.1连接表达式
4.1.1连接条件
4.1.2外连接
4.1.3连接类型和条件
4.2视图
4.2.1视图定义
4.2.2SQL查询中使用视图
4.2.3物化视图
4.2.4视图更新
4.3事务
4.4完整性约束
4.4.1单个关系上的约束
4.4.2not.null约束
4.4.3unique约束
4.4.4check子句
4.4.5参照完整性
4.4.6事务中对完整性约束的违反
4.4.7复杂check条件与断言
4.5SQL的数据类型与模式
4.5.1SQL中的日期和时间类型
4.5.2默认值
4.5.3创建索引
4.5.4大对象类型
4.5.5用户定义的类型
4.5.6create.table的扩展
4.5.7模式、目录与环境
4.6授权
4.6.1权限的授予与收回
4.6.2角色
4.6.3视图的授权
4.6.4模式的授权
4.6.5权限的转移
4.6.6权限的收回
4.7总结
术语回顾
实践习题
习题
文献注解
第5章高级SQL
5.1使用程序设计语言访问数据库
5.1.1JDBC
5.1.2ODBC
5.1.3嵌入式SQL
5.2函数和过程
5.2.1声明和调用SQL函数和过程
5.2.2支持过程和函数的语言构造
5.2.3外部语言过程
5.3触发器
5.3.1对触发器的需求
5.3.2SQL中的触发器
5.3.3何时不用触发器
5.4递归查询**
5.4.1用迭代来计算传递闭包
5.4.2SQL中的递归
5.5高级聚集特性**
5.5.1排名
5.5.2分窗
5.6OLAP**
5.6.1联机分析处理
5.6.2交叉表与关系表
5.6.3SQL中的OLAP
5.7总结
术语回顾
实践习题
习题
工具
文献注解
第6章形式化关系查询语言
6.1关系代数
6.1.1基本运算
6.1.2关系代数的形式化定义
6.1.3附加的关系代数运算
6.1.4扩展的关系代数运算
6.2元组关系演算
6.2.1查询示例
6.2.2形式化定义
6.2.3表达式的安全性
6.2.4语言的表达能力
6.3域关系演算
6.3.1形式化定义
6.3.2查询的例子
6.3.3表达式的安全性
6.3.4语言的表达能力
6.4总结
术语回顾
实践习题
习题
文献注解
第二部分数据库设计
第7章数据库设计和E-R模型
7.1设计过程概览
7.1.1设计阶段
7.1.2设计选择
7.2实体-联系模型
7.2.1实体集
7.2.2联系集
7.2.3属性
7.3约束
7.3.1映射基数
7.3.2参与约束
7.3.3码
7.4从实体集中删除冗余属性
7.5实体-联系图
7.5.1基本结构
7.5.2映射基数
7.5.3复杂的属性
7.5.4角色
7.5.5非二元的联系集
7.5.6弱实体集
7.5.7大学的E-R图
7.6转换为关系模式
7.6.1具有简单属性的强实体集的表示
7.6.2具有复杂属性的强实体集的表示
7.6.3弱实体集的表示
7.6.4联系集的表示
7.7实体-联系设计问题
7.7.1用实体集还是用属性
7.7.2用实体集还是用联系集
7.7.3二元还是n元联系集
7.7.4联系属性的布局
7.8扩展的E-R特性
7.8.1特化
7.8.2概化
7.8.3属性继承
7.8.4概化上的约束
7.8.5聚集
7.8.6转换为关系模式
7.9数据建模的其他表示法
7.9.1E-R图的其他表示法
7.9.2统一建模语言UML
7.10数据库设计的其他方面
7.10.1数据约束和关系数据库设计
7.10.2使用需求:查询、性能
7.10.3授权需求
7.10.4数据流、工作流
7.10.5数据库设计的其他问题
7.11总结
术语回顾
实践习题
习题
工具
文献注解
第8章关系数据库设计
8.1好的关系设计的特点
8.1.1设计选择:更大的模式
8.1.2设计选择:更小的模式
8.2原子域和第一范式
8.3使用函数依赖进行分解
8.3.1码和函数依赖
8.3.2Boyce-Codd范式
8.3.3BCNF和保持依赖
8.3.4第三范式
8.3.5更高的范式
8.4函数依赖理论
8.4.1函数依赖集的闭包
8.4.2属性集的闭包
8.4.3正则覆盖
8.4.4无损分解
8.4.5保持依赖
8.5分解算法
8.5.1BCNF分解
8.5.23NF分解
8.5.33NF算法的正确性
8.5.4BCNF和3NF的比较
8.6使用多值依赖的分解
8.6.1多值依赖
8.6.2第四范式
8.6.34NF分解
8.7更多的范式
8.8数据库设计过程
8.8.1E-R模型和规范化
8.8.2属性和联系的命名
8.8.3为了性能去规范化
8.8.4其他设计问题
8.9时态数据建模
8.10总结
术语回顾
实践习题
习题
文献注解
第9章应用设计和开发
9.1应用程序和用户界面
9.2Web基础
9.2.1统一资源定位符
9.2.2超文本标记语言
9.2.3Web服务器和会话
9.3servlet和JSP
9.3.1一个servlet的例子
9.3.2servlet会话
9.3.3servlet的生命周期
9.3.4servlet支持
9.3.5服务器端脚本
9.3.6客户端脚本
9.4应用架构
9.4.1业务逻辑层
9.4.2数据访问层和对象-关系映射
9.4.3Web服务
9.4.4断连操作
9.5快速应用开发
9.5.1构建用户界面的工具
9.5.2Web应用框架
9.5.3报表生成器
9.6应用程序性能
9.6.1利用缓存减少开销
9.6.2并行处理
9.7应用程序安全性
9.7.1SQL注入
9.7.2跨站点脚本和请求伪造
9.7.3密码泄露
9.7.4应用程序认证
9.7.5应用级授权
9.7.6审计追踪
9.7.7隐私
9.8加密及其应用
9.8.1加密技术
9.8.2数据库中的加密支持
9.8.3加密和认证
9.9总结
术语回顾
实践习题
习题
项目建议
工具
文献注解
第三部分数据存储、查询和事务管理
第10章数据存储和数据存取
10.1物理存储介质概述
10.2磁盘和闪存
10.2.1磁盘的物理特性
10.2.2磁盘性能的度量
10.2.3磁盘块访问的优化
10.2.4快闪存储
10.3文件和记录的组织
10.3.1文件组织
10.3.2文件中记录的组织
10.4数据字典存储
10.5数据库缓冲区
10.5.1缓冲区管理器
10.5.2缓冲区替换策略
10.6索引的基本概念
10.7顺序索引
10.7.1稠密索引和稀疏索引
10.7.2多级索引
10.7.3辅助索引
10.7.4多码上的索引
10.8B+树索引文件
10.8.1B+树的结构
10.8.2B+树的查询
10.8.3B+树的更新
10.8.4不唯一的搜索码
10.8.5B+树更新的复杂性
10.9散列文件组织和散列索引
10.9.1散列函数
10.9.2桶溢出处理
10.9.3散列索引
10.9.4动态散列
10.9.5顺序索引和散列的比较
10.10SQL中的索引定义
10.11总结
术语回顾
实践习题
习题
文献注解
第11章查询处理和查询优化
11.1概述
.11.2查询代价的度量
.11.3关系代数运算的执行
11.3.1选择运算
11.3.2连接运算
11.4表达式计算
11.4.1物化
11.4.2流水线
11.5查询优化
11.5.1查询优化概述
11.5.2关系表达式的转换
11.5.3表达式结果集统计大小的估计
11.5.4执行计划选择
11.6总结
术语回顾
实践习题
习题
文献注解
第12章事务管理
12.1事务概念
12.2事务原子性和持久性
12.3事务隔离性
12.4可串行化
12.5可恢复性
12.5.1可恢复调度
12.5.2无级联调度
12.5.3事务隔离性级别
12.6并发控制
12.6.1基于锁的协议
12.6.2基于时间戳的协议
12.6.3基于有效性检查的协议
12.7恢复系统
12.7.1故障分类
12.7.2数据访问
12.7.3恢复与原子性
12.8总结
术语回顾
实践习题
习题
文献注解
第四部分高级话题
第13章数据仓库与数据挖掘
13.1决策支持系统
13.2数据仓库
13.2.1数据仓库成分
13.2.2数据仓库模式
13.2.3面向列的存储
13.3数据挖掘
13.3.1分类
13.3.2关联规则
13.3.3聚类
13.3.4其他类型的数据挖掘
13.4总结
术语回顾
实践习题
习题
工具
文献注解
第14章基于对象的数据库
14.1概述
14.2复杂数据类型
14.3SQL中的结构类型和继承
14.3.1结构类型
14.3.2类型继承
14.4表继承
14.5SQL中的数组和多重集合类型
14.5.1创建和访问集合体值
14.5.2查询以集合体为值的属性
14.5.3嵌套和解除嵌套
14.6SQL中的对象标识和引用类型
14.7O-R特性的实现
14.8持久化程序设计语言
14.8.1对象的持久化
14.8.2对象标识和指针
14.8.3持久对象的存储和访问
14.8.4持久化C++系统
14.8.5持久化Java系统
14.9对象-关系映射
14.10面向对象与对象-关系
14.11总结
术语回顾
实践习题
习题
工具
文献注解
第15章XML
15.1动机
15.2XML数据结构
15.3XML文档模式
15.3.1文档类型定义
15.3.2XML.Schema
15.4查询和转换
15.4.1XML树模型
15.4.2XPath
15.4.3XQuery
15.5XML应用程序接口
15.6XML数据存储
15.6.1非关系的数据存储
15.6.2关系数据库
15.6.3SQL/XML
15.7XML应用
15.7.1存储复杂结构数据
15.7.2标准化数据交换格式
15.7.3Web服务
15.7.4数据中介
15.8总结
术语回顾
实践习题
习题
工具
文献注解
第16章高级应用开发
16.1性能调整
16.1.1提高面向集合的特性
16.1.2批量加载和更新的调整
16.1.3瓶颈位置
16.1.4可调参数
16.1.5硬件调整
16.1.6模式调整
16.1.7索引调整
16.1.8使用物化视图
16.1.9物理设计的自动调整
16.1.10并发事务调整
16.1.11性能模拟
16.2性能基准程序
16.2.1任务集
16.2.2数据库应用类型
16.2.3TPC基准程序
16.3应用系统开发的其他问题
16.3.1应用系统测试
16.3.2应用系统移植
16.4标准化
16.4.1SQL标准
16.4.2数据库连接标准
16.4.3对象数据库标准
16.4.4基于XML的标准
16.5总结
术语回顾
实践习题
习题
文献注解
参考文献
1.6数据库设计
1.6.1设计过程
1.6.2大学机构的数据库设计
1.6.3实体-联系模型
1.6.4规范化
1.7数据存储和查询
1.7.1存储管理器
1.7.2查询处理器
1.8事务管理
1.9数据库体系结构
1.9.1客户/服务器系统
1.9.2并行数据库系统
1.9.3分布式数据库系统
1.10数据挖掘与信息检索
1.11特种数据库
1.11.1基于对象的数据模型
1.11.2半结构化数据模型
1.12数据库用户和管理员
1.12.1数据库用户和用户界面
1.12.2数据库管理员
1.13数据库系统的历史
1.14总结
术语回顾
实践习题
习题
工具
文献注解
第一部分关系数据库
第2章关系模型介绍
2.1关系数据库的结构
2.2数据库模式
2.3码
2.4模式图
2.5关系查询语言
2.6关系运算
2.7总结
术语回顾
实践习题
习题
文献注解
第3章SQL
3.1SQL查询语言概览
3.2SQL数据定义
3.2.1基本类型
3.2.2基本模式定义
3.3SQL查询的基本结构
3.3.1单关系查询
3.3.2多关系查询
3.3.3自然连接
3.4附加的基本运算
3.4.1更名运算
3.4.2字符串运算
3.4.3select子句中的属性说明
3.4.4排列元组的显示次序
3.4.5where子句谓词
3.5集合运算
3.5.1并运算
3.5.2交运算
3.5.3差运算
3.6空值
3.7聚集函数
3.7.1基本聚集
3.7.2分组聚集
3.7.3having子句
3.7.4对空值和布尔值的聚集
3.8嵌套子查询
3.8.1集合成员资格
3.8.2集合的比较
3.8.3空关系测试
3.8.4重复元组存在性测试
3.8.5from子句中的子查询
3.8.6with子句
3.8.7标量子查询
3.9数据库的修改
3.9.1删除
3.9.2插入
3.9.3更新
3.10总结
术语回顾
实践习题
习题
工具
文献注解
第4章中级SQL
4.1连接表达式
4.1.1连接条件
4.1.2外连接
4.1.3连接类型和条件
4.2视图
4.2.1视图定义
4.2.2SQL查询中使用视图
4.2.3物化视图
4.2.4视图更新
4.3事务
4.4完整性约束
4.4.1单个关系上的约束
4.4.2not.null约束
4.4.3unique约束
4.4.4check子句
4.4.5参照完整性
4.4.6事务中对完整性约束的违反
4.4.7复杂check条件与断言
4.5SQL的数据类型与模式
4.5.1SQL中的日期和时间类型
4.5.2默认值
4.5.3创建索引
4.5.4大对象类型
4.5.5用户定义的类型
4.5.6create.table的扩展
4.5.7模式、目录与环境
4.6授权
4.6.1权限的授予与收回
4.6.2角色
4.6.3视图的授权
4.6.4模式的授权
4.6.5权限的转移
4.6.6权限的收回
4.7总结
术语回顾
实践习题
习题
文献注解
第5章高级SQL
5.1使用程序设计语言访问数据库
5.1.1JDBC
5.1.2ODBC
5.1.3嵌入式SQL
5.2函数和过程
5.2.1声明和调用SQL函数和过程
5.2.2支持过程和函数的语言构造
5.2.3外部语言过程
5.3触发器
5.3.1对触发器的需求
5.3.2SQL中的触发器
5.3.3何时不用触发器
5.4递归查询**
5.4.1用迭代来计算传递闭包
5.4.2SQL中的递归
5.5高级聚集特性**
5.5.1排名
5.5.2分窗
5.6OLAP**
5.6.1联机分析处理
5.6.2交叉表与关系表
5.6.3SQL中的OLAP
5.7总结
术语回顾
实践习题
习题
工具
文献注解
第6章形式化关系查询语言
6.1关系代数
6.1.1基本运算
6.1.2关系代数的形式化定义
6.1.3附加的关系代数运算
6.1.4扩展的关系代数运算
6.2元组关系演算
6.2.1查询示例
6.2.2形式化定义
6.2.3表达式的安全性
6.2.4语言的表达能力
6.3域关系演算
6.3.1形式化定义
6.3.2查询的例子
6.3.3表达式的安全性
6.3.4语言的表达能力
6.4总结
术语回顾
实践习题
习题
文献注解
第二部分数据库设计
第7章数据库设计和E-R模型
7.1设计过程概览
7.1.1设计阶段
7.1.2设计选择
7.2实体-联系模型
7.2.1实体集
7.2.2联系集
7.2.3属性
7.3约束
7.3.1映射基数
7.3.2参与约束
7.3.3码
7.4从实体集中删除冗余属性
7.5实体-联系图
7.5.1基本结构
7.5.2映射基数
7.5.3复杂的属性
7.5.4角色
7.5.5非二元的联系集
7.5.6弱实体集
7.5.7大学的E-R图
7.6转换为关系模式
7.6.1具有简单属性的强实体集的表示
7.6.2具有复杂属性的强实体集的表示
7.6.3弱实体集的表示
7.6.4联系集的表示
7.7实体-联系设计问题
7.7.1用实体集还是用属性
7.7.2用实体集还是用联系集
7.7.3二元还是n元联系集
7.7.4联系属性的布局
7.8扩展的E-R特性
7.8.1特化
7.8.2概化
7.8.3属性继承
7.8.4概化上的约束
7.8.5聚集
7.8.6转换为关系模式
7.9数据建模的其他表示法
7.9.1E-R图的其他表示法
7.9.2统一建模语言UML
7.10数据库设计的其他方面
7.10.1数据约束和关系数据库设计
7.10.2使用需求:查询、性能
7.10.3授权需求
7.10.4数据流、工作流
7.10.5数据库设计的其他问题
7.11总结
术语回顾
实践习题
习题
工具
文献注解
第8章关系数据库设计
8.1好的关系设计的特点
8.1.1设计选择:更大的模式
8.1.2设计选择:更小的模式
8.2原子域和第一范式
8.3使用函数依赖进行分解
8.3.1码和函数依赖
8.3.2Boyce-Codd范式
8.3.3BCNF和保持依赖
8.3.4第三范式
8.3.5更高的范式
8.4函数依赖理论
8.4.1函数依赖集的闭包
8.4.2属性集的闭包
8.4.3正则覆盖
8.4.4无损分解
8.4.5保持依赖
8.5分解算法
8.5.1BCNF分解
8.5.23NF分解
8.5.33NF算法的正确性
8.5.4BCNF和3NF的比较
8.6使用多值依赖的分解
8.6.1多值依赖
8.6.2第四范式
8.6.34NF分解
8.7更多的范式
8.8数据库设计过程
8.8.1E-R模型和规范化
8.8.2属性和联系的命名
8.8.3为了性能去规范化
8.8.4其他设计问题
8.9时态数据建模
8.10总结
术语回顾
实践习题
习题
文献注解
第9章应用设计和开发
9.1应用程序和用户界面
9.2Web基础
9.2.1统一资源定位符
9.2.2超文本标记语言
9.2.3Web服务器和会话
9.3servlet和JSP
9.3.1一个servlet的例子
9.3.2servlet会话
9.3.3servlet的生命周期
9.3.4servlet支持
9.3.5服务器端脚本
9.3.6客户端脚本
9.4应用架构
9.4.1业务逻辑层
9.4.2数据访问层和对象-关系映射
9.4.3Web服务
9.4.4断连操作
9.5快速应用开发
9.5.1构建用户界面的工具
9.5.2Web应用框架
9.5.3报表生成器
9.6应用程序性能
9.6.1利用缓存减少开销
9.6.2并行处理
9.7应用程序安全性
9.7.1SQL注入
9.7.2跨站点脚本和请求伪造
9.7.3密码泄露
9.7.4应用程序认证
9.7.5应用级授权
9.7.6审计追踪
9.7.7隐私
9.8加密及其应用
9.8.1加密技术
9.8.2数据库中的加密支持
9.8.3加密和认证
9.9总结
术语回顾
实践习题
习题
项目建议
工具
文献注解
第三部分数据存储、查询和事务管理
第10章数据存储和数据存取
10.1物理存储介质概述
10.2磁盘和闪存
10.2.1磁盘的物理特性
10.2.2磁盘性能的度量
10.2.3磁盘块访问的优化
10.2.4快闪存储
10.3文件和记录的组织
10.3.1文件组织
10.3.2文件中记录的组织
10.4数据字典存储
10.5数据库缓冲区
10.5.1缓冲区管理器
10.5.2缓冲区替换策略
10.6索引的基本概念
10.7顺序索引
10.7.1稠密索引和稀疏索引
10.7.2多级索引
10.7.3辅助索引
10.7.4多码上的索引
10.8B+树索引文件
10.8.1B+树的结构
10.8.2B+树的查询
10.8.3B+树的更新
10.8.4不唯一的搜索码
10.8.5B+树更新的复杂性
10.9散列文件组织和散列索引
10.9.1散列函数
10.9.2桶溢出处理
10.9.3散列索引
10.9.4动态散列
10.9.5顺序索引和散列的比较
10.10SQL中的索引定义
10.11总结
术语回顾
实践习题
习题
文献注解
第11章查询处理和查询优化
11.1概述
.11.2查询代价的度量
.11.3关系代数运算的执行
11.3.1选择运算
11.3.2连接运算
11.4表达式计算
11.4.1物化
11.4.2流水线
11.5查询优化
11.5.1查询优化概述
11.5.2关系表达式的转换
11.5.3表达式结果集统计大小的估计
11.5.4执行计划选择
11.6总结
术语回顾
实践习题
习题
文献注解
第12章事务管理
12.1事务概念
12.2事务原子性和持久性
12.3事务隔离性
12.4可串行化
12.5可恢复性
12.5.1可恢复调度
12.5.2无级联调度
12.5.3事务隔离性级别
12.6并发控制
12.6.1基于锁的协议
12.6.2基于时间戳的协议
12.6.3基于有效性检查的协议
12.7恢复系统
12.7.1故障分类
12.7.2数据访问
12.7.3恢复与原子性
12.8总结
术语回顾
实践习题
习题
文献注解
第四部分高级话题
第13章数据仓库与数据挖掘
13.1决策支持系统
13.2数据仓库
13.2.1数据仓库成分
13.2.2数据仓库模式
13.2.3面向列的存储
13.3数据挖掘
13.3.1分类
13.3.2关联规则
13.3.3聚类
13.3.4其他类型的数据挖掘
13.4总结
术语回顾
实践习题
习题
工具
文献注解
第14章基于对象的数据库
14.1概述
14.2复杂数据类型
14.3SQL中的结构类型和继承
14.3.1结构类型
14.3.2类型继承
14.4表继承
14.5SQL中的数组和多重集合类型
14.5.1创建和访问集合体值
14.5.2查询以集合体为值的属性
14.5.3嵌套和解除嵌套
14.6SQL中的对象标识和引用类型
14.7O-R特性的实现
14.8持久化程序设计语言
14.8.1对象的持久化
14.8.2对象标识和指针
14.8.3持久对象的存储和访问
14.8.4持久化C++系统
14.8.5持久化Java系统
14.9对象-关系映射
14.10面向对象与对象-关系
14.11总结
术语回顾
实践习题
习题
工具
文献注解
第15章XML
15.1动机
15.2XML数据结构
15.3XML文档模式
15.3.1文档类型定义
15.3.2XML.Schema
15.4查询和转换
15.4.1XML树模型
15.4.2XPath
15.4.3XQuery
15.5XML应用程序接口
15.6XML数据存储
15.6.1非关系的数据存储
15.6.2关系数据库
15.6.3SQL/XML
15.7XML应用
15.7.1存储复杂结构数据
15.7.2标准化数据交换格式
15.7.3Web服务
15.7.4数据中介
15.8总结
术语回顾
实践习题
习题
工具
文献注解
第16章高级应用开发
16.1性能调整
16.1.1提高面向集合的特性
16.1.2批量加载和更新的调整
16.1.3瓶颈位置
16.1.4可调参数
16.1.5硬件调整
16.1.6模式调整
16.1.7索引调整
16.1.8使用物化视图
16.1.9物理设计的自动调整
16.1.10并发事务调整
16.1.11性能模拟
16.2性能基准程序
16.2.1任务集
16.2.2数据库应用类型
16.2.3TPC基准程序
16.3应用系统开发的其他问题
16.3.1应用系统测试
16.3.2应用系统移植
16.4标准化
16.4.1SQL标准
16.4.2数据库连接标准
16.4.3对象数据库标准
16.4.4基于XML的标准
16.5总结
术语回顾
实践习题
习题
文献注解
参考文献
前言
数据库管理已经从一种专门的计算机应用发展为现代计算环境中的一个重要成分,因此,有关数据库系统的知识已成为计算机科学教育中的一个核心部分。
本书改编自《数据库系统概念》第6版,适合作为本科生三年级或四年级数据库入门课程的教科书。在本书中,讲述数据库管理的基本概念,这些概念包括数据库设计、数据库语言、数据库系统实现等多个方面。除了这些作为入门课程的基本内容外,本书还包括了可作为课程补充或作为高级课程介绍性材料的高级内容。
我们仅要求读者熟悉基本的数据结构、计算机组织结构和一种高级程序设计语言,例如Java、C或Pascal。书中的概念都以直观的方式加以描述,其中的许多概念都基于我们大学运行的例子加以阐释。本书中包括重要的理论结果,但省略了形式化证明,取而代之的是用图表和例子来说明为什么结论是正确的。对于形式化描述和研究结果的证明,读者可以参考文献注解中列出的研究论文和高级教材。
本书中所包括的基本概念和算法通常是基于当今的商品化或试验性的数据库系统中采用的概念和算法。我们的目标是在一个通常环境下描述这些概念和算法,而没有与某个特定的数据库系统绑定。
在这本《数据库系统概念》第6版的改编版本中,我们保留了原书的基本内容,压缩或删除了一些高级内容,其目的是使得本改编版本更适合本科生的数据库入门课程使用。下面我们简单描述本书内容的组织。
本书的组织
本书组织成四个主要部分:
综述(第1章)。第1章对数据库系统的性质和目标进行了一般性综述。我们解释了数据库系统的概念是如何发展的,各数据库系统的共同特性是什么,数据库系统能为用户做什么,以及数据库系统如何与操作系统交互。我们还引入了一个数据库应用的例子:包括多个系、教员、学生和课程的一个大学机构。这个应用作为贯穿全书的运行实例。这一章本质上是诱导性、历史性和解释性的。
第一部分:关系数据库(第2章至第6章)。第2章介绍了数据的关系模型,包括基本概念,诸如关系数据库的结构、数据库模式、码、模式图、关系查询语言和关系操作等。第3~5章主要介绍最具影响力的面向用户的关系语言——SQL。第6章介绍形式化的关系查询语言,包括关系代数、元组关系演算和域关系演算。
这部分描述了数据操纵,包括查询、修改、插入和删除(假设已有一个模式设计)。关于模式设计的问题延迟到第二部分讲述。
第二部分:数据库设计(第7章至第9章)。第7章给出了数据库设计过程的概要介绍,主要侧重于用实体-联系数据模型来进行数据库设计。实体-联系模型为数据库设计问题,以及我们在数据模型的约束下捕获现实应用的语义时所遇到的问题提供了一个高层视图。UML类图表示也在这一章中讲述。
第8章介绍关系数据库设计理论。这一章讲述了函数依赖和规范化,重点强调提出各种范式的动机,以及它们的直观含义。这一章以关系设计的概览开始,依赖于对函数依赖的逻辑蕴涵的直观理解。这使得规范化的概念可以在函数依赖理论的完整内容之前先作介绍。函数依赖理论将在本章中稍后部分讨论。教师可以只选用81节至83节这些较前面的章节,而不会丢失连贯性。不过,完整地讲授这一章将有利于学生较好地理解规范化概念,从而诱导出函数依赖理论中一些较艰深的概念。
第9章讲述应用设计和开发。这一章侧重于用基于Web的界面构建数据库应用。另外,这一章还讲述了应用安全性。
第三部分:数据存储、查询和事务管理(第10章至第12章)。第10章简单介绍物理存储介质,描述记录是如何映射到文件,然后又如何映射到磁盘中的比特的,并讲解数据库系统使用的几种索引类型。第11章描述如何处理查询,给出用于实现单独操作的算法,并描述查询优化过程。第12章详细阐述事务的概念,包括事务的原子性、一致性、隔离性和持久性,还介绍了几种实现隔离性的并发控制技术,并描述了数据库恢复管理部件(它实现了数据库的原子性与持久性)。
第四部分:高级话题(第13章至第16章)。第13章介绍数据仓库和数据挖掘的概念和主要方法。第14章介绍基于对象的数据库,讲述对象-关系数据模型,还描述了用面向对象的编程语言来访问数据库。第15章介绍数据表示的XML标准(它正日益广泛地应用于复杂数据交换和存储),还描述了XML的查询语言。第16章讨论与高级应用开发相关的性能调整、性能基准程序、标准化等内容。
我们保持《数据库系统概念》第6版的做法,把习题划分成两部分:实践习题(practice exercise)和习题(exercise)。实践习题的解答在《数据库系统概念》第6版的配套网站(wwwdbbookcom)可以得到。我们鼓励学生独立解决这些实践习题,然后用网站上的解答来检查自己的答案。其他习题的解答只有授课教师能得到(参看下面的“配套网站和教学补充材料”以获取如何得到解答的信息)。
授课教师注意事项
本书包括基本内容和高级内容,在一个学期内也许不能讲授所有这些内容。
本书的前12章是最基本的内容,对于入门性课程来说,教师可以选择重点讲授前12章,并介绍第13至16章中的部分内容。
配套网站和教学补充材料
本书改编自《数据库系统概念》第6版,适合作为本科生三年级或四年级数据库入门课程的教科书。在本书中,讲述数据库管理的基本概念,这些概念包括数据库设计、数据库语言、数据库系统实现等多个方面。除了这些作为入门课程的基本内容外,本书还包括了可作为课程补充或作为高级课程介绍性材料的高级内容。
我们仅要求读者熟悉基本的数据结构、计算机组织结构和一种高级程序设计语言,例如Java、C或Pascal。书中的概念都以直观的方式加以描述,其中的许多概念都基于我们大学运行的例子加以阐释。本书中包括重要的理论结果,但省略了形式化证明,取而代之的是用图表和例子来说明为什么结论是正确的。对于形式化描述和研究结果的证明,读者可以参考文献注解中列出的研究论文和高级教材。
本书中所包括的基本概念和算法通常是基于当今的商品化或试验性的数据库系统中采用的概念和算法。我们的目标是在一个通常环境下描述这些概念和算法,而没有与某个特定的数据库系统绑定。
在这本《数据库系统概念》第6版的改编版本中,我们保留了原书的基本内容,压缩或删除了一些高级内容,其目的是使得本改编版本更适合本科生的数据库入门课程使用。下面我们简单描述本书内容的组织。
本书的组织
本书组织成四个主要部分:
综述(第1章)。第1章对数据库系统的性质和目标进行了一般性综述。我们解释了数据库系统的概念是如何发展的,各数据库系统的共同特性是什么,数据库系统能为用户做什么,以及数据库系统如何与操作系统交互。我们还引入了一个数据库应用的例子:包括多个系、教员、学生和课程的一个大学机构。这个应用作为贯穿全书的运行实例。这一章本质上是诱导性、历史性和解释性的。
第一部分:关系数据库(第2章至第6章)。第2章介绍了数据的关系模型,包括基本概念,诸如关系数据库的结构、数据库模式、码、模式图、关系查询语言和关系操作等。第3~5章主要介绍最具影响力的面向用户的关系语言——SQL。第6章介绍形式化的关系查询语言,包括关系代数、元组关系演算和域关系演算。
这部分描述了数据操纵,包括查询、修改、插入和删除(假设已有一个模式设计)。关于模式设计的问题延迟到第二部分讲述。
第二部分:数据库设计(第7章至第9章)。第7章给出了数据库设计过程的概要介绍,主要侧重于用实体-联系数据模型来进行数据库设计。实体-联系模型为数据库设计问题,以及我们在数据模型的约束下捕获现实应用的语义时所遇到的问题提供了一个高层视图。UML类图表示也在这一章中讲述。
第8章介绍关系数据库设计理论。这一章讲述了函数依赖和规范化,重点强调提出各种范式的动机,以及它们的直观含义。这一章以关系设计的概览开始,依赖于对函数依赖的逻辑蕴涵的直观理解。这使得规范化的概念可以在函数依赖理论的完整内容之前先作介绍。函数依赖理论将在本章中稍后部分讨论。教师可以只选用81节至83节这些较前面的章节,而不会丢失连贯性。不过,完整地讲授这一章将有利于学生较好地理解规范化概念,从而诱导出函数依赖理论中一些较艰深的概念。
第9章讲述应用设计和开发。这一章侧重于用基于Web的界面构建数据库应用。另外,这一章还讲述了应用安全性。
第三部分:数据存储、查询和事务管理(第10章至第12章)。第10章简单介绍物理存储介质,描述记录是如何映射到文件,然后又如何映射到磁盘中的比特的,并讲解数据库系统使用的几种索引类型。第11章描述如何处理查询,给出用于实现单独操作的算法,并描述查询优化过程。第12章详细阐述事务的概念,包括事务的原子性、一致性、隔离性和持久性,还介绍了几种实现隔离性的并发控制技术,并描述了数据库恢复管理部件(它实现了数据库的原子性与持久性)。
第四部分:高级话题(第13章至第16章)。第13章介绍数据仓库和数据挖掘的概念和主要方法。第14章介绍基于对象的数据库,讲述对象-关系数据模型,还描述了用面向对象的编程语言来访问数据库。第15章介绍数据表示的XML标准(它正日益广泛地应用于复杂数据交换和存储),还描述了XML的查询语言。第16章讨论与高级应用开发相关的性能调整、性能基准程序、标准化等内容。
我们保持《数据库系统概念》第6版的做法,把习题划分成两部分:实践习题(practice exercise)和习题(exercise)。实践习题的解答在《数据库系统概念》第6版的配套网站(wwwdbbookcom)可以得到。我们鼓励学生独立解决这些实践习题,然后用网站上的解答来检查自己的答案。其他习题的解答只有授课教师能得到(参看下面的“配套网站和教学补充材料”以获取如何得到解答的信息)。
授课教师注意事项
本书包括基本内容和高级内容,在一个学期内也许不能讲授所有这些内容。
本书的前12章是最基本的内容,对于入门性课程来说,教师可以选择重点讲授前12章,并介绍第13至16章中的部分内容。
配套网站和教学补充材料
. 《数据库系统概念》的配套网站是:
http://www.db.book.com
该站点包括:
所有各章的幻灯片
实践习题的答案
五个附录
最新勘误表
实验素材,包括大学模式和习题中用到的其他关系的SQL DDL和样例数据,以及关于建立和使用各种数据库系统和工具的说明书
下列附加材料仅有教师可以获得:
包括书中所有习题答案的教师手册
包括额外习题的问题库
关于如何得到教师手册和问题库的进一步信息,请发电子邮件到customerservice@mcgrawhill.com。McGrawHill关于本书的网页是
http://www.mhhe.com/silberschatz
http://www.db.book.com
该站点包括:
所有各章的幻灯片
实践习题的答案
五个附录
最新勘误表
实验素材,包括大学模式和习题中用到的其他关系的SQL DDL和样例数据,以及关于建立和使用各种数据库系统和工具的说明书
下列附加材料仅有教师可以获得:
包括书中所有习题答案的教师手册
包括额外习题的问题库
关于如何得到教师手册和问题库的进一步信息,请发电子邮件到customerservice@mcgrawhill.com。McGrawHill关于本书的网页是
http://www.mhhe.com/silberschatz
序言
数据库系统是对数据进行存储、管理、处理和维护的软件系统,是现代计算环境中的一个核心成分。随着计算机硬件、软件技术的飞速发展和计算机系统在各行各业的广泛应用,数据库技术的发展尤其迅速,引人注目。有关数据库系统的理论和技术是计算机科学技术教育中必不可少的部分。《数据库系统概念》是一本经典的、备受赞扬的数据库系统教科书,其内容由浅入深,既包含数据库系统的基本概念,又反映数据库技术新进展。本书被国际上许多著名大学采用,并多次再版。
我们先后将本书的第3版、第4版、第5版和第6版译成中文,由机械工业出版社分别于2000年、2003年、2006年和2012年出版发行。国内许多大学采用《数据库系统概念》作为本科生和研究生数据库课程的教材或主要教学参考书,收到了良好的效果。
我们基于《数据库系统概念》第5版进行了改编,保留其中的基本内容,压缩或删除了一些高级内容,形成了该书的本科教学版,其目的是使它更适合本科生的数据库课程使用。该本科教学版由机械工业出版社于2008年出版发行,被国内许多高校采用作为本科生数据库课程的教材或主要教学参考书。
现在我们又基于《数据库系统概念》第6版进行了本科教学版的改编工作,希望它能够成为一本效果更好、更实用的本科生数据库课程的教材。
本书的前9章是最基本的内容,讲述数据库系统的基本概念,包括对数据库系统的性质和目标的综述,对关系数据模型和关系语言的介绍,对数据库设计过程、关系数据库理论以及数据库应用设计和开发的详细讨论。第10至12章介绍了数据库系统实现的核心技术,包括数据存储管理、查询处理和事务管理。第13至16章是高级话题,介绍了数据仓库和数据挖掘,新型的数据库系统——基于对象的数据库和XML数据库,以及与高级应用开发相关的性能调整、性能基准程序、标准化等内容。
本书可作为大学本科数据库概论课程的教材或主要参考资料,教师可以选择重点讲授前12章,并介绍第13至16章中的部分内容。
限于改编者水平,改编中疏漏和错误在所难免,欢迎批评指正。
杨冬青
2012年10月于北京大学
我们先后将本书的第3版、第4版、第5版和第6版译成中文,由机械工业出版社分别于2000年、2003年、2006年和2012年出版发行。国内许多大学采用《数据库系统概念》作为本科生和研究生数据库课程的教材或主要教学参考书,收到了良好的效果。
我们基于《数据库系统概念》第5版进行了改编,保留其中的基本内容,压缩或删除了一些高级内容,形成了该书的本科教学版,其目的是使它更适合本科生的数据库课程使用。该本科教学版由机械工业出版社于2008年出版发行,被国内许多高校采用作为本科生数据库课程的教材或主要教学参考书。
现在我们又基于《数据库系统概念》第6版进行了本科教学版的改编工作,希望它能够成为一本效果更好、更实用的本科生数据库课程的教材。
本书的前9章是最基本的内容,讲述数据库系统的基本概念,包括对数据库系统的性质和目标的综述,对关系数据模型和关系语言的介绍,对数据库设计过程、关系数据库理论以及数据库应用设计和开发的详细讨论。第10至12章介绍了数据库系统实现的核心技术,包括数据存储管理、查询处理和事务管理。第13至16章是高级话题,介绍了数据仓库和数据挖掘,新型的数据库系统——基于对象的数据库和XML数据库,以及与高级应用开发相关的性能调整、性能基准程序、标准化等内容。
本书可作为大学本科数据库概论课程的教材或主要参考资料,教师可以选择重点讲授前12章,并介绍第13至16章中的部分内容。
限于改编者水平,改编中疏漏和错误在所难免,欢迎批评指正。
杨冬青
2012年10月于北京大学
书摘
第1章
Database System Concepts,6E
引言
数据库系统(DataBase System, DBS)由一个互相关联的数据的集合和一组用以访问这些数据的程序组成。这个数据集合通常称作数据库(database),其中包含了关于某个企业的信息。DBS的主要目标是提供一种可以方便、高效地存取数据库信息的途径。
设计数据库系统的目的是为了管理大量信息。对数据的管理既涉及信息存储结构的定义,又涉及信息操作机制的提供。此外,数据库系统还必须提供所存储信息的安全性保证,即使在系统崩溃或有人企图越权访问时也应保障信息的安全性。如果数据将被多用户共享,那么系统还必须设法避免可能产生的异常结果。
在大多数组织中信息是非常重要的,因而计算机科学家开发了大量的用于有效管理数据的概念和技术。这些概念和技术正是本书所关注的。在这一章里,我们将简要介绍数据库系统的基本原理。
1.1数据库系统的应用
数据库的应用非常广泛,以下是一些具有代表性的应用:
企业信息
销售:用于存储客户、产品和购买信息。
会计:用于存储付款、收据、账户余额、资产和其他会计信息。
人力资源:用于存储雇员﹑工资﹑所得税和津贴的信息,以及产生工资单。
生产制造:用于管理供应链,跟踪工厂中产品的生产情况、仓库和商店中产品的详细清单以及产品的订单。
联机零售:用于存储以上所述的销售数据,以及实时的订单跟踪,推荐品清单的生成,还有实时的产品评估的维护。
银行和金融
银行业:用于存储客户信息、账户、贷款,以及银行的交易记录。
信用卡交易:用于记录信用卡消费的情况和产生每月清单。
金融业:用于存储股票、债券等金融票据的持有、出售和买入的信息;也可用于存储实时的市场数据,以便客户能够进行联机交易,公司能够进行自动交易。
大学:用于存储学生信息﹑课程注册和成绩。(此外,还存储通常的单位信息,例如人力资源和会计信息等。)
航空业:用于存储订票和航班的信息。航空业是最先以地理上分布的方式使用数据库的行业之一。
Database System Concepts,6E
引言
数据库系统(DataBase System, DBS)由一个互相关联的数据的集合和一组用以访问这些数据的程序组成。这个数据集合通常称作数据库(database),其中包含了关于某个企业的信息。DBS的主要目标是提供一种可以方便、高效地存取数据库信息的途径。
设计数据库系统的目的是为了管理大量信息。对数据的管理既涉及信息存储结构的定义,又涉及信息操作机制的提供。此外,数据库系统还必须提供所存储信息的安全性保证,即使在系统崩溃或有人企图越权访问时也应保障信息的安全性。如果数据将被多用户共享,那么系统还必须设法避免可能产生的异常结果。
在大多数组织中信息是非常重要的,因而计算机科学家开发了大量的用于有效管理数据的概念和技术。这些概念和技术正是本书所关注的。在这一章里,我们将简要介绍数据库系统的基本原理。
1.1数据库系统的应用
数据库的应用非常广泛,以下是一些具有代表性的应用:
企业信息
销售:用于存储客户、产品和购买信息。
会计:用于存储付款、收据、账户余额、资产和其他会计信息。
人力资源:用于存储雇员﹑工资﹑所得税和津贴的信息,以及产生工资单。
生产制造:用于管理供应链,跟踪工厂中产品的生产情况、仓库和商店中产品的详细清单以及产品的订单。
联机零售:用于存储以上所述的销售数据,以及实时的订单跟踪,推荐品清单的生成,还有实时的产品评估的维护。
银行和金融
银行业:用于存储客户信息、账户、贷款,以及银行的交易记录。
信用卡交易:用于记录信用卡消费的情况和产生每月清单。
金融业:用于存储股票、债券等金融票据的持有、出售和买入的信息;也可用于存储实时的市场数据,以便客户能够进行联机交易,公司能够进行自动交易。
大学:用于存储学生信息﹑课程注册和成绩。(此外,还存储通常的单位信息,例如人力资源和会计信息等。)
航空业:用于存储订票和航班的信息。航空业是最先以地理上分布的方式使用数据库的行业之一。
. 电信业:用于存储通话记录,产生每月账单,维护预付电话卡的余额和存储通信网络的信息。
正如以上所列举的,数据库已经成为当今几乎所有企业不可默认的组成部分,它不仅存储大多数企业都有的普通的信息,也存储各类企业特有的信息。
在20世纪最后的40年中,数据库的使用在所有的企业中都有所增长。在早期,很少有人直接和数据库系统打交道,尽管没有意识到这一点,他们还是与数据库间接地打着交道,比如,通过打印的报表(如信用卡的对账单)或者通过代理(如银行的出纳员和机票预订代理等)与数据库打交道。自动取款机的出现,使用户可以直接和数据库进行交互。计算机的电话界面(交互式语音应答系统)也使得用户可以直接和数据库进行交互,呼叫者可以通过拨号和按电话键来输入信息或选择可选项,来找出如航班的起降时间或注册大学的课程等。
20世纪90年代末的互联网革命急剧地增加了用户对数据库的直接访问。很多组织将他们的访问数据库的电话界面改为Web界面,并提供了大量的在线服务和信息。比如,当你访问一家在线书店,浏览一本书或一个音乐集时,其实你正在访问存储在某个数据库中的数据。当你确认了一个网上订购,你的订单也就保存在了某个数据库中。当你访问一个银行网站,检索你的账户余额和交易信息时,这些信息也是从银行的数据库系统中取出来的。当你访问一个网站时,关于你的一些信息可能会从某个数据库中取出,并且选择出那些适合显示给你的广告。此外,关于你访问网络的数据也可能会存储在一个数据库中。
因此,尽管用户界面隐藏了访问数据库的细节,大多数人甚至没有意识到他们正在和一个数据库打交道,然而访问数据库已经成为当今几乎每个人生活中不可默认的组成部分。
也可以从另一个角度来评判数据库系统的重要性。如今,像Oracle这样的数据库系统厂商是世界上最大的软件公司之一,并且在微软和IBM等这些有多样化产品的公司中,数据库系统也是其产品线的一个重要组成部分。
1.2数据库系统的目标
数据库系统作为商业数据计算机化管理的早期方法而产生。作为20世纪60年代这类方法的典型实例之一,考虑大学组织中的一个部分,除其他数据外,需要保存关于所有教师、学生、系和开设课程的信息。在计算机中保存这些信息的一种方法是将它们存放在操作系统文件中。为了使用户可以对信息进行操作,系统中应有一些对文件进行操作的应用程序,包括:
增加新的学生、教师和课程。
为课程注册学生,并产生班级花名册。
为学生填写成绩、计算绩点(GPA)、产生成绩单。
这些应用程序是由系统程序员根据大学的需求编写的。
随着需求的增长,新的应用程序被加入到系统中。例如,某大学决定创建一个新的专业(例如,计算机科学),那么这个大学就要建立一个新的系并创建新的永久性文件(或在现有文件中添加信息)来记录关于这个系中所有的教师、这个专业的所有学生、开设的课程、学位条件等信息。进而就有可能需要编写新的应用程序来处理这个新专业的特殊规则。也可能会需要编写新的应用程序来处理大学中的新规则。因此,随着时间的推移,越来越多的文件和应用程序就会加入到系统中。
以上所描述的典型的文件处理系统(fileprocessing system)是传统的操作系统所支持的。永久记录被存储在多个不同的文件中,人们编写不同的应用程序来将记录从有关文件中取出或加入到适当的文件中。在数据库系统(DBS)出现以前,各个组织通常都采用这样的系统来存储信息。
在文件处理系统中存储组织信息的主要弊端包括:
数据的冗余和不一致(data redundancy and inconsistency)。由于文件和程序是在很长的一段时间内由不同的程序员创建的,不同文件可能有不同的结构,不同程序可能采用不同的程序设计语言写成。此外,相同的信息可能在几个地方(文件)重复存储。例如,如果某学生有两个专业(例如,音乐和数学),该学生的地址和电话号码就可能既出现在包含音乐系学生记录的文件中,又出现在包含数学系学生记录的文件中。这种冗余除了导致存储和访问开销增大外,还可能导致数据不一致性(data inconsistency),即同一数据的不同副本不一致。例如,学生地址的更改可能在音乐系记录中得到反映而在系统的其他地方却没有。
数据访问困难(difficulty in accessing data)。假设大学的某个办事人员需要找出居住在某个特定邮编地区的所有学生的姓名,于是他要求数据处理部门生成这样的一个列表。由于原始系统的设计者并未预料到会有这样的需求,因此没有现成的应用程序去满足这个需求。但是,系统中却有一个产生所有学生列表的应用程序。这时该办事人员有两种选择:一种是取得所有学生的列表并从中手工提取所需信息,另一种是要求数据处理部门让某个程序员编写相应的应用程序。这两种方案显然都不太令人满意。假设编写了相应的程序,几天以后这个办事人员可能又需要将该列表减少到只列出至少选课60学时的那些学生。可以预见,产生这样一个列表的程序又不存在,这个职员就再次面临前面那两种都不尽如人意的选择。
这里需要指出的是,传统的文件处理环境不支持以一种方便而高效的方式去获取所需数据。我们需要开发通用的、能对变化的需求做出更快反应的数据检索系统。
数据孤立(data isolation)。由于数据分散在不同文件中,这些文件又可能具有不同的格式,因此编写新的应用程序来检索适当数据是很困难的。
完整性问题(integrity problem)。数据库中所存储数据的值必须满足某些特定的一致性约束(consistency constraint)。假设大学为每个系维护一个账户,并且记录各个账户的余额。我们还假设大学要求每个系的账户余额永远不能低于零。开发者通过在各种不同应用程序中加入适当的代码来强制系统中的这些约束。然而,当新的约束加入时,很难通过修改程序来体现这些新的约束。尤其是当约束涉及不同文件中的多个数据项时,问题就变得更加复杂了。
原子性问题(atomicity problem)。如同任何别的设备一样,计算机系统也会发生故障。一旦故障发生,数据就应被恢复到故障发生以前的一致的状态,
对很多应用来说,这样的保证是至关重要的。让我们看看把A系的账户余额中的500美元转入B系的账户余额中的这样一个程序。假设在程序的执行过程中发生了系统故障,很可能A系的余额中减去的500美元还没来得及存入B系的余额中,这就造成了数据库状态的不一致。显然,为了保证数据库的一致性,这里的借和贷两个操作必须是要么都发生,要么都不发生。也就是说,转账这个操作必须是原子的——它要么全部发生要么根本不发生。在传统的文件处理系统中,保持原子性是很难做到的。
并发访问异常(concurrentaccess anomaly)。为了提高系统的总体性能以及加快响应速度,许多系统允许多个用户同时更新数据。实际上,如今最大的互联网零售商每天就可能有来自购买者对其数据的数百万次访问。在这样的环境中,并发的更新操作可能相互影响,有可能导致数据的不一致。设A系的账户余额中有10 000美元,假如系里的两个职员几乎同时从系的账户中取款(例如分别取出500美元和100美元),这样的并发执行就可能使账户处于一种错误的(或不一致的)状态。假设每个取款操作对应执行的程序是读取原始账户余额,在其上减去取款的金额,然后将结果写回。如果两次取款的程序并发执行,可能它们读到的余额都是10 000美元,并将分别写回9500美元和9900美元。A系的账户余额中到底剩下9500美元还是9900美元视哪个程序后写回结果而定,而实际上正确的值应该是9400美元。为了消除这种情况发生的可能性,系统必须进行某种形式的管理。但是,由于数据可能被多个不同的应用程序访问,这些程序相互间事先又没有协调,管理就很难进行。
作为另一个例子,假设为确保注册一门课程的学生人数不超过上限,注册程序维护一个注册了某门课程的学生计数。当一个学生注册时,该程序读入这门课程的当前计数值,核实该计数还没有达到上限,给计数值加1,将计数存回数据库。假设两个学生同时注册,而此时的计数值是(例如)39。尽管两个学生都成功地注册了这门课程,计数值应该是41,然而两个程序执行可能都读到值39,然后都写回值40,导致不正确地只增加了1个注册人数。此外,假设该课程注册人数的上限是40;在上面的例子中,两个学生都成功注册,就导致违反了40个学生为注册上限的规定。
安全性问题(security problem)。并非数据库系统的所有用户都可以访问所有数据。例如在大学中,工资发放人员只需要看到数据库中关于财务信息的那个部分。他们不需要访问关于学术记录的信息。但是,由于应用程序总是即席地加入到文件处理系统中来,这样的安全性约束难以实现。
以上问题以及一些其他问题,促进了数据库系统的发展。接下来我们将看一看数据库系统为了解决上述在文件处理系统中存在的问题而提出的概念和算法。在本书的大部分篇幅中,我们在讨论典型的数据处理应用时总以大学作为实例。
1.3数据视图
数据库系统是一些互相关联的数据以及一组使得用户可以访问和修改这些数据的程序的集合。数据库系统的一个主要目的是给用户提供数据的抽象视图,也就是说,系统隐藏关于数据存储和维护的某些细节。
1.3.1数据抽象
一个可用的系统必须能高效地检索数据。这种高效性的需求促使设计者在数据库中使用复杂的数据结构来表示数据。由于许多数据库系统的用户并未受过计算机专业训练,系统开发人员通过如下几个层次上的抽象来对用户屏蔽复杂性,以简化用户与系统的交互:
物理层(physical level)。最低层次的抽象,描述数据实际上是怎样存储的。物理层详细描述复杂的底层数据结构。
逻辑层(logical level)。比物理层层次稍高的抽象,描述数据库中存储什么数据及这些数据间存在什么关系。这样逻辑层就通过少量相对简单的结构描述了整个数据库。虽然逻辑层的简单结构的实现可能涉及复杂的物理层结构,但逻辑层的用户不必知道这样的复杂性。这称作物理数据独立性(physical data independence)。数据库管理员使用抽象的逻辑层,他必须确定数据库中应该保存哪些信息。
视图层(view level)。最高层次的抽象,只描述整个数据库的某个部分。尽管在逻辑层使用了比较简单的结构,但由于一个大型数据库中所存信息的多样性,仍存在一定程度的复杂性。数据库系统的很多用户并不需要关心所有的信息,
图11数据抽象的三个层次
而只需要访问数据库的一部分。视图层抽象的定义正是为了使这样的用户与系统的交互更简单。系统可以为同一数据库提供多个视图。
这三层抽象的相互关系如图11所示。
通过与程序设计语言中数据类型的概念进行类比,我们可以弄清各层抽象间的区别。大多数高级程序设计语言支持结构化类型的概念。例如,我们可以定义如下记录实际的类型说明依赖于所使用的语言。C和C++使用struct说明。Java没有这样的说明,而可以定义一个简单的类来起到同样的作用。 :
type instructor = record
ID: char(5);
name: char(20);
dept_name: char(20);
salary: numeric(8,2);
end;
以上代码定义了一个具有四个字段的新记录instructor。每个字段有一个字段名和所属类型。对一个大学来说,可能包括几个这样的记录类型:
department,包含字段dept_name、building和budget。
course,包含字段course_id、 title、 dept_name 和credits。
student、 包含字段ID、 name、 dept_name 和tot_cred。
在物理层,一个instructor、department或student记录可能被描述为连续存储位置组成的存储块。
编译器为程序设计人员屏蔽了这一层的细节。与此类似,数据库系统为数据库程序设计人员屏蔽了许多最底层的存储细节。而数据库管理员可能需要了解数据物理组织的某些细节。
在逻辑层,每个这样的记录通过类型定义进行描述,正如前面的代码段所示。在逻辑层上同时还要定义这些记录类型的相互关系。程序设计人员正是在这个抽象层次上使用某种程序设计语言进行工作。与此类似,数据库管理员常常在这个抽象层次上工作。
最后,在视图层,计算机用户看见的是为其屏蔽了数据类型细节的一组应用程序。与此类似,视图层上定义了数据库的多个视图,数据库用户看到的是这些视图。除了屏蔽数据库的逻辑层细节以外,视图还提供了防止用户访问数据库的某些部分的安全性机制。例如,大学注册办公室的职员只能看见数据库中关于学生的那部分信息,而不能访问涉及教师工资的信息。
1.3.2实例和模式
随着时间的推移,信息会被插入或删除,数据库也就发生了改变。特定时刻存储在数据库中的信息的集合称作数据库的一个实例(instance)。而数据库的总体设计称作数据库模式(schema)。数据库模式即使发生变化,也不频繁。
数据库模式和实例的概念可以通过与用程序设计语言写出的程序进行类比来理解。数据库模式对应于程序设计语言中的变量声明(以及与之关联的类型的定义)。每个变量在特定的时刻会有特定的值,程序中变量在某一时刻的值对应于数据库模式的一个实例。
根据前面我们所讨论的不同的抽象层次,数据库系统可以分为几种不同的模式。物理模式(physical schema)在物理层描述数据库的设计,而逻辑模式(logical schema)则在逻辑层描述数据库的设计。数据库在视图层也可以有几种模式,有时称为子模式(subschema),它描述了数据库的不同视图。
在这些模式中,因为程序员使用逻辑模式来构造数据库应用程序,从其对应用程序的效果来看,逻辑模式是目前最重要的一种模式。物理模式隐藏在逻辑模式下,并且通常可以在应用程序丝毫不受影响的情况下被轻易地更改。应用程序如果不依赖于物理模式,它们就被称为是具有物理数据独立性(physical data independence),因此即使物理模式改变了它们也无需重写。
在介绍完下一节数据模型的概念后,我们将学习描述模式的语言。
1.3.3数据模型
数据库结构的基础是数据模型(data model)。数据模型是一个描述数据、数据联系、数据语义以及一致性约束的概念工具的集合。数据模型提供了一种描述物理层、逻辑层以及视图层数据库设计的方式。
下文中,我们将提到几种不同的数据模型。数据模型可被划分为四类:
关系模型(relational model)。关系模型用表的集合来表示数据和数据间的联系。每个表有多个列,每列有唯一的列名。关系模型是基于记录的模型的一种。基于记录的模型的名称的由来是因为数据库是由若干种固定格式的记录来构成的。每个表包含某种特定类型的记录。每个记录类型定义了固定数目的字段(或属性)。表的列对应于记录类型的属性。关系数据模型是使用最广泛的数据模型,当今大量的数据库系统都基于这种关系模型。第2~8章将详细介绍关系模型。
实体-联系模型(entityrelationship model)。实体-联系(ER)数据模型基于对现实世界的这样一种认识:现实世界由一组称作实体的基本对象以及这些对象间的联系构成。实体是现实世界中可区别于其他对象的一件“事情”或一个“物体”。实体-联系模型被广泛用于数据库设计。第7章将详细探讨该模型。
基于对象的数据模型(objectbased data model)。面向对象的程序设计(特别是Java、C++或C#)已经成为占主导地位的软件开发方法。这导致面向对象数据模型的发展,面向对象的数据模型可以看成是ER模型增加了封装、方法(函数)和对象标识等概念后的扩展。对象-关系数据模型结合了面向对象的数据模型和关系数据模型的特征。第14章将讲述对象-关系数据模型。
半结构化数据模型(semistructured data model)。半结构化数据模型允许那些相同类型的数据项含有不同的属性集的数据定义。这和早先提到的数据模型形成了对比:在那些数据模型中所有某种特定类型的数据项必须有相同的属性集。可扩展标记语言(eXtensible Markup Language,XML)被广泛地用来表示半结构化数据。这将在第15章中详述。
在历史上,网状数据模型(network data model)和层次数据模型(hierarchical data model)先于关系数据模型出现。这些模型和底层的实现联系很紧密,并且使数据建模复杂化。因此,除了在某些地方仍在使用的旧数据库中之外,如今它们已经很少被使用了。
1.4数据库语言
数据库系统提供数据定义语言(datadefinition language)来定义数据库模式,以及数据操纵语言(datamanipulation language)来表达数据库的查询和更新。而实际上,数据定义和数据操纵语言并不是两种分离的语言,相反地,它们简单地构成了单一的数据库语言(如广泛使用的SQL语言)的不同部分。
1.4.1数据操纵语言
数据操纵语言(DataManipulation Language,DML)是这样一种语言,它使得用户可以访问或操纵那些按照某种适当的数据模型组织起来的数据。有以下访问类型:
对存储在数据库中的信息进行检索。
向数据库中插入新的信息。
从数据库中删除信息。
修改数据库中存储的信息。
通常有两类基本的数据操纵语言:
过程化DML(procedural DML)要求用户指定需要什么数据以及如何获得这些数据。
声明式DML(declarative DML)(也称为非过程化DML)只要求用户指定需要什么数据,而不指明如何获得这些数据。
通常声明式DML比过程化DML易学易用。但是,由于用户不必指明如何获得数据,数据库系统必须找出一种访问数据的高效途径。
查询(query)是要求对信息进行检索的语句。DML中涉及信息检索的部分称作查询语言(query language)。实践中常把查询语言和数据操纵语言作为同义词使用,尽管从技术上来说这并不正确。
目前有很多商业性的或者实验性的数据库查询语言,我们在第3章、第4章和第5章中将学习最广泛使用的查询语言SQL。我们还会在第6章中学习一些其他的查询语言。
我们在1.3节讨论的抽象层次不仅可以用于定义或构造数据,而且还可以用于操纵数据。在物理层,我们必须定义可高效访问数据的算法;在更高的抽象层次,我们则强调易用性。目标是使人们能够更有效地和系统交互。数据库系统的查询处理器部件(我们将在第11章学习)将DML的查询语句翻译成数据库系统物理层的动作序列。
1.4.2数据定义语言
数据库模式是通过一系列定义来说明的,这些定义由一种称作数据定义语言(DataDefinition Language,DDL)的特殊语言来表达。DDL也可用于定义数据的其他特征。
数据库系统所使用的存储结构和访问方式是通过一系列特殊的DDL语句来说明的,这种特殊的DDL称作数据存储和定义(data storage and definition)语言。这些语句定义了数据库模式的实现细节,而这些细节对用户来说通常是不可见的。
存储在数据库中的数据值必须满足某些一致性约束(consistency constraint)。例如,假设大学要求一个系的账户余额必须不能为负值。DDL语言提供了指定这种约束的工具。每当数据库被更新时,数据库系统都会检查这些约束。通常,约束可以是关于数据库的任意谓词。然而,如果要测试任意谓词,可能代价比较高。因此,数据库系统实现可以以最小代价测试的完整性约束。
域约束(domain constraint)。每个属性都必须对应于一个所有可能的取值构成的域(例如,整数型、字符型、日期/时间型)。声明一种属性属于某种具体的域就相当于约束它可以取的值。域约束是完整性约束的最基本形式。每当有新数据项插入到数据库中,系统就能方便地进行域约束检测。
参照完整性(referential integrity)。我们常常希望,一个关系中给定属性集上的取值也在另一关系的某一属性集的取值中出现(参照完整性)。例如,每门课程所列出的系必须是实际存在的系。更准确地说,一个course记录中的dept_name值必须出现在department关系中的某个记录的dept_name属性中。数据库的修改会导致参照完整性的破坏。当参照完整性约束被违反时,通常的处理是拒绝执行导致完整性被破坏的操作。
断言(assertion)。一个断言就是数据库需要时刻满足的某一条件。域约束和参照完整性约束是断言的特殊形式。然而,还有许多约束不能仅用这几种特殊形式表达。例如,“每一学期每一个系必须至少开设5门课程”,必须表达成一个断言。断言创建以后,系统会检测其有效性。如果断言有效,则以后只有不破坏断言的数据库更新才被允许。
授权(authorization)。我们也许想对用户加以区别,对于不同的用户在数据库中的不同数据值上允许不同的访问类型。这些区别以授权来表达,最常见的是:读权限(read authorization),允许读取数据,但不能修改数据;插入权限(insert authorization),允许插入新数据,但不允许修改已有数据;更新权限(update authorization),允许修改,但不能删除数据;删除权限(delete authorization),允许删除数据。我们可以赋予用户所有的权限,或者没有或部分拥有这些权限。
正如其他任何程序设计语言一样,DDL以一些指令(语句)作为输入,生成一些输出。DDL的输出放在数据字典(data dictionary)中,数据字典包含了元数据(metadata),元数据是关于数据的数据。可把数据字典看作一种特殊的表,这种表只能由数据库系统本身(不是常规的用户)来访问和修改。在读取和修改实际的数据前,数据库系统先要参考数据字典。
图12关系数据库的一个实例
1.5关系数据库
关系数据库基于关系模型,使用一系列表来表达数据以及这些数据之间的联系。关系数据库也包括DML和DDL。在第2章中,我们简单介绍关系模型的基本概念。多数的商用关系数据库系统使用SQL语言,该语言将在第3章、第4章和第5章中详细介绍。第6章我们将讨论其他有影响的语言。
1.5.1表
每个表有多个列,每个列有唯一的名字。图12展示了一个关系数据库示例,它由两个表组成:其一给出大学教师的细节,其二给出大学中各个系的细节。
第一个表是instructor表,表示,例如,ID为22222的名叫Einstein的教师是物理系的成员,他的年薪为95 000美元。第二个表是department表,表示,例如,生物系在Watson大楼,经费预算为90 000美元。当然,一个现实世界的大学会有更多的系和教师。在本书中,我们使用小型的表来描述概念。相同模式的更大型的例子可以在联机的版本中得到。
关系模型是基于记录的模型的一个实例。基于记录的模型,之所以有此称谓,是因为数据库的结构是几种固定格式的记录。每个表包含一种特定类型的记录。每种记录类型定义固定数目的字段或属性。表的列对应记录类型的属性。
不难看出,表可以如何存储在文件中。例如,一个特殊的字符(比如逗号)可以用来分隔记录的不同属性,另一特殊的字符(比如换行符)可以用来分隔记录。对于数据库的开发者和用户,关系模型屏蔽了这些低层实现细节。
我们也注意到,在关系模型中,有可能创建一些有问题的模式,比如出现不必要的冗余信息。例如,假设我们把系的budget存储为instructor记录的一个属性。那么,每当一个经费预算的值(例如,物理系的经费预算)发生变化时,这个变化必须被反映在与物理系相关联的所有教员记录中。在第8章,我们将研究如何区分好的和不好的模式设计。
1.5.2数据操纵语言
SQL查询语言是非过程化的。它以几个表作为输入(也可能只有一个),总是仅返回一个表。下面是一个SQL查询的例子,它找出历史系的所有教员的名字:
select instructor.name
from instructor
where instructor.dept_name = ‘History’;
这个查询指定了从instructor表中要取回的是dept_name为History的那些行,并且这些行的name属性要显示出来。更具体点,执行本查询的结果是一个表,它有一列name和若干行,每一行都是dept_name为 History的一个教员的名字。如果这个查询运行在图12中的表上,那么结果将有两行,一个是名字El Said,另一个是名字Califieri。
查询可以涉及来自不止一个表的信息。例如,下面的查询将找出与经费预算超过95 000美元的系相关联的所有教员的ID和系名。
select instructor.ID,department.dept_name
from instructor, department
where instructor.dept_name=department.dept_name and
department.budget> 95000;
如果上述查询运行在图12中的表上,那么系统将会发现,有两个系的经费预算超过95 000美元——计算机科学系和金融系;这些系里有5位教员。于是,结果将由一个表组成,这个表有两列(ID,dept_name)和五行(12121, Finance)、(45565, Computer Science)、(10101, Computer Science)、(83821, Computer Science)、(76543, Finance)。
1.5.3数据定义语言
SQL提供了一个丰富的DDL语言,通过它,我们可以定义表、完整性约束、断言,等等。
例如,以下的SQL DDL语句定义了department表:
create table department
(dept_name char(20),
building char(15),
budget numeric(12,2));
上面的DDL语句执行的结果就是创建了department表,该表有3个列:dept_name、building和budget,每个列有一个与之相关联的数据类型。在第3章我们将更详细地讨论数据类型。另外,DDL语句还更新了数据字典,它包含元数据(见1.4.2节)。表的模式就是元数据的一个例子。
1.5.4来自应用程序的数据库访问
SQL不像一个通用的图灵机那么强大,即有一些计算可以用通用的程序设计语言来表达,但无法通过SQL来表达。SQL还不支持诸如从用户那儿输入、输出到显示器,或者通过网络通信这样的动作。这样的计算和动作必须用一种宿主语言来写,比如C、C++或Java,在其中使用嵌入式的SQL查询来访问数据库中的数据。应用程序(application program)在这里是指以这种方式与数据库进行交互的程序。在大学系统的例子中,就是那些使学生能够注册课程、产生课程花名册、计算学生的GPA、产生工资支票等的程序。
为了访问数据库,DML语句需要由宿主语言来执行。有两种途径可以做到这一点:
一种是通过提供应用程序接口(过程集),它可以用来将DML和DDL的语句发送给数据库,再取回结果。
与C语言一起使用的开放数据库连接(ODBC)标准,是一种常用的应用程序接口标准。Java数据库连接(JDBC)标准为Java语言提供了相应的特性。
另一种是通过扩展宿主语言的语法,在宿主语言的程序中嵌入DML调用。通常用一个特殊字符作为DML调用的开始,并且通过预处理器,称为DML预编译器(DML precompiler),来将DML语句转变成宿主语言中的过程调用。
1.6数据库设计
数据库系统被设计用来管理大量的信息。这些大量的信息并不是孤立存在的,而是企业行为的一部分;企业的终端产品可以是从数据库中得到的信息,或者是某种设备或服务,数据库对它们起到支持的作用。
数据库设计的主要内容是数据库模式的设计。为设计一个满足企业需求模型的完整的数据库应用环境还要考虑更多的问题。在本书中,我们先着重讨论数据库查询语句的书写以及数据库模式的设计,第9章将讨论应用设计的整个过程。
1.6.1设计过程
高层的数据模型为数据库设计者提供了一个概念框架,去说明数据库用户的数据需求,以及将怎样构造数据库结构以满足这些需求。因此,数据库设计的初始阶段是全面刻画预期的数据库用户的数据需求。为了完成这个任务,数据库设计者有必要和领域专家、数据库用户广泛地交流。这个阶段的成果是制定出用户需求的规格文档。
下一步,设计者选择一个数据模型,并运用该选定的数据模型的概念,将那些需求转换成一个数据库的概念模式。在这个概念设计(conceptualdesign)阶段开发出来的模式提供了企业的详细概述。设计者再复审这个模式,确保所有的数据需求都满足并且相互之间没有冲突,在检查过程中设计者也可以去掉一些冗余的特性。这一阶段的重点是描述数据以及它们之间的联系,而不是指定物理的存储细节。
从关系模型的角度来看,概念设计阶段涉及决定数据库中应该包括哪些属性,以及如何将这些属性组织到多个表中。前者基本上是商业的决策,在本书中我们不进一步讨论。而后者主要是计算机科学的问题,解决这个问题主要有两种方法:一种是使用实体-联系模型(见1.6.3节),另一种是引入一套算法(通称为规范化),这套算法将所有属性集作为输入,生成一组关系表(见1.6.4节)。
一个开发完全的概念模式还将指出企业的功能需求。在功能需求说明(specification of functional requirement)中,用户描述数据之上的各种操作(或事务),例如更新数据、检索特定的数据、删除数据等。在概念设计的这个阶段,设计者可以对模式进行复审,确保它满足功能需求。
现在,将抽象数据模型转换到数据库实现进入最后两个设计阶段。在逻辑设计阶段(logicaldesign phrase),设计者将高层的概念模式映射到要使用的数据库系统的实现数据模型上;然后设计者将得到的特定于系统的数据库模式用到物理设计阶段(physicaldesign phrase)中,在这个阶段中指定数据库的物理特性,这些特性包括文件组织的形式以及内部的存储结构,这些内容将在第10章中讨论。
1.6.2大学机构的数据库设计
为了阐明设计过程,我们来看如何为大学做数据库设计。初始的用户需求说明可以基于与数据库用户的交流以及设计者自己对大学机构的分析。这个设计阶段中的需求描述是制定数据库的概念结构的基础。以下是大学的主要特性:
大学分成多个系。每个系由自己唯一的名字(dept_name)来标识,坐落在特定的建筑物(building)中,有它的经费预算(budget)。
每一个系有一个开设课程列表。每门课程有课程号(course_id)、课程名(title)、系名(dept_name)和学分(credits),还可能有先修要求(prerequisites)。
教师由个人唯一的标识号(ID)来标识。每位教师有姓名(name)、所在的系(dept_name)和工资(salary)。
学生由个人唯一的标识号(ID)来标识。每位学生有姓名(name)、主修的系(dept_name)和已修学分数(tot_cred)。
大学维护一个教室列表,详细说明楼名(building)、房间号(room_number)和容量(capacity)。
大学维护开设的所有课程(开课)的列表。每次开课由课程号(course_id)、开课号(sec_id)、年(year)和学期(semester)来标识,与之相关联的有学期(semester)、年(year)、楼名(building)、房间号(room_number)和时段号(time_slot_id,即上课的时间)。
系有一个教学任务列表,说明每位教师的授课情况。
大学有一个所有学生课程注册的列表,说明每位学生在哪些课程的哪次开课中注册了。
一个真正的大学数据库会比上述的设计复杂得多。然而,我们就用这个简化了的模型来帮助你理解概念思想,避免你迷失在复杂设计的细节中。
1.6.3实体-联系模型
实体-联系(ER)数据模型使用一组称作实体的基本对象,以及这些对象间的联系。实体是现实世界中可区别于其他对象的一件“事情”或一个“物体”。例如,每个人是一个实体,每个银行账户也是一个实体。
数据库中实体通过属性(attribute)集合来描述。例如, 属性dept_name、building与budget可以描述大学中的一个系,并且它们也组成了department实体集的属性。类似地,属性ID、name和salary可以描述instructor实体。机敏的读者会注意到我们从描述instructor实体集的属性集中丢掉了dept_name属性。在第7章中我们将详细解释为什么这样做。
我们用额外的属性ID来唯一标识教师(因为可能存在两位教师有相同的名字和相同的工资)。必须给每位教师分配唯一的教师标识。在美国,许多机构用一个人的社会保障号(它是美国政府分配给每个美国人的一个唯一的号码)作为他的唯一标识。
联系(relationship)是几个实体之间的关联。例如,member联系将一位教师和她所在的系关联在一起。同一类型的所有实体的集合称作实体集(entity set),同一类型的所有联系的集合称作联系集(relationship set)。
数据库的总体逻辑结构(模式)可以用实体-联系图(entityrelationship diagram,ER图)进行图形化表示。有几种方法来画这样的图。最常用的方法之一是采用统一建模语言(Unified Modeling Language,UML)。在我们使用的基于UML的符号中,ER图如下表示:
实体集用矩形框表示,实体名在头部,属性名列在下面。
联系集用连接一对相关的实体集的菱形表示,联系名放在菱形内部。
作为例子,我们来看一下大学数据库中包括教师和系以及它们之间的关联的部分。对应的ER图如图13所示。ER图表示出有instructor和department这两个实体集,它们具有先前已经列出的一些属性。这个图还指明了在教师和系之间的member联系。
图13ER图示例
除了实体和联系外,ER模型还描绘了数据库必须遵守的对其内容的某些约束。一个重要的约束是映射基数(mapping cardinality),它表示通过某个联系集能与一实体进行关联的实体数目。例如,如果一位教师只能属于一个系,ER模型就能表达出这种约束。
实体-联系模型在数据库设计中使用广泛,在第7章中将详细研究。
1.6.4规范化
设计关系数据库所用到的另外一种方法是通常被称为规范化的过程。它的目标是生成一个关系模式集合,使我们存储信息时没有不必要的冗余,同时又能很轻易地检索数据。这种方法是设计一种符合适当的范式(normal form)的模式,为确定一个关系模式是否符合想要的范式,我们需要额外的关于用数据库建模的现实世界中机构的信息。最常用的方法是使用函数依赖(functional dependency),我们将在8.4节讨论。
为了理解规范化的必要性,我们看一看在不好的数据库设计中会发生什么问题。一个不好的设计可能会包括如下不良特性:
信息重复。
缺乏表达某些信息的能力。
我们在对大学例子修改后的数据库设计中讨论这些问题。
假设不将表instructor和department分开,我们使用单个表faculty,它将两个表的数据合并在一起(见图14)。注意到在表faculty中有两行包含重复的关于历史系的信息,具体地说,是系所在的大楼和经费预算。这种修改后的设计中出现了我们不想要的信息重复。信息重复浪费存储空间,而且使得更新数据库变得复杂。假设我们想将历史系的经费预算从50 000美元改成46 800美元,这个修改必须在两行中都体现出来;这与原来的设计不同,原来只需要修改一行就可以了。因此,改变后的设计中更新操作的代价比原来的设计中大了。当我们在改变设计的数据库中进行更新时,我们必须保证与历史系有关的每个元组都被更新,否则我们的数据库将会显示出历史系有两个不同的经费预算数。
图14faculty 表
现在我们再看一看缺乏表达某些信息的能力的问题。假设我们在大学里建立一个新的系。在上述改变了的数据库设计中,我们不能直接表示关于一个系的信息(dept_name, building, budget),除非那个系在大学里至少有一位教员。这是因为faculty表中的行需要ID、name和 salary的值。这意味着我们不能记录新建立的系的信息,直到这个新的系聘用了第一位教员。
这个问题的一个解决办法是引入空值(null)。空值表示这个值不存在(或者未知),未知值可能是缺失(该值确实存在,但我们没有得到它)或不知道(我们不知道该值是否存在)。正如我们后面要看到的那样,空值很难处理,所以最好不要用它。如果我们不愿意处理空值,我们可以仅当一个系有至少一位教员时才为它建立系的信息项。而且,当系的最后一位教员离开时,我们必须删除系的信息。很明显,这种情况不是我们想要的,因为在我们原来的数据库设计里,不管一个系有没有教员,这个系的信息都是可以保存的,也不必使用空值。
规范化的详尽理论已经研究形成,它有助于形式化地定义什么样的数据库设计是不好的,以及如何得到我们想要的设计。第8章将讨论关系数据库设计,包括规范化。
1.7数据存储和查询
数据库系统划分为不同的模块,每个模块完成整个系统的一个功能。数据库系统的功能部件大致可分为存储管理器和查询处理部件。
存储管理非常重要,因为数据库常常需要大量存储空间。企业的大型数据库的大小达到数百个gigabyte,甚至达到terabyte。一个gigabyte大约等于1000个(实际上是1024个)megabyte(十亿字节),一个terabyte等于一百万个megabyte(一万亿个字节)。由于计算机主存不可能存储这么多信息,所以信息被存储在磁盘上。需要时数据在主存和磁盘间移动。由于相对于中央处理器的速度来说数据出入磁盘的速度很慢,因此数据库系统对数据的组织必须满足使磁盘和主存之间数据的移动最小化。
查询处理也非常重要,因为它帮助数据库系统简化和方便了数据的访问。查询处理器使得数据库用户能够获得很高的性能,同时可以在视图的层次上工作,不必承受了解系统实现的物理层次细节的负担。将在逻辑层编写的更新和查询转变成物理层的高效操作序列,这是数据库系统的任务。
1.7.1存储管理器
存储管理器是数据库系统中负责在数据库中存储的低层数据与应用程序以及向系统提交的查询之间提供接口的部件。存储管理器负责与文件管理器进行交互。原始数据通过操作系统提供的文件系统存储在磁盘上。存储管理器将各种DML语句翻译为底层文件系统命令。因此,存储管理器负责数据库中数据的存储、检索和更新。
存储管理部件包括:
权限及完整性管理器(authorization and integrity manager),它检测是否满足完整性约束,并检查试图访问数据的用户的权限。
事务管理器(transaction manager),它保证即使发生了故障,数据库也保持在一致的(正确的)状态,并保证并发事务的执行不发生冲突。
文件管理器(file manager),它管理磁盘存储空间的分配,管理用于表示磁盘上所存储信息的数据结构。
缓冲区管理器(buffer manager),它负责将数据从磁盘上取到内存中来,并决定哪些数据应被缓冲存储在内存中。缓冲区管理器是数据库系统中的一个关键部分,因为它使数据库可以处理比内存更大的数据。
存储管理器实现了几种数据结构,作为系统物理实现的一部分:
数据文件(data files),存储数据库自身。
数据字典(data dictionary),存储关于数据库结构的元数据,尤其是数据库模式。
索引(index),提供对数据项的快速访问。和书中的索引一样,数据库索引提供了指向包含特定值的数据的指针。例如,我们可以运用索引找到具有特定的ID的instructor记录,或者具有特定的name的所有instructor记录。散列是另外一种索引方式,在某些情况下速度更快,但不是在所有情况下都这样。
我们在第10章讨论存储介质、文件结构和缓冲区管理,以及通过索引和散列高效访问数据的方法。
1.7.2查询处理器
查询处理器组件包括:
DDL解释器(DDL interpreter),它解释DDL语句并将这些定义记录在数据字典中。
DML编译器(DML compiler),将查询语言中的DML语句翻译为一个执行方案,包括一系列查询执行引擎能理解的低级指令。
一个查询通常可被翻译成多种等价的具有相同结果的执行方案的一种。DML编译器还进行查询优化(query optimization),也就是从几种选择中选出代价最小的一种。
查询执行引擎(query evaluation engine),执行由DML编译器产生的低级指令。
第11章将介绍查询执行,以及查询优化器选择合适的执行策略的方法。
1.8事务管理
通常,对数据库的几个操作合起来形成一个逻辑单元。如1.2节所示的例子是一个资金转账,其中一个系(A系)的账户进行取出操作,而另一个系(B系)的账户进行存入操作。显然,这两个操作必须保证要么都发生要么都不发生。也就是说,资金转账必须完成或根本不发生。这种要么完成要么不发生的要求称为原子性(atomicity)。除此以外,资金转账还必须保持数据库的一致性。也就是说,A和B的余额之和应该是保持不变的。这种正确性的要求称作一致性(consistency)。最后,当资金转账成功结束后,即使发生系统故障,账户A和账户B的余额也应该保持转账成功结束后的新值。这种保持的要求称作持久性(durability)。
事务(transaction)是数据库应用中完成单一逻辑功能的操作集合。每一个事务是一个既具原子性又具一致性的单元。因此,我们要求事务不违反任何的数据库一致性约束,也就是说,如果事务启动时数据库是一致的,那么当这个事务成功结束时数据库也应该是一致的。然而,在事务执行过程中,必要时允许暂时的不一致,因为无论是A取出的操作在前还是B存入的操作在前,这两个操作都必然有一个先后次序。这种暂时的不一致虽然是必需的,但在故障发生时,很可能导致问题的产生。
适当地定义各个事务是程序员的职责,事务的定义应使之能保持数据库的一致性。例如,资金从A系的账户转到B系的账户这个事务可以被定义为由两个单独的程序组成:一个对账户A执行取出操作,另一个对账户B执行存入操作。这两个程序的依次执行可以保持一致性。但是,这两个程序自身都不是把数据库从一个一致的状态转入一个新的一致的状态,因此它们都不是事务。
原子性和持久性的保证是数据库系统自身的职责,确切地说,是恢复管理器(recovery manager)的职责。在没有故障发生的情况下,所有事务均成功完成,这时要保证原子性很容易。但是,由于各种各样的故障,事务并不总能成功执行完毕。为了保证原子性,失败的事务必须对数据库状态不产生任何影响。因此,数据库必须被恢复到该失败事务开始执行以前的状态。这种情况下数据库系统必须进行故障恢复(failure recovery),即检测系统故障并将数据库恢复到故障发生以前的状态。
最后,当多个事务同时对数据库进行更新时,即使每个单独的事务都是正确的,数据的一致性也可能被破坏。并发控制管理器(concurrencycontrol manager)控制并发事务间的相互影响,保证数据库一致性。事务管理器(transaction manager)包括并发控制管理器和恢复管理器。
第12章介绍事务处理的基本概念,以及并发事务的管理和故障恢复。
事务的概念已经广泛应用在数据库系统和应用当中。虽然最初是在金融应用中使用事务,现在事务已经使用在电信业的实时应用中,以及长时间的活动如产品设计和工作流管理中。
1.9数据库体系结构
现在我们可以给出一个数据库系统各个部分以及它们之间联系的图了(见图15)。
图15系统体系结构
数据库系统的体系结构很大程度上取决于数据库系统所运行的计算机系统。数据库系统可以是集中式的、客户/服务器式的(一台服务器为多个客户机执行任务);也可以针对并行计算机体系结构设计数据库系统;分布式数据库包含地理上分离的多台计算机。
1.9.1客户/服务器系统
今天数据库系统的大多数用户并不直接面对数据库系统,而是通过网络与其相连。因此我们可区分远程数据库用户工作用的客户机(client)和运行数据库系统的服务器(server)。
数据库应用通常可分为两或三个部分,如图16所示。在一个两层体系结构(twotier architecture)中,应用程序驻留在客户机上,通过查询语言表达式来调用服务器上的数据库系统功能。像ODBC和JDBC这样的应用程序接口标准被用于进行客户端和服务器的交互。
图16两层和三层体系结构
而在一个三层体系结构(threetier architecture)中,客户机只作为一个前端并且不包含任何直接的数据库调用。客户端通常通过一个表单界面与应用服务器(application server)进行通信。而应用服务器与数据库系统通信以访问数据。应用程序的业务逻辑(business logic),也就是说在何种条件下做出何种反应,被嵌入到应用服务器中,而不是分布在多个客户机上。三层结构的应用更适合大型应用和互联网上的应用。
1.9.2并行数据库系统
并行系统通过并行地使用多个处理器和磁盘来提高处理速度和I/O速度。并行计算机正变得越来越普及,相应地使得并行数据库系统的研究变得更加重要。有些应用需要查询非常大型的数据库(TB数量级,即1012字节),有些应用需要在每秒钟里处理很大数量的事务(每秒数千个事务),这些应用的需求推动了并行数据库系统的发展。集中式数据库系统和客户-服务器数据库系统的能力不够强大,不足以处理这样的应用。
在并行处理中,许多操作是同时执行的,而不是串行处理的(即按顺序执行各个计算步骤)。粗粒度(coarsegrain)并行机由少量能力强大的处理器组成;而
大规模并行(massive parallel)机或细粒度并行(finegrain parallel)机使用数千个更小的处理器。当今所有的高端计算机都提供了一定程度的粗粒度并行性,它们至少具有2个或4个处理器。大规模并行计算机与粗粒度并行机的区别在于它支持的并行程度要高得多。市场上可以买到具有数百个处理器和磁盘的并行计算机。
对数据库系统性能的度量有两种主要方式。(1)吞吐量(throughput):在给定时间段内所能完成任务的数量。(2)响应时间(response time):单个任务从提交到完成所需的时间。对于处理大量小事务的系统,通过并行地处理多个事务可以提高它的吞吐量。对于处理大事务的系统,通过并行地执行每个事务中的子任务可以缩短它的响应时间,同时提高它的吞吐量。
1.9.3分布式数据库系统
在分布式数据库系统(distributed database system)中,数据库存储在几台计算机中。分布式系统中的计算机之间通过诸如高速私有网络或因特网那样的通信媒介相互通信。它们不共享主存储器或磁盘。分布式系统中的计算机在规模和功能上是可变的,小到工作站,大到大型机系统。
对于分布式系统中的计算机有多种不同的称呼,例如站点(site)或结点(node)。分布式系统的通用结构如图17所示。
图17分布式系统
建立分布式数据库系统有几个原因,包括数据共享、自治性和可用性。
数据共享(sharing data)。建立分布式数据库系统的主要优点是,它提供了一个环境使一个站点上的用户可以访问存放在其他站点上的数据。例如,在一个分布式大学系统中,每个校区存储与该校区相关的数据。一个校区的用户可以访问另一个校区的数据。如果没有这种能力,那么要把学生记录从一个校区传送到另一个校区,就需要借助于与现有系统相互关联的外部机制。
自治性(autonomy)。通过数据分布的方式来共享数据的主要优点在于,每个站点可以对本地存储的数据保持一定程度的控制。在集中式系统中,中心站点的数据库管理员对数据库进行控制。在分布式系统中,有一个全局数据库管理员负责整个系统。有一部分职责被委派给每个站点的本地数据库管理员。每个管理员可以有不同程度的局部自治(local autonomy),其程度的不同依赖于分布式数据库系统的设计。可以进行本地自治通常是分布式数据库的一个主要优势。
可用性(availability)。在分布式系统中,如果一个站点发生故障,其他站点可能还能继续运行。特别地,如果数据项在几个站点上进行了复制(replicate),需要某个特定数据项的事务可以在这几个站点中的任何一个上找到该数据项。于是,一个站点的故障不一定意味着整个系统停止运转。
1.10数据挖掘与信息检索
数据挖掘(data mining)这个术语指半自动地分析大型数据库并从中找出有用的模式的过程。和人工智能中的知识发现(也称为机器学习(machine learning))或者统计分析一样,数据挖掘试图从数据中寻找规则或模式。但是,数据挖掘和机器学习、统计分析不一样的地方在于它处理大量的主要存储在磁盘上的数据。也就是说,数据挖掘就是在数据库中发现知识。
从数据库中发现的某些类型的知识可以用一套规则(rule)表示。下面是一条规则的例子,非形式化地描述为:“年收入高于50 000美元的年轻女性是最可能购买小型运动车的人群”。当然这条规则并不是永远正确的,但它有一定的“支持度”和“置信度”。其他类型的知识表达方式有联系不同变量的方程式,或者通过其他机制根据某些已知的变量来预测输出。
还有很多其他类型的有用模式以及发现不同模式的技术。在第13章我们将研究一些模式的例子以及如何自动地从数据库中得出这些模式。
通常在数据挖掘中还需要人参与,包括数据预处理使数据变为适合算法的格式,在已发现模式的后处理中找到新奇的有用模式。给定一个数据库,可能有不止一种类型的模式,需要人工交互挑选有用类型的模式。由于这个原因,现实中的数据挖掘是一个半自动的过程。但是,在我们的描述中,主要介绍挖掘的自动处理过程的部分。
商业上已经开始利用蓬勃发展的联机数据来支持对于业务活动的更好的决策,例如储备哪些物品,如何更好地锁定目标客户以提高销售额。但是,它们的许多查询都相当复杂,有些类型的信息甚至使用SQL都不能抽取出来。
目前有几种技术和工具可用于帮助做决策支持。一些数据分析的工具让分析人员能够从不同的角度观察数据。其他的分析工具提前计算出大量数据的汇总信息,以更快响应查询。现在的SQL标准也增加了支持数据分析的成分。
大型企业有各种不同的可用于业务决策的数据来源。要在这些各种各样的数据上高效地执行查询,企业建立了数据仓库(data warehouse)。数据仓库从多个来源收集数据,建立统一的模式,驻留在单个节点上。于是,就为用户提供了单个统一的数据界面。
文本数据也爆炸式增长。文本数据是非结构化的,与关系数据库中严格的结构化数据不同。查询非结构化的文本数据被称为信息检索(information retrieval)。信息检索系统和数据库系统很大程度上是相同的——特别是基于辅助存储器的数据存储和检索。但是信息系统领域与数据库系统所强调的重点是不同的,信息系统重点强调基于关键词的查询,文档与查询的相似度,以及文档的分析、分类和索引。第13章我们将讨论决策支持,包括联机分析处理、数据挖掘、数据仓库和信息检索。
1.11特种数据库
数据库系统的一些应用领域受到关系数据模型的限制。其结果是,研究人员开发了几种数据模型来处理这些领域的应用,包括基于对象的数据模型和半结构化数据模型。
1.11.1基于对象的数据模型
面向对象程序设计已经成为占统治地位的软件开发方法学。这导致面向对象数据模型(objectbased data model)的发展,面向对象模型可以看作ER模型的扩展,增加了封装、方法(函数)和对象标识。继承、对象标识和信息封装(信息隐蔽),以及对外提供方法作为访问对象的接口,这些是面向对象程序设计的关键概念,现在在数据建模中也找到了应用。面向对象数据模型还支持丰富的类型系统,包括结构和集合类型。在20世纪80年代,开发了好几个基于面向对象数据模型的数据库系统。
现在主要的数据库厂商都支持对象-关系数据模型(objectrelational data model),这是一个将面向对象数据模型和关系数据模型的特点结合在一起的数据模型。它扩展了传统的关系模型,增加了新的特征如结构和集合类型,以及面向对象特性。第14章介绍对象-关系数据模型。
1.11.2半结构化数据模型
半结构化数据模型允许那些相同类型的数据项有不同的属性集的数据说明。这和早先提到的数据模型形成了对比:在那些数据模型中所有某种特定类型的数据项必须有相同的属性集。
XML语言设计的初衷是为文本文档增加标签信息,但由于它在数据交换中的应用而变得日益重要。XML提供了表达含有嵌套结构的数据的方法,能够灵活组织数据结构,这对于一些非传统数据来说非常重要。第15章简单介绍XML语言、XML格式的数据的各种查询表示方法,以及不同XML数据格式之间的转换。
1.12数据库用户和管理员
数据库系统的一个主要目标是从数据库中检索信息和往数据库中存储新信息。使用数据库的人员可分为数据库用户和数据库管理员。
1.12.1数据库用户和用户界面
根据所期望的与系统交互方式的不同,数据库系统的用户可以分为四种不同类型。系统为不同类型的用户设计了不同类型的用户界面。
无经验的用户(nave user)是默认经验的用户,他们通过激活事先已经写好的应用程序同系统进行交互。例如,大学的一位职员需要往A 系中添加一位新的教师时,激活一个叫做new_hire的程序。该程序要求这位职员输入新教师的名字、她的新ID、系的名字(即A)以及她的工资额。
此类用户的典型用户界面是表格界面,用户只需填写表格的相应项就可以了。无经验的用户也可以很简单地阅读数据库产生的报表。
作为另外一个例子,我们考虑一个学生,他在课程注册的过程中想通过Web界面来注册一门课程。应用程序首先验证该用户的身份,然后允许她去访问一个表格,她可以在表格中填入想填的信息。表格信息被送回给服务器上的Web应用程序,然后应用程序确定该课程是否还有空额(通过从数据库中检索信息),如果有,就把这位学生的信息添加到数据库中的该课程花名册中。
应用程序员(application programmer)是编写应用程序的计算机专业人员。有很多工具可以供应用程序员选择来开发用户界面。快速应用开发(Rapid Application Development, RAD)工具是使应用程序员能够尽量少编写程序就可以构造出表格和报表的工具。
老练的用户(sophisticated user)不通过编写程序来同系统交互,而是用数据库查询语言或数据分析软件这样的工具来表达他们的要求。分析员通过提交查询来研究数据库中的数据,所以属于这一类用户。
专门的用户(specialized user)是编写专门的、不适合于传统数据处理框架的数据库应用的富有经验的用户。这样的应用包括:计算机辅助设计系统、知识库和专家系统、存储复杂结构数据(如图形数据和声音数据)的系统,以及环境建模系统。在第14章中我们将要讨论几个这样的应用。
1.12.2数据库管理员
使用DBS的一个主要原因是可以对数据和访问这些数据的程序进行集中控制。对系统进行集中控制的人称作数据库管理员(DataBase Administrator, DBA)。DBA的作用包括:
模式定义(schema definition)。DBA通过用DDL书写的一系列定义来创建最初的数据库模式。
存储结构及存取方法定义(storage structure and accessmethod definition)。
模式及物理组织的修改(schema and physicalorganization modification)。由数据库管理员(DBA)对模式和物理组织进行修改,以反映机构的需求变化,或为提高性能选择不同的物理组织。
数据访问授权(granting of authorization for data access)。通过授予不同类型的权限,数据库管理员可以规定不同的用户各自可以访问的数据库的部分。授权信息保存在一个特殊的系统结构中,一旦系统中有访问数据的要求,数据库系统就去查阅这些信息。
日常维护(routine maintenance)。数据库管理员的日常维护活动有:
定期备份数据库,或者在磁带上或者在远程服务器上,以防止像洪水之类的灾难发生时数据丢失。
确保正常运转时所需的空余磁盘空间,并且在需要时升级磁盘空间。
监视数据库的运行,并确保数据库的性能不因一些用户提交了花费时间较多的任务就下降很多。
1.13数据库系统的历史
从商业计算机的出现开始,数据处理就一直推动着计算机的发展。事实上,数据处理自动化早于计算机的出现。Herman Hollerith发明的穿孔卡片,早在20世纪初就用来记录美国的人口普查数据,并且用机械系统来处理这些卡片和列出结果。穿孔卡片后来被广泛用作将数据输入计算机的一种手段。
数据存储和处理技术发展的年表如下:
20世纪50年代和20世纪60年代初:磁带被用于数据存储。诸如工资单这样的数据处理已经自动化了,数据存储在磁带上。数据处理包括从一个或多个磁带上读取数据,并将数据写回到新的磁带上。数据也可以由一叠穿孔卡片输入,而输出到打印机上。例如,工资增长的处理是通过将增长表示到穿孔卡片上,在读入一叠穿孔卡片时同步地读入保存主要工资细节的磁带。记录必须有相同的排列顺序。工资的增加额将被加入到从主磁带读出的工资中,并被写到新的磁带上,新磁带将成为新的主磁带。
磁带(和卡片组)都只能顺序读取,数据规模可以比内存大得多,因此,数据处理程序被迫以一种特定的顺序来对数据进行处理,读取和合并来自磁带和卡片组的数据。
20世纪60年代末和20世纪70年代:20世纪60年代末硬盘的广泛使用极大地改变了数据处理的情况,因为硬盘允许直接对数据进行访问。数据在磁盘上的位置是无关紧要的,因为磁盘上的任何位置都可在几十毫秒内访问到。数据由此摆脱了顺序访问的限制。有了磁盘,我们就可以创建网状和层次的数据库,它可以将表和树这样的数据结构保存在磁盘上。程序员可以构建和操作这些数据结构。
由Codd[1970]撰写的一篇具有里程碑意义的论文定义了关系模型和在关系模型中查询数据的非过程化方法,由此关系型数据库诞生了。关系模型的简单性和能够对程序员屏蔽所有实现细节的能力具有真正的诱惑力。随后,Codd因其所做的工作获得了声望很高的ACM图灵奖。
20世纪80年代:尽管关系模型在学术上很受重视,但是最初并没有实际的应用,这是因为它被认为性能不好;关系型数据库在性能上还不能和当时已有的网状和层次数据库相提并论。这种情况直到System R的出现才得以改变,这是IBM研究院的一个突破性项目,它开发了能构造高效的关系型数据库系统的技术。Astrahan等[1976]和Chamberlin等[1981]给出了关于System R的很好的综述。完整功能的System R原型导致了IBM的第一个关系型数据库产品SQL/DS的出现。与此同时,加州大学伯克利分校开发了Ingres系统。它后来发展成具有相同名字的商品化关系数据库系统。最初的商品化关系型数据库系统,如IBM DB2、Oracle、Ingres和DEC Rdb,在推动高效处理声明性查询的技术上起到了主要的作用。到了20世纪80年代初期,关系型数据库已经可以在性能上与网状和层次型数据库进行竞争了。关系型数据库是如此简单易用,以至于最后它完全取代了网状/层次型数据库,因为程序员在使用后者时,必须处理许多底层的实现细节,并且不得不将他们要做的查询任务编码成过程化的形式。更重要的,他们在设计应用程序时还要时时考虑效率问题,而这需要付出很大的努力。相反,在关系型数据库中,几乎所有的底层工作都由数据库自动来完成,使得程序员可以只考虑逻辑层的工作。自从在20世纪80年代取得了统治地位以来,关系模型在数据模型中一直独占鳌头。
在20世纪80年代人们还对并行和分布式数据库进行了很多研究,同样在面向对象数据库方面也有初步的工作。
20世纪90年代初:SQL语言主要是为决策支持应用设计的,这类应用是查询密集的;而20世纪80年代数据库的支柱是事务处理应用,它们是更新密集的。决策支持和查询再度成为数据库的一个主要应用领域。分析大量数据的工具有了很大的发展。
在这个时期许多数据库厂商推出了并行数据库产品。数据库厂商还开始在他们的数据库中加入对象-关系的支持。
20世纪90年代:最重大的事件就是互联网的爆炸式发展。数据库比以前有了更加广泛的应用。现在数据库系统必须支持很高的事务处理速度,而且还要有很高的可靠性和24×7的可用性(一天24小时,一周7天都可用,也就是没有进行维护的停机时间)。数据库系统还必须支持对数据的Web接口。
21世纪第一个十年:21世纪的最初五年中,我们看到了XML的兴起以及与之相关联的XQuery查询语言成为了新的数据库技术。虽然XML广泛应用于数据交换和一些复杂数据类型的存储,但关系数据库仍然构成大多数大型数据库应用系统的核心。在这个时期,我们还见证了“自主计算/自动管理”技术的成长,其目的是减少系统管理开销;我们还看到了开源数据库系统应用的显著增长,特别是PostgreSQL和MySQL。
在21世纪第一个十年的后几年中,用于数据分析的专门的数据库有很大增长,特别是将一个表的每一个列高效地存储为一个单独的数组的列存储,以及为非常大的数据集的分析而设计的高度并行的数据库系统。有几个新颖的分布式数据存储系统被构建出来,以应对非常大的Web节点如Amazon、Facebook、Google、Microsoft和Yahoo!的数据管理需求,并且这些系统中的某些现在可以作为Web服务提供给应用开发人员使用。在管理和分析流数据如股票市场报价数据或计算机网络监测数据方面也有重要的工作。数据挖掘技术现在被广泛部署应用,应用实例包括基于Web的产品推荐系统和Web页面上的相关广告自动布放。
1.14总结
数据库系统(DataBase System, DBS)由相互关联的数据集合以及一组用于访问这些数据的程序组成。数据描述某特定的企业。
DBS的主要目标是为人们提供方便、高效的环境来存储和检索数据。
如今数据库系统无所不在,很多人每天直接或间接地与数据库系统打交道。
数据库系统设计用来存储大量的信息。数据的管理既包括信息存储结构的定义,也包括提供处理信息的机制。另外数据库系统还必须提供所存储信息的安全性,以处理系统崩溃或者非授权访问企图,如果数据在多个用户之间共享,系统必须避免可能的异常结果。
数据库系统的一个主要目的是为用户提供数据的抽象视图,也就是说,系统隐藏数据存储和维护的细节。
数据库结构的基础是数据模型(data model):一个用于描述数据、数据之间的联系、数据语义和数据约束的概念工具的集合。
关系数据模型是最广泛使用的将数据存储到数据库中的模型。其他的数据模型有面向对象模型、对象-关系模型和半结构化数据模型。
数据操纵语言(DataManipulation Language, DML)是使得用户可以访问和操纵数据的语言。当今广泛使用的是非过程化的DML,它只需要用户指明需要什么数据,而不需指明如何获得这些数据。
数据定义语言(DataDefinition Language, DDL)是说明数据库模式和数据的其他特性的语言。
数据库设计主要包括数据库模式的设计。实体-联系(ER)数据模型是广泛用于数据库设计的数据模型,它提供了一种方便的图形化的方式来观察数据、联系和约束。
数据库系统由几个子系统构成:
存储管理器(storage manager)子系统在数据库中存储的低层数据与应用程序和向系统提交的查询之间提供接口。
查询处理器(query processor)子系统编译和执行DDL和DML语句。
事务管理(transaction management)负责保证不管是否有故障发生,数据库都要处于一致的(正确的)状态。事务管理器还保证并发事务的执行互不冲突。
数据库系统的体系结构受支持其运行的计算机系统的影响很大。数据库系统可以是集中式的或者客户/服务器方式的,即一个服务器机器为多个客户机执行工作。数据库系统还可以设计成具有能充分利用并行计算机系统结构的能力。分布式数据库跨越多个地理上分布的互相分离的计算机。
典型地,数据库应用可被分为运行在客户机上的前端和运行在后端的部分。在两层的体系结构中,前端直接和后端运行的数据库进行通信。在三层结构中,后端又被分为应用服务器和数据库服务器。
知识发现技术试图自动地从数据中发现统计规律和模式。数据挖掘(data mining)领域将人工智能和统计分析研究人员创造的知识发现技术,与使得知识发现技术能够在极大的数据库上高效实现的技术结合起来。
有4种不同类型的数据库用户,按用户期望与数据库进行交互的不同方式来区分他们。为不同类的用户设计了不同的用户界面。
术语回顾
数据库系统(DBS)
数据库系统应用
文件处理系统
数据不一致性
一致性约束
数据抽象
实例
模式
物理模式
逻辑模式
物理数据独立性
数据模型
实体-联系模型
关系数据模型
基于对象的数据模型
半结构化数据模型
数据库语言
数据定义语言
数据操纵语言
查询语言
元数据
应用程序
规范化
数据字典
存储管理器
查询处理器
事务
原子性
故障恢复
并发控制
两层和三层数据库体系结构
数据挖掘
数据库管理员(DBA)
实践习题
1.1这一章讲述了数据库系统的几个主要的优点。它有哪两个不足之处?
1.2列出Java或C++之类的语言中的类型说明系统与数据库系统中使用的数据定义语言的5个不同之处。
1.3列出为一个企业建立数据库的六个主要步骤。
1.4除1.6.2节中已经列出的之外,请列出大学要维护的至少3种不同类型的信息。
1.5假设你想要建立一个类似于YouTube的视频节点。考虑1.2节中列出的将数据保存在文件系统中的各个缺点,讨论每一个缺点与存储实际的视频数据和关于视频的元数据(诸如标题、上传它的用户、标签、观看它的用户)的关联。
1.6在Web查找中使用的关键字查询与数据库查询很不一样。请列出这两者之间在查询表达方式和查询结果是什么方面的主要差异。
习题
1.7列出四个你使用过的很可能使用了数据库来存储持久数据的应用。
1.8列出文件处理系统和DBS的四个主要区别。
1.9解释物理数据独立性的概念,以及它在数据库系统中的重要性。
1.10列出数据库管理系统的五个职责。对每个职责,说明当它不能被履行时会产生什么样的问题。
1.11请给出至少两种理由说明为什么数据库系统使用声明性查询语言,如SQL,而不是只提供C或者C++的函数库来执行数据操作。
1.12解释用图14中的表来设计会导致哪些问题。
1.13数据库管理员的五种主要作用是什么?
1.14解释两层和三层体系结构之间的区别。对Web应用来说哪一种更合适?为什么?
1.15描述可能被用于存储一个社会网络系统如Facebook中的信息的至少3个表。
工具
如今已有大量的商业数据库系统投入使用,主要的有:IBM DB2(www.ibm.com/software/data/db2)、Oracle(www.oracle.com)、Microsoft SQL Server (www.microsoft.com/sql)、Sybase(www.sybase.com) 和IBM Informix(www.ibm.com/software/data/informix)。其中一些对个人或者非商业使用或开发是免费的,但是对实际的部署是不免费的。
也有不少免费/公开的数据库系统,使用很广泛的有MySQL(www.mysql.com)和PostgreSQL(www. postgresql.org)。
在本书的主页www.dbbook.com上可以获得更多的厂商网址的链接和其他信息。
文献注解
我们在下面列出了关于数据库的通用书籍、研究论文集和Web节点。后续各章提供了本章略述的每个主题的资料参考。
Codd[1970]的具有里程碑意义的论文引入了关系模型。
关于数据库系统的教科书有Abiteboul等[1995]、ONeil和 ONeil[2000]、Ramakrishnan 和 Gehrke [2002]、 Date [2003]、 Kifer 等[2005]、Elmasri 和 Navathe [2006], 以及 GarciaMolina 等 [2008]。涵盖事务处理的教科书有Bernstein和Newcomer[1997]以及Gray和Reuter[1993]。有一本书中包含了关于数据库管理的研究论文的汇集,这本书是Hellerstein和Stonebraker [2005]。
Silberschatz等[1990]、Silberschatz等[1996]、Bernstein等[1998],以及Abiteboul等[2003]给出了关于数据库管理已有成果和未来研究挑战的综合评述。ACM的数据管理兴趣组的主页(www.acm.org/sigmod)提供了关于数据库研究的大量信息。数据库厂商的网址(参看上面的工具部分)提供了他们各自产品的细节。
第一部分
Part 1
关系数据库
数据模型是描述数据、数据联系、数据语义以及一致性约束的概念工具的集合。在这一部分中,我们集中学习关系模型。
关系模型利用表的集合来表示数据和数据间的联系,第2章将专门介绍关系模型。其概念上的简单性使得它被广泛采纳;今天大量的数据库产品都是基于关系模型的。关系模型在逻辑层和视图层描述数据,使用户不必关注数据存储的底层细节。在后面第二部分的第7章中讨论的实体-联系模型是一种更高层的数据模型,被广泛用于数据库设计。
为了让用户可以使用关系数据库中的数据,我们需要解决几个问题。最重要的问题是用户如何说明对数据的检索和更新请求,为此已经开发了好几种查询语言。第二个问题也很重要,就是数据完整性和数据保护。无论用户有意或无意地破坏数据,数据库都要保护数据,使其免遭破坏。
第3章、第4章和第5章讲述当今应用最普遍的一种查询语言——SQL语言。第3章和第4章介绍SQL及其中等程度的应用知识。第4章还将介绍通过数据库施加完整性约束以及授权机制,用来控制用户发出的哪些访问和更新操作是可以执行的。第5章介绍更为深入的主题,包括如何在编程语言中使用SQL,如何利用SQL进行数据分析。
第6章介绍三种形式的查询语言:关系代数、元组关系演算和域关系演算,它们是基于数学逻辑的声明式查询语言。这些形式语言构成了SQL,以及另外两种用户友好的语言QBE和Datalog的基础。
第2章
Database System Concepts,6E
关系模型介绍
在商用数据处理应用中,关系模型已经成为当今主要的数据模型。之所以占据主要位置,是因为和早期的数据模型如网络模型或层次模型相比,关系模型以其简易性简化了编程者的工作。
本章我们先学习关系模型的基础知识。关系数据库具有坚实的理论基础,我们在第6章学习关系数据库理论中与查询相关的部分,从第7章到第8章我们将考察其中用于关系数据库模式设计的部分,在第11章我们将讨论高效处理查询的理论。
2.1关系数据库的结构
关系数据库由表(table)的集合构成,每个表有唯一的名字。例如,图21中的instructor表记录了有关教师的信息,它有四个列首:ID、name、dept_name和salary。该表中每一行记录了一位教师的信息,包括该教师的ID、name、dept_name以及salary。类似地,图22中的course表存放了关于课程的信息,包括每门课程的course_id、title、dept_name和credits。注意,每位教师通过ID列的取值进行标识,而每门课程则通过course_id列的取值来标识。
图21instructor关系
图23给出的第三个表是prereq,它存放了每门课程的先修课程信息。该表具有course_id和prereq_id两列,每一行由一个课程对组成,这个课程对表示了第二门课程是第一门课程的先修课。
由此,prereq表中的每行表示了两门课程之间的联系:其中一门课程是另一门课程的先修课。作为另一个例子,我们考察instructor表,表中的行可被认为是代表了从一个特定的ID到相应的name、dept_name和salary值之间的联系。
一般说来,表中一行代表了一组值之间的一种联系。由于一个表就是这种联系的一个集合,表这个概念和数学上的关系这个概念是密切相关的,这也正是关系数据模型名称的由来。在数学术语中,元组(tuple)只是一组值的序列(或列表)。在n个值之间的一种联系可以在数学上用关于这些值的一个n元组(ntuple)来表示,换言之,n元组就是一个有n个值的元组,它对应于表中的一行。
图22course关系
这样,在关系模型的术语中,关系(relation)用来指代表,而元组(tuple)用来指代行。类似地,属性(attribute)指代的是表中的列。
图23prereq关系
考察图21,我们可以看出instructor关系有四个属性:ID、name、dept_name和salary。
我们用关系实例(relation instance)这个术语来表示一个关系的特定实例,也就是所包含的一组特定的行。图21所示的instructor的实例有12个元组,对应于12个教师。
本章我们将使用多个不同的关系来说明作为关系数据模型基础的各种概念。这些关系代表一个大学的一部分。它们并没有包含真实的大学数据库中的所有数据,这主要是为了简化表示。在第7章和第8章里我们将详细讨论如何判断适当的关系结构的相关准则。
由于关系是元组集合,所以元组在关系中出现的顺序是无关紧要的。因此,无论关系中的元组是像图21那样被排序后列出,还是像图24那样无序的,都没有关系;在上述两图中的关系是一样的,因为它们具有同样的元组集合。为便于说明,当我们在显示关系时,大多数情况下都按其第一个属性排序。
图24instructor关系的无序显示
对于关系的每个属性,都存在一个允许取值的集合,称为该属性的域(domain)。这样instructor关系的salary属性的域就是所有可能的工资值的集合,而name属性的域是所有可能的教师名字的集合。
我们要求对所有关系r而言,r的所有属性的域都是原子的。如果域中元素被看作是不可再分的单元,则域是原子的(atomic)。例如,假设instructor表上有一个属性phone_number,它存放教师的一组联系电话号码。那么phone_number的域就不是原子的,因为其中的元素是一组电话号码,是可以被再分为单个电话号码这样的子成分的。
重要的问题不在于域本身是什么,而在于我们怎样在数据库中使用域中元素。现在假设phone_number属性存放单个电话号码。即便如此,如果我们把电话号码的属性值拆分成国家编号、地区编号以及本地号码,那么我们还是把它作为非原子值来对待。如果我们把每个电话号码作为不可再分的单元,那么phone_number属性才会有原子的域。
在本章以及第3章~第6章,我们假设所有属性的域都是原子的。在第14章中,我们将讨论对关系数据模型进行扩展以便允许非原子域。
空(null)值是一个特殊的值,表示值未知或不存在。如前所述,如果我们在关系instructor中包括属性phone_number,则可能某教师根本没有电话号码,或者电话号码未提供。这时我们就只能使用空值来强调该值未知或不存在。以后我们会看到,空值给数据库访问和更新带来很多困难,因此应尽量避免使用空值。我们先假设不存在空值,然后在3.6节中我们将描述空值对不同操作的影响。
2.2数据库模式
当我们谈论数据库时,我们必须区分数据库模式(database schema)和数据库实例(database instance),前者是数据库的逻辑设计,后者是给定时刻数据库中数据的一个快照。
关系的概念对应于程序设计语言中变量的概念,而关系模式(relation schema)的概念对应于程序设计语言中类型定义的概念。
一般说来,关系模式由属性序列及各属性对应域组成。等第3章讨论SQL语言时,我们才去关心每个属性的域的精确定义。
关系实例的概念对应于程序设计语言中变量的值的概念。给定变量的值可能随时间发生变化;类似地,当关系被更新时,关系实例的内容也随时间发生了变化。相反,关系的模式是不常变化的。
尽管知道关系模式和关系实例的区别非常重要,我们常常使用同一个名字,比如instructor,既指代模式,也指代实例。在需要的时候,我们会显示地指明模式或实例。例如“instructor模式”或“instructor关系的一个实例”。然而,在模式或实例的含义清楚的情况下,我们就简单地使用关系的名字。
图25department关系
考察图25中的department关系,该关系的模式是:
department (dept_name, building, budget)
请注意属性dept_name既出现在instructor模式中,又出现在department模式中。这样的重复并不是一种巧合。实际上,在关系模式中使用相同属性正是将不同关系的元组联系起来的一种方法。例如,假设我们希望找出在Watson大楼工作的所有教师的相关信息。我们首先在department关系中找出所有位于Watson的系的dept_name。接着,对每一个这样的系,我们在instructor关系中找出与dept_name对应的教师信息。
我们继续看大学数据库的例子。
大学里的每门课程可能要讲授多次,可以在不同学期授课,甚至可能在同一个学期授课。我们需要一个关系来描述每次课的授课情况或分段情况。该关系模式为:
section (course_id, sec_id, semester, year, building, room_number, time_slot_id)
图26给出了section关系的一个示例。
我们需要一个关系来描述教师和他们所讲授的课程段之间的联系。描述此联系的关系模式是:
teaches (ID, course_id, sec_id, semester, year)
图26section关系
图27teaches关系
图27给出了teaches关系的一个示例。
正如你可以料想的,在一个真正的大学数据库中还维护了更多的关系。除了我们已经列出的这些关系:instructor、department、course、section、prereq和teaches,在本书中我们还要使用下列关系:
student (ID, name, dept_name, tot_cred)
advisor (s_id, i_id)
takes (ID, course_id, sec_id, semester, year, grade)
classroom (building, room_number, capacity)
time_slot (time_slot_id, day, start_time, end_time)
2.3码
我们必须有一种能区分给定关系中的不同元组的方法。这用它们的属性来表明。也就是说,一个元组的属性值必须是能够唯一区分元组的。换句话说,一个关系中没有两个元组在所有属性上的取值都相同。
超码(superkey)是一个或多个属性的集合,这些属性的组合可以使我们在一个关系中唯一地标识一个元组。例如,instructor关系的ID属性足以将不同的教师元组区分开来,因此,ID是一个超码。另一方面,instructor的name属性却不是一个超码,因为几个教师可能同名。
形式化地描述,设R表示关系r模式中的属性集合。如果我们说R的一个子集K是r的一个超码,则限制了关系r中任意两个不同元组不会在K的所有属性上取值完全相等,即如果t1和t2在r中且t1≠t2,则t1.K≠t2.K。
超码中可能包含无关紧要的属性。例如,ID和name的组合是关系instructor的一个超码。如果K是一个超码,那么K的任意超集也是超码。我们通常只对这样的一些超码感兴趣,它们的任意真子集都不能成为超码。这样的最小超码称为候选码(candidate key)。
几个不同的属性集都可以做候选码的情况是存在的。假设name和dept_name的组合足以区分instructor关系的各个成员,那么{ID}和{name, dept_name}都是候选码。虽然属性ID和name一起能区分instructor元组,但它们的组合{ID, name }并不能成为候选码,因为单独的属性ID已是候选码。
我们用主码(primary key)这个术语来代表被数据库设计者选中的、主要用来在一个关系中区分不同元组的候选码。码(不论是主码、候选码或超码)是整个关系的一种性质,而不是单个元组的性质。关系中的任意两个不同的元组都不允许同时在码属性上具有相同的值。码的指定代表了被建模的事物在现实世界中的约束。
主码的选择必须慎重。正如我们所注意到的那样,人名显然是不足以作主码的,因为可能有多个人重名。在美国,人的社会保障号可以作候选码。而非美国居民通常不具有社会保障号,所以跨国企业必须设置他们自己的唯一标识符。另外也可以使用另一些属性的唯一组合作为码。
主码应该选择那些值从不或极少变化的属性。例如,一个人的地址就不应该作为主码的一部分,因为它很可能变化。另一方面,社会保障号却可以保证决不变化。企业产生的唯一标识符通常不变,除非两个企业合并了,这种情况下可能在两个公司中会使用相同的标识符,因此需要重新分配标识符以确保其唯一性。
习惯上把一个关系模式的主码属性列在其他属性前面;例如,department中的dept_name属性最先列出,因为它是主码。主码属性还加上了下划线。
一个关系模式(如r1)可能在它的属性中包括另一个关系模式(如r2)的主码。这个属性在r1上称作参照r2的外码(foreign key)。关系r1也称为外码依赖的参照关系(referencing relation),r2叫做外码的被参照关系(referenced relation)。例如,instructor中的dept_name属性在instructor上是外码,它参照department,因为dept_name是department的主码。在任意的数据库实例中,从instructor关系中任取一个元组,比如ta,在department关系中必定存在某个元组,比如tb,使得ta在dept_name属性上的取值与tb在主码dept_name上的取值相同。
现在考察section和teaches关系。如下需求是合理的:如果一门课程是分段授课的,那么它必须至少由一位教师来讲授;当然它可能由不止一位教师来讲授。为了施加这种约束,我们需要保证如果一个特定的(course_id, sec_id, semester, year)组合出现在section中,那么该组合也必须出现在teaches中。可是,这组值并不构成teaches的主码,因为不止一位教师可能讲授同一个这样的课程段。其结果是,我们不能声明从section到teaches的外码约束(尽管我们可以在相反的方向上声明从teaches到section的外码约束)。
从section到teaches的约束是参照完整性约束(referential integrity constraint)的一个例子。参照完整性约束要求在参照关系中任意元组在特定属性上的取值必然等于被参照关系中某个元组在特定属性上的取值。
2.4模式图
一个含有主码和外码依赖的数据库模式可以用模式图(schema diagram)来表示。图28展示了我们大学组织的模式图。每一个关系用一个矩形来表示,关系的名字显示在矩形上方,矩形内列出各属性。主码属性用下划线标注。外码依赖用从参照关系的外码属性到被参照关系的主码属性之间的箭头来表示。
图28大学数据库的模式图
除外码约束之外,模式图中没有显示表示出参照完整性约束。在后面第7章,我们将学习一种不同的、称作实体-联系图的图形化表示。实体-联系图有助于我们表示几种约束,包括通用的参照完整性约束。
很多数据库系统提供图形化用户界面设计工具来建立模式图。我们将在第7章详细讨论模式的图形化表示。
在后面的章节中我们使用大学作为例子。图29给出了我们在例子中使用的关系模式,其中主码属性被标上了下划线。正如我们将在第3章中看到的一样,这对应于在SQL的数据定义语言中定义关系的方法。
图29大学数据库模式
2.5关系查询语言
查询语言(query language)是用户用来从数据库中请求获取信息的语言。这些语言通常比标准的程序设计语言层次更高。查询语言可以分为过程化的和非过程化的。在过程化语言(procedural language)中,用户指导系统对数据库执行一系列操作以计算出所需结果。在非过程化语言(nonprocedural language)中,用户只需描述所需信息,而不用给出获取该信息的具体过程。
实际使用的查询语言既包含过程化方式的成分,又包含非过程化方式的成分。我们从第3章到第5章学习被广泛应用的查询语言SQL。
有一些“纯”查询语言:关系代数是过程化的,而元组关系演算和域关系演算是非过程化的。这些语言简洁且形式化,默认商用语言的“句法修饰”,但它们说明了从数据库中提取数据的基本技术。在第6章,我们详细研究关系代数和关系演算的两种形式,即元组关系演算和域关系演算。关系代数包括一个运算的集合,这些运算以一个或两个关系为输入,产生一个新的关系作为结果。关系演算使用谓词逻辑来定义所需的结果,但不需给出获取结果的特定代数过程。
2.6关系运算
所有的过程化关系查询语言都提供了一组运算,这些运算要么施加于单个关系上,要么施加于一对关系上。这些运算具有一个很好的,并且也是所需的性质:运算结果总是单个的关系。这个性质使得人们可以模块化的方式来组合几种这样的运算。特别是,由于关系查询的结果本身也是关系,所以关系运算可施加到查询结果上,正如施加到给定关系集上一样。
在不同语言中,特定的关系运算的表示是不同的,但都符合我们在本章所描述的通用结构。在第3章,我们将给出在SQL中表达关系运算的特殊方式。
最常用的关系运算是从单个关系(如instructor)中选出满足一些特定谓词(如salary> 85 000美元)的特殊元组。其结果是一个新关系,它是原始关系(instructor)的一个子集。例如,如果我们从图21的instructor关系中选择满足谓词“工资大于85 000美元”的元组,我们得到的结果如图210所示。
图210选择工资大于85 000美元的
instructor元组的查询结果
另一个常用的运算是从一个关系中选出特定的属性(列)。其结果是一个只包含那些被选择属性的新关系。例如,假设我们从图21的instructor关系中只希望列出教师的ID和工资,但不列出name和dept_name的值,那么其结果有ID和salary两个属性,如图211所示。结果中的每个元组都是从instructor关系中的某个元组导出的,不过只具有被选中的属性。
连接运算可以通过下述方式来结合两个关系:把分别来自两个关系的元组对合并成单个元组。
图211从instructor
关系中选取属性ID
和salary的查询结果
有几种不同的方式来对关系进行连接(正如我们将在第3章中看到的)。图212显示了一个连接来自instructor和department表中元组的例子,新元组给出了有关每个教师及其工作所在系的信息。此结果是通过把instructor关系中的每个元组和department关系中对应于教师所在系的元组合并形成的。
图212所示的连接被称作自然连接,在这种连接形式中,对于来自instructor关系的一个元组与department关系中的一个元组来说,如果它们在dept_name属性上的取值相同,那它们就是匹配的。所有这样匹配的元组对都会在连接结果中出现。通常说来,两个关系上的自然连接运算所匹配的元组在两个关系共有的所有属性上取值相同。
笛卡儿积运算从两个关系中合并元组,但不同于连接运算的是,其结果包含来自两个关系元组的所有对,无论它们的属性值是否匹配。
图212instructor关系和department关系的自然连接结果
因为关系是集合,所以我们可以在关系上施加标准的集合运算。并运算在两个“相似结构”的表(比如一个所有毕业生的表和一个所有大学生的表)上执行集合并。例如,我们可以得到一个系中所有学生的集合。另外的集合运算如交和集合差也都可以被执行。
正如我们此前讲到的,我们可以在查询结果上施加运算。例如,如果我们想找出工资超过85 000美元的那些教师的ID和salary,我们可以执行上述例子中的前两种运算。首先我们从instructor关系中选出salary值大于85 000美元的元组,然后从结果中选出ID和salary两个属性,结果关系如图213所示,由ID和salary构成。在此例中,我们可以任一次序来执行运算,但正如我们将看到的,并非在所有情况下均可如此。
图213选择工资大于
85 000美元的教师的ID
和salary属性的结果
有时候,查询结果中包含重复的元组。例如,如果我们从instructor关系中选出dept_name属性,就会有好几种重复的情况,其中包括“Comp.Sci.”,它出现了三次。一些关系语言严格遵守集合的数学定义,去除了重复。另一些考虑到从大的结果关系中去除重复需要大量相关的处理,就保留了重复。在后面这类情况中,关系并非是纯粹数学意义上的真正关系。
当然,数据库中的数据是随时间而改变的。关系可以随新元组的插入、已有元组的删除或更改元组在特定属性上的值而更新。整个关系可被删除,新的关系可被创建。
从第3章到第5章,我们将讨论如何使用SQL语言来表示关系的查询和更新。
关 系 代 数
关系代数定义了在关系上的一组运算,对应于作用在数字上的普通代数运算,如加法、减法或乘法。正如作用在数字上的代数运算以一个或多个数字作为输入,返回一个数字作为输出,关系代数运算通常以一个或两个关系作为输入,返回一个关系作为输出。
第6章将详细介绍关系代数,下面我们给出几个运算的概述:
符号(名字)使用示例
σ(选择)
(投影)
(自然连接)
×(笛卡儿积)
∪(并)
σsalary>=85 000(instructor)
返回输入关系中满足谓词的行
∏ID,salary(instructor)
对输入关系的所有行输出指定的属性。从输出中去除重复元组
instructordepartment
从两个输入关系中输出这样的元组对:它们在具有相同名字的所有属性上取值相同
instructor×department
从两个输入关系中输出所有的元组对(无论它们在共同属性上的取值是否相同)
∏name(instructor)∪∏name(student)
输出两个输入关系中元组的并
2.7总结
关系数据模型(relational data model)建立在表的集合的基础上。数据库系统的用户可以对这些表进行查询,可以插入新元组、删除元组以及更新(修改)元组。表达这些操作的语言有几种。
关系的模式(schema)是指它的逻辑设计,而关系的实例(instance)是指它在特定时刻的内容。数据库的模式和实例的定义是类似的。关系的模式包括它的属性,还可能包括属性类型和关系上的约束,比如主码和外码约束。
关系的超码(superkey)是一个或多个属性的集合,这些属性上的取值保证可以唯一识别出关系中的元组。候选码是一个最小的超码,也就是说,它是一组构成超码的属性集,但这组属性的任意子集都不是超码。关系的一个候选码被选作主码(primary key)。
在参照关系中的外码(foreign key)是这样的一个属性集合:对于参照关系中的每个元组来说,它在外码属性上的取值肯定等于被参照关系中某个元组在主码上的取值。
模式图(schema diagram)是数据库中模式的图形化表示,它显示了数据库中的关系,关系的属性、主码和外码。
关系查询语言(relational query language)定义了一组运算集,这些运算可作用于表上,并输出表作为结果。这些运算可以组合成表达式,表达所需的查询。
关系代数(relational algebra)提供了一组运算,它们以一个或多个关系为输入,返回一个关系作为输出。诸如SQL这样的实际查询语言是基于关系代数的,但增加了一些有用的句法特征。
术语回顾
表
关系
元组
空值
数据库模式
数据库实例
关系模式
关系实例
码
超码
候选码
主码
外码
参照关系
被参照关系
属性
域
原子域
参照完整性约束
模式图
查询语言
过程化语言
非过程化语言
关系运算
选择元组
选择属性
自然连接
笛卡儿积
集合运算
关系代数
实践习题
2.1考虑图214所示关系数据库。这些关系上适当的主码是什么?
2.2考虑从instructor的dept_name属性到department关系的外码约束,给出对这些关系的插入和删除示例,使得它们破坏外码约束。
2.3考虑time_slot关系。假设一个特定的时间段可以在一周之内出现多次,解释为什么day和start_time是该关系主码的一部分,而end_time却不是。
2.4在图21所示instructor的实例中,没有两位教师同名。我们是否可以据此断定name可用来作为instructor的超码(或主码)?
2.5先执行student和advisor的笛卡儿积,然后在结果上执行基于谓词s_id=ID的选择运算,最后的结果是什么?(采用关系代数的符号表示,此查询可写作σs_id=ID(student×advisor)。)
employee(personname, street, city)
works(personname, companyname, salary)
company(companyname, city)
图214习题2.1、习题2.7和习题2.12的关系数据库
2.6考虑下面的表达式,哪些使用了关系代数运算的结果来作为另一个运算的输入?对于每个表达式,说明表达式的含义。
a.(σyear≥2009(takes) student)
b.(σyear≥2009(takes student)
c.∏ID,name,course_id (student takes)
2.7考虑图214所示关系数据库。给出关系代数表达式来表示下列每一个查询:
a.找出居住在“Miami”城市的所有员工姓名。
b.找出工资在100 000美元以上的所有员工姓名。
c.找出居住在“Miami”并且工资在100 000美元以上的所有员工姓名。
2.8考虑图215所示银行数据库。对于下列每个查询,给出一个关系代数表达式:
a.找出位于“Chicago”的所有支行名字。
b.找出在支行“Downtown”有贷款的所有贷款人姓名。
branch(branch_name, branch_city, assets)
customer (customer_name, customer_street, customer_city)
loan (loan_number, branch_name, amount)
borrower (customer_name, loan_number)
account (account_number, branch_name, balance)
depositor (customer_name, account_number)
图215习题2.8、习题2.9和习题2.13的银行数据库
习题
2.9考虑图215所示银行数据库。
a.适当的主码是什么?
b.给出你选择的主码,确定适当的外码。
假定支行名字和客户名字分别唯一地标识各支行和各客户,但贷款和账户可以与一个以上客户相联系。
2.10考虑图28所示advisor关系,advisor的主码是s_id。假设一个学生可以有多位指导老师。那么,s_id还是advisor关系的主码吗?如果不是,advisor的主码会是什么呢?
2.11解释术语关系和关系模式在意义上的区别。
2.12考虑图214所示关系数据库。给出关系代数表达式来表示下列每一个查询:
a.找出为“First Bank Corporation”工作的所有员工姓名。
b.找出为“First Bank Corporation”工作的所有员工的姓名和居住城市。
c.找出为“First Bank Corporation”工作且挣钱超过10 000美元的所有员工的姓名、街道地址和居住城市。
2.13考虑图215所示银行数据库。对于下列每个查询,给出一个关系代数表达式:
a.找出贷款额度超过10 000美元的所有贷款号。
b.找出所有这样的存款人姓名,他拥有一个存款额大于6000美元的账户。
c.找出所有这样的存款人姓名,他在“Uptown”支行拥有一个存款额大于6000美元的账户。
2.14列出在数据库中引入空值的两个原因。
2.15讨论过程化和非过程化语言的相对优点。
文献注解
IBM San Jose研究实验室的E.F.Codd于20世纪60年代末提出了关系模型(Codd [1970])。这一工作使Codd在1981年获得了声望很高的ACM图灵奖(Codd[1982])。
在Codd最初的论文发表之后,几个研究项目开始进行,它们的目标是构造实际的关系数据库系统,其中包括IBM San Jose研究实验室的System R、加州大学Berkeley分校的Ingres,以及IBM T.J.Watson 研究实验中心的QuerybyExample。
大量关系数据库产品现在可以从市场上购得。其中包括IBM的DB2以及Informix、Oracle、Sybase和微软的SQL Server。开源关系数据库系统包括MySQL和PostgreSQL。微软的Access是一个单用户的数据库产品,它作为微软Office套件的一部分。
Atzeni和Antonellis[1993]、Maier[1983]以及Abiteboul等[1995]是专门讨论关系数据模型的文献。
第3章
Database System Concepts,6E
SQL
商业性使用或实验性使用的数据库查询语言有好几种。在本章以及第4章和第5章,我们学习使用最为广泛的查询语言:SQL。
尽管我们说SQL语言是一种“查询语言”,但是除了数据库查询,它还具有很多别的功能,它可以定义数据结构、修改数据库中的数据以及说明安全性约束条件等。
我们的目的并不是提供一个完整的SQL用户手册,而是介绍SQL的基本结构和概念。SQL的各种实现可能在一些细节上有所不同,或者只支持整个语言的一个子集。
3.1SQL查询语言概览
SQL最早的版本是由IBM开发的,它最初被叫做Sequel,在20世纪70年代早期作为System R项目的一部分。Sequel语言一直发展至今,其名称已变为SQL(结构化查询语言)。现在有许多产品支持SQL语言,SQL已经很明显地确立了自己作为标准的关系数据库语言的地位。
1986年美国国家标准化组织(ANSI)和国际标准化组织(ISO)发布了SQL标准:SQL86。1989年ANSI发布了一个SQL的扩充标准:SQL89。该标准的下一个版本是SQL92标准,接着是SQL:1999,SQL:2003,SQL:2006,最新的版本是SQL:2008。文献注解中提供了关于这些标准的参考文献。
SQL语言有以下几个部分:
数据定义语言(DataDefinition Language, DDL):SQL DDL提供定义关系模式、删除关系以及修改关系模式的命令。
数据操纵语言(DataManipulation Language, DML):SQL DML提供从数据库中查询信息,以及在数据库中插入元组、删除元组、修改元组的能力。
完整性(integrity):SQL DDL包括定义完整性约束的命令,保存在数据库中的数据必须满足所定义的完整性约束。破坏完整性约束的更新是不允许的。
视图定义(view definition):SQL DDL包括定义视图的命令。
事务控制(transaction control):SQL包括定义事务的开始和结束的命令。
嵌入式SQL和动态SQL(embedded SQL and dynamic SQL):嵌入式和动态SQL定义SQL语句如何嵌入到通用编程语言,如C、C++和Java中。
授权(authorization):SQL DDL包括定义对关系和视图的访问权限的命令。
本章我们给出对SQL的基本DML和DDL特征的概述。在此描述的特征自SQL92以来就一直是SQL标准的部分。
在第4章我们提供对SQL查询语言更详细的介绍,包括:(a) 各种连接的表达;(b) 视图;(c) 事务;(d) 完整性约束;(e) 类型系统;(f) 授权。
在第5章我们介绍SQL语言更高级的特征,包括:(a) 允许从编程语言中访问SQL的机制;(b) SQL函数和过程;(c) 触发器;(d) 递归查询;(e) 高级聚集特征;(f) 为数据分析设计的一些特征,它们在SQL:1999中引入,并在SQL的后续版本中使用。
尽管大多数SQL实现支持我们在此描述的标准特征,读者还是应该意识到不同SQL实现之间的差异。大多数SQL实现还支持一些非标准的特征,但不支持一些更高级的特征。万一你发现在此描述的一些语言特征在你使用的系统中不起作用,请参考你的数据库系统用户手册,看看它所支持的特征究竟是什么。
3.2SQL数据定义
数据库中的关系集合必须由数据定义语言(DDL)指定给系统。SQL的DDL不仅能够定义一组关系,还能够定义每个关系的信息,包括:
每个关系的模式。
每个属性的取值类型。
完整性约束。
每个关系维护的索引集合。
每个关系的安全性和权限信息。
每个关系在磁盘上的物理存储结构。
我们在此只讨论基本模式定义和基本类型,对SQL DLL其他特征的讨论将放到第4章和第5章进行。
3.2.1基本类型
SQL标准支持多种固有类型,包括:
char(n):固定长度的字符串,用户指定长度n。也可以使用全称character。
varchar(n):可变长度的字符串,用户指定最大长度n,等价于全称character varying。
int:整数类型(和机器相关的整数的有限子集),等价于全称integer。
smallint:小整数类型(和机器相关的整数类型的子集)。
numeric(p,d):定点数,精度由用户指定。这个数有p位数字(加上一个符号位),其中d位数字在小数点右边。所以在一个这种类型的字段上,numeric(3,1)可以精确储存44.5,但不能精确存储444.5或0.32这样的数。
real,double precision:浮点数与双精度浮点数,精度与机器相关。
float(n):精度至少为n位的浮点数。
更多类型将在4.5节介绍。
每种类型都可能包含一个被称作空值的特殊值。空值表示一个缺失的值,该值可能存在但并不为人所知,或者可能根本不存在。在可能的情况下,我们希望禁止加入空值,正如我们马上将看到的那样。
char数据类型存放固定长度的字符串。例如,属性A的类型是char(10)。如果我们为此属性存入字符串“Avi”,那么该字符串后会追加7个空格来使其达到10个字符的串长度。反之,如果属性B的类型是varchar(10),我们在属性B中存入字符串“Avi”,则不会增加空格。当比较两个char类型的值时,如果它们的长度不同,在比较之前会自动在短值后面加上额外的空格以使它们的长度一致。
当比较一个char类型和一个varchar类型的时候,也许读者会期望在比较之前会自动在varchar类型后面加上额外的空格以使长度一致;然而,这种情况可能发生也可能不发生,这取决于数据库系统。其结果是,即便上述属性A和B中存放的是相同的值“Avi”,A=B的比较也可能返回假。我们建议始终使用varchar类型而不是char类型来避免这样的问题。
SQL也提供nvarchar类型来存放使用Unicode表示的多语言数据。然而,很多数据库甚至允许在varchar类型中存放Unicode(采用UTF8表示)。
3.2.2基本模式定义
我们用create table命令定义SQL关系。下面的命令在数据库中创建了一个department关系。
create table department
(dept_name varchar (20),
building varchar (15),
budget numeric (12,2),
primary key (dept_name));
上面创建的关系具有三个属性,dept_name是最大长度为20的字符串,building是最大长度为15的字符串,budget是一个12位的数,其中2位数字在小数点后面。create table命令还指明了dept_name属性是department关系的主码。
create table命令的通用形式是:
create table r
(A1D1,
A2D2,
…,
AnDn,
<完整性约束1>,
…,
< 完整性约束k>);
其中r是关系名,每个Ai是关系r模式中的一个属性名,Di是属性Ai的域,也就是说Di指定了属性Ai的类型以及可选的约束,用于限制所允许的Ai取值的集合。
create table命令后面用分号结束,本章后面的其他SQL语句也是如此,在很多SQL实现中,分号是可选的。
SQL支持许多不同的完整性约束。在本节我们只讨论其中少数几个:
primary key(Aj1,Aj2,…,A jm):primarykey声明表示属性Aj1,Aj2,…,A jm构成关系的主码。主码属性必须非空且唯一,也就是说没有一个元组在主码属性上取空值,关系中也没有两个元组在所有主码属性上取值相同。虽然主码的声明是可选的,但为每个关系指定一个主码通常会更好。
foreign key(Ak1 , Ak2, …, Akn)references :foreign key声明表示关系中任意元组在属性(Ak1 , Ak2, …, Akn)上的取值必须对应于关系s中某元组在主码属性上的取值。
图31给出了我们在书中使用的大学数据库的部分SQL DDL定义。course表的定义中声明了“foreign key(dept_name) references department”。此外码声明表明对于每个课程元组来说,该元组所表示的系名必然存在于department关系的主码属性(dept_name)中。没有这个约束的话,就可能有某门课程指定了一个不存在的系名。图31还给出了表section、instructor和teaches上的外码约束。
create table department
(dept_name varchar(20),
building varchar (15),
budget numeric (12,2),
primary key (dept_name));
create table course
(course_id varchar (7),
title varchar (50),
dept_name varchar (20),
credits numeric (2,0),
primary key (course_id),
foreign key (dept_name) references department);
create table instructor
(ID varchar (5),
name varchar (20) not null,
dept_name varchar (20),
salary numeric (8,2),
primary key (ID),
foreign key (dept_name) references department);
create table section
(course_id varchar (8),
sec_id varchar (8),
semester varchar (6),
year numeric (4,0),
building varchar (15),
room_number varchar (7),
time_slot_id varchar (4),
primary key (course_id, sec_id, semester, year),
foreign key (course_id) references course);
create table teaches
(ID varchar (5),
course_id varchar (8),
sec_id varchar (8),
semester varchar (6),
year numeric (4,0),
primary key (ID, course_id, sec_id, semester, year),
foreign key (course_id, sec_id, semester, year) references section,
foreign key (ID) references instructor);
图31大学数据库的部分SQL数据定义
not null:一个属性上的not null约束表明在该属性上不允许空值。换句话说,此约束把空值排除在该属性域之外。例如在图31中,instructor关系的name属性上的not null约束保证了教师的姓名不会为空。
有关外码约束的更多细节以及create table命令可能包含的其他完整性约束将在后面4.4节介绍。
SQL禁止破坏完整性约束的任何数据库更新。例如,如果关系中一条新插入或新修改的元组在任意一个主码属性上有空值,或者元组在主码属性上的取值与关系中的另一个元组相同,SQL将标记一个错误,并阻止更新。类似地,如果插入的course元组在dept_name上的取值没有出现在department关系中,就会破坏course上的外码约束,SQL会阻止这种插入的发生。
一个新创建的关系最初是空的。我们可以用insert命令将数据加载到关系中。例如,如果我们希望插入如下事实:在Biology系有一个名叫Smith的教师,其instructor_id为10211,工资为66 000美元,可以这样写:
insert into instructor
values (10211, ’Smith’, ’Biology’, 66000);
值被给出的顺序应该遵循对应属性在关系模式中列出的顺序。插入命令有很多有用的特性,后面将在3.9.2节进行更详细的介绍。
我们可以使用delete命令从关系中删除元组。命令
delete from student;
将从student关系中删除所有元组。其他格式的删除命令允许指定待删除的元组;我们将在3.9.1节对删除命令进行更详细的介绍。
如果要从SQL数据库中去掉一个关系,我们使用drop table命令。drop table命令从数据库中删除关于被去掉关系的所有信息。命令
drop table r;
是比
delete from r;
更强的语句。后者保留关系r,但删除r中的所有元组。前者不仅删除r的所有元组,还删除r的模式。一旦r被去掉,除非用create table命令重建r,否则没有元组可以插入到r中。
我们使用alter table命令为已有关系增加属性。关系中的所有元组在新属性上的取值将被设为null。alter table命令的格式为:
alter table r add A D;
其中r是现有关系的名字,A是待添加属性的名字,D是待添加属性的域。我们可以通过命令
alter table r drop A;
从关系中去掉属性。其中r是现有关系的名字,A是关系的一个属性的名字。很多数据库系统并不支持去掉属性,尽管它们允许去掉整个表。
图32“select name from
instructor”的结果
3.3SQL查询的基本结构
SQL查询的基本结构由三个子句构成:select、from和where。查询的输入是在from子句中列出的关系,在这些关系上进行where和select子句中指定的运算,然后产生一个关系作为结果。我们通过例子介绍SQL的语法,后面再描述SQL查询的通用结构。
3.3.1单关系查询
我们考虑使用大学数据库例子的一个简单查询:“找出所有教师的名字”。教师的名字可以在instructor关系中找到,因此我们把该关系放到from子句中。教师的名字出现在name属性中,因此我们把它放到select子句中。
select name
from instructor;
其结果是由属性名为name的单个属性构成的关系。如果instructor关系如图21所示,那么上述查询的结果关系如图32所示。
现在考虑另一个查询:“找出所有教师所在的系名”,此查询可写为:
select dept_name
from instructor;
因为一个系有多个教师,所以在instructor关系中,一个系的名称可以出现不止一次。上述查询的结果是一个包含系名的关系,如图33所示。
图33“select dept_name
from instructor”的结果
在关系模型的形式化数学定义中,关系是一个集合。因此,重复的元组不会出现在关系中。在实践中,去除重复是相当费时的,所以SQL允许在关系以及SQL表达式结果中出现重复。因此,在上述SQL查询中,每个系名在instructor关系的元组中每出现一次,都会在查询结果中列出一次。
有时候我们想要强行删除重复,可在select后加入关键词distinct。如果我们想去除重复,可将上述查询重写为:
select distinct dept_name
from instructor;
在上述查询的结果中,每个系名最多只出现一次。
SQL允许我们使用关键词all来显式指明不去除重复:
select all dept_name
from instructor;
既然保留重复元组是默认的,在例子中我们将不再使用all。为了保证在我们例子的查询结果中删除重复元组,我们将在所有必要的地方使用distinct。
select子句还可带含有+、-、*、/运算符的算术表达式,运算对象可以是常数或元组的属性。例如,查询
select ID, name, dept_name, salary* 1.1
from instructor;
返回一个与instructor一样的关系,只是属性salary的值是原来的1.1倍。这显示了如果我们给每位教师增长10%的工资的结果。注意这并不导致对instructor关系的任何改变。
SQL还提供了一些特殊数据类型,如各种形式的日期类型,并允许一些作用于这些类型上的算术函数。我们在4.5.1节进一步讨论这个问题。
where子句允许我们只选出那些在from子句的结果关系中满足特定谓词的元组。考虑查询“找出所有在Computer Science系并且工资超过70 000美元的教师的姓名”,该查询用SQL可以写为:
select name
from instructor
where dept_name = ‘Comp.Sci.’ and salary> 70000;
如果instructor关系如图21所示,那么上述查询的结果关系如图34所示。
图34“找出所有在Computer
Science系并且工资超过70 000
美元的教师的姓名”的结果
SQL允许在where子句中使用逻辑连词and、or和not。逻辑连词的运算对象可以是包含比较运算符<、<=、>、>=、=和<>的表达式。SQL允许我们使用比较运算符来比较字符串、算术表达式以及特殊类型,如日期类型。
在本章的后面,我们将研究where子句谓词的其他特征。
3.3.2多关系查询
到此为止我们的查询示例都是基于单个关系的。通常查询需要从多个关系中获取信息。我们现在来学习如何书写这样的查询。
作为一个示例,假设我们想回答这样的查询:“找出所有教师的姓名,以及他们所在系的名称和系所在建筑的名称”。
考虑instructor关系的模式,我们发现可以从dept_name属性得到系名,但是系所在建筑的名称是在department关系的building属性中给出的。为了回答查询,instructor关系中的每个元组必须与department关系中的元组匹配,后者在dept_name上的取值相配于instructor元组在dept_name上的取值。
为了在SQL中回答上述查询,我们把需要访问的关系都列在from子句中,并在where子句中指定匹配条件。上述查询可用SQL写为:
select name, instructor.dept_name, building
from instructor, department
where instructor.dept_name = department.dept_name;
如果instructor和department关系分别如图21和图25所示,那么此查询的结果关系如图35所示。
图35“找出所有教师的姓名,以及他们
所在系的名称和系所在建筑的名称”的结果
注意dept_name属性既出现在instructor关系中,也出现在department中,关系名被用作前缀(在instructor.dept_name和department.dept_name中)来说明我们使用的是哪个属性。相反,属性name和building只出现在一个关系中,因而不需要把关系名作为前缀。
这种命名惯例需要出现在from子句中的关系具有可区分的名字。在某些情况下这样的要求会引发问题,比如当需要把来自同一个关系的两个不同元组的信息进行组合的时候。在3.4.1节,我们将看到如何使用更名运算来避免这样的问题。
现在我们考虑涉及多个关系的SQL查询的通用形式。正如我们前面已经看到的,一个SQL查询可以包括三种类型的子句:select子句、from子句和where子句。每种子句的作用如下:
select子句用于列出查询结果中所需要的属性。
from子句是一个查询求值中需要访问的关系列表。
where子句是一个作用在from子句中关系的属性上的谓词。
一个典型的SQL查询具有如下形式:
select A1, A2, … , An
from r1, r2, … , rm
where P;
每个Ai代表一个属性,每个ri代表一个关系。P是一个谓词。如果省略where子句,则谓词P为true。
尽管各子句必须以select、from、where的次序写出,但理解查询所代表运算的最容易的方式是以运算的顺序来考察各子句:首先是from,然后是where,最后是select实践中,SQL也许会将表达式转换成更高效执行的等价形式。我们将把效率问题推迟到第11章中探讨。 。
通过from子句定义了一个在该子句中所列出关系上的笛卡儿积。它可以用集合理论来形式化地定义,但最好通过下面的迭代过程来理解,此过程可为from子句的结果关系产生元组。
for each 元组 t1 in 关系 r1
for each 元组 t2 in 关系 r2
…
for each 元组 tm in 关系 rm
把t1, t2, …, tm 连接成单个元组 t
把 t加入结果关系中
此结果关系具有来自from子句中所有关系的所有属性。由于在关系ri和rj中可能出现相同的属性名,正如我们此前所看到的,我们在属性名前加上关系名作为前缀,表示该属性来自于哪个关系。
例如,关系instructor和teaches的笛卡儿积的关系模式为:
(instructor.ID, instructor.name, instructor.dept_name, instructor.salary
teaches.ID, teaches.course_id, teaches.sec_id, teaches.semester, teaches.year)
有了这个模式,我们可以区分出instructor.ID和teaches.ID。对于那些只出现在单个模式中的属性,我们通常去掉关系名前缀。这种简化并不会造成任何混淆。这样我们可以把关系模式写为:
(instructor.ID, name, dept_name, salary
teaches.ID, course_id, sec_id, semester, year)
为举例说明,考察图21中的instructor关系和图27中的teaches关系。它们的笛卡儿积如图36所示,图中只包括了构成笛卡儿积结果的一部分元组。注意为了减小图36中表的宽度,我们把instructor.ID更名为inst.ID。
图36instructor关系和teaches关系的笛卡儿积
通过笛卡儿积把来自instructor和teaches中相互没有关联的元组组合起来。instructor中的每个元组和teaches中的所有元组都要进行组合,即使是那些代表不同教师的元组。其结果可能是一个非常庞大的关系,创建这样的笛卡儿积通常是没有意义的。
反之,where子句中的谓词用来限制笛卡儿积所建立的组合,只留下那些对所需答案有意义的组合。我们希望有个涉及instructor和teaches的查询,它把instructor中的特定元组t只与teaches中表示跟t相同教师的元组进行组合。也就是说,我们希望把teaches元组只和具有相同ID值的instructor元组进行匹配。下面的SQL查询满足这个条件,从这些匹配元组中输出教师名和课程标识。
select name, course_id
from instructor, teaches
where instructor.ID = teaches.ID;
注意上述查询只输出讲授了课程的教师,不会输出那些没有讲授任何课程的教师。如果我们希望输出那样的元组,可以使用一种被称作外连接的运算,外连接将在4.1.2节讲述。
如果instructor关系如图21所示,teaches关系如图27所示,那么前述查询的结果关系如图37所示。注意教师Gold、Califieri和Singh,由于他们没有讲授任何课程,就不出现在上述结果中。
图37“对于大学中所有讲授课程
的教师,找出他们的姓名以及所
讲述的所有课程标识”的结果
如果我们只希望找出Computer Science系的教师名和课程标识,我们可以在where子句中增加另外的谓词,如下所示:
select name, course_id
from instructor, teaches
where instructor.ID = teaches.ID and instructor.dept_name = ’Comp.Sci.’;
注意既然dept_name属性只出现在instructor关系中,我们在上述查询中可以只使用dept_name来替代instructor.dept_name。
通常说来,一个SQL查询的含义可以理解如下:
1.为from子句中列出的关系产生笛卡儿积。
2.在步骤1的结果上应用where子句中指定的谓词。
3.对于步骤2结果中的每个元组,输出select子句中指定的属性(或表达式的结果)。
上述步骤的顺序有助于明白一个SQL查询的结果应该是什么样的,而不是这个结果是怎样被执行的。在SQL的实际实现中不会执行这种形式的查询,它会通过(尽可能)只产生满足where子句谓词的笛卡儿积元素来进行优化执行。我们在后面第11章学习那样的实现技术。
当书写查询时,需要小心设置合适的where子句条件。如果在前述SQL查询中省略where子句条件,就会输出笛卡儿积,那是一个巨大的关系。对于图21中的instructor样本关系和图27中的teaches样本关系,它们的笛卡儿积具有12×13=156个元组,比我们在书中能显示的还要多!在更糟的情况下,假设我们有比图中所示样本关系更现实的教师数量,比如200个教师。假使每位教师讲授3门课程,那么我们在teaches关系中就有600个元组。这样上述迭代过程会产生出200×600=120 000个元组作为结果。
3.3.3自然连接
在我们的查询示例中,需要从instructor和teaches表中组合信息,匹配条件是需要instructor.ID等于teaches.ID。这是在两个关系中具有相同名称的所有属性。实际上这是一种通用的情况,也就是说,from子句中的匹配条件在最通常的情况下需要在所有匹配名称的属性上相等。
为了在这种通用情况下简化SQL编程者的工作,SQL支持一种被称作自然连接的运算,下面我们就来讨论这种运算。事实上SQL还支持几种另外的方式使得来自两个或多个关系的信息可以被连接(join)起来。我们已经见过怎样利用笛卡儿积和where子句谓词来连接来自多个关系的信息。连接来自多个关系信息的其他方式在4.1节介绍。
自然连接(natural join)运算作用于两个关系,并产生一个关系作为结果。不同于两个关系上的笛卡儿积,它将第一个关系的每个元组与第二个关系的所有元组都进行连接;自然连接只考虑那些在两个关系模式中都出现的属性上取值相同的元组对。因此,回到instructor和teaches关系的例子上,instructor和teaches的自然连接计算中只考虑这样的元组对:来自instructor的元组和来自teaches的元组在共同属性ID上的取值相同。
结果关系如图38所示,只有13个元组,它们给出了关于每个教师以及该教师实际讲授的课程的信息。注意我们并没有重复列出那些在两个关系模式中都出现的属性,这样的属性只出现一次。还要注意列出属性的顺序:先是两个关系模式中的共同属性,然后是那些只出现在第一个关系模式中的属性,最后是那些只出现在第二个关系模式中的属性。
图38instructor关系和teaches关系的自然连接
考虑查询“对于大学中所有讲授课程的教师,找出他们的姓名以及所讲述的所有课程标识”,此前我们曾把该查询写为:
select name, course_id
from instructor, teaches
where instructor.ID = teaches.ID;
该查询可以用SQL的自然连接运算更简洁地写作:
select name, course_id
from instructor natural join teaches;
以上两个查询产生相同的结果。
正如我们此前所见,自然连接运算的结果是关系。从概念上讲,from子句中的“instructor natural join teaches”表达式可以替换成执行该自然连接后所得到的关系。其结果是不可能用包含了原始关系名的属性名来指代自然连接结果中的属性,例如instructor.name或teaches.course_id,但是我们可以使用诸如name和course_id那样的属性名,而不带关系名。 然后在这个关系上执行where和select子句,就如我们在前面3.3.2节所看到的那样。
在一个SQL查询的from子句中,可以用自然连接将多个关系结合在一起,如下所示:
select A1, A2, …, An
from r1 natural join r2 natural join …natural join rm
where P;
更为一般地,from子句可以为如下形式:
from E1, E2, …, En
其中每个Ei可以是单个关系或一个包含自然连接的表达式。例如,假设我们要回答查询“列出教师的名字以及他们所讲授课程的名称”。此查询可以用SQL写为:
select name, title
from instructor natural join teaches, course
where teaches.course_id= course.course_id;
先计算instructor和teaches的自然连接,正如我们此前所见,再计算该结果和course的笛卡儿积,where子句从这个结果中提取出这样的元组:来自连接结果的课程标识与来自course关系的课程标识相匹配。注意where子句中的teaches.course_id表示自然连接结果中的course_id域,因为该域最终来自teaches关系。
相反,下面的SQL查询不会计算出相同的结果:
select name, title
from instructor natural join teaches natural join course;
为了说明原因,注意instructor和teaches的自然连接包括属性(ID, name, dept_name, salary, course_id, sec_id),而course关系包含的属性是(course_id, title, dept_name, credits)。作为这二者自然连接的结果,需要来自这两个输入的元组既要在属性dept_name上取值相同,还要在course_id上取值相同。该查询将忽略所有这样的(教师姓名,课程名称)对:其中教师所讲授的课程不是他所在系的课程。而前一个查询会正确输出这样的对。
为了发扬自然连接的优点,同时避免不必要的相等属性带来的危险,SQL提供了一种自然连接的构造形式,允许用户来指定需要哪些列相等。下面的查询说明了这个特征:
select name, title
from (instructor natural join teaches) join course using (course_id);
join…using运算中需要给定一个属性名列表,其两个输入中都必须具有指定名称的属性。考虑运算r1 join r2 using(A1, A2),它与r1和r2的自然连接类似,只不过在t1.A1=t2.A1并且t1.A2=t2.A2成立的前提下,来自r1的元组t1和来自r2的元组t2就能匹配,即使r1和r2都具有名为A3的属性,也不需要t1.A3=t2.A3成立。
这样,在前述SQL查询中,连接构造允许teaches.dept_name和course.dept_name是不同的,该SQL查询给出了正确的答案。
3.4附加的基本运算
SQL中还支持几种附加的基本运算。
3.4.1更名运算
重新考察我们此前使用过的查询:
select name, course_id
from instructor, teaches
where instructor.ID = teaches.ID;
此查询的结果是一个具有下列属性的关系:
name, course_id
结果中的属性名来自from子句中关系的属性名。
但我们不能总是用这个方法派生名字,其原因有几点:首先,from子句的两个关系中可能存在同名属性,在这种情况下,结果中就会出现重复的属性名;其次,如果我们在select子句中使用算术表达式,那么结果属性就没有名字;再次,尽管如上例所示,属性名可以从基关系导出,但我们也许想要改变结果中的属性名字。因此,SQL提供了一个重命名结果关系中属性的方法。即使用如下形式的as子句:
oldname as newname
as子句既可出现在select子句中,也可出现在from子句中。
其结果是,有可能在某些系统中不能用包含了原始关系名的属性名来指代自然连接结果中的属性,例如instructor.name或teaches.course_id。在某些系统中允许这样做,某些系统中不允许,某些系统中对于除连接属性(即在两个关系模式中同时出现的属性)之外的其他属性允许。然而,我们可以不带关系名地使用诸如name和course_id这样的属性名。
例如,如果我们想用名字instructor_name来代替属性名name,我们可以重写上述查询如下:
select name as instructor_name, course_id
from instructor, teaches
where instructor.ID= teaches.ID;
as子句在重命名关系时特别有用。重命名关系的一个原因是把一个长的关系名替换成短的,这样在查询的其他地方使用起来就更为方便。为了说明这一点,我们重写查询“对于大学中所有讲授课程的教师,找出他们的姓名以及所讲述的所有课程标识”:
select T.name, S.course_id
from instructor as T, teaches as S
where T.ID = S.ID;
重命名关系的另一个原因是为了适用于需要比较同一个关系中的元组的情况。为此我们需要把一个关系跟它自身进行笛卡儿积运算,如果不重命名的话,就不可能把一个元组与其他元组区分开来。假设我们希望写出查询:“找出满足下面条件的所有教师的姓名,他们的工资至少比Biology系某一个教师的工资要高”,我们可以写出这样的SQL表达式:
select distinct T.name
from instructor as T, instructor as S
where T.salary> S.salary and S.dept_name = ’Biology’;
注意我们不能使用instructor.salary这样的写法,因为这样并不清楚到底是希望引用哪一个instructor。
在上述查询中,T和S可以被认为是instructor关系的两个拷贝,但更准确地说是被声明为instructor关系的别名,也就是另外的名字。像T和S那样被用来重命名关系的标识符在SQL标准中被称作相关名称(correlation name),但通常也被称作表别名(table alias),或者相关变量(correlation variable),或者元组变量(tuple variable)。
注意用文字表达上述查询更好的方式是:“找出满足下面条件的所有教师的姓名,他们比Biology系教师的最低工资要高”。我们早先的表述更符合我们所写的SQL,但后面的表述更直观,事实上它可以直接用SQL来表达,正如我们将在3.8.2节看到的那样。
3.4.2字符串运算
SQL使用一对单引号来标示字符串,例如‘Computer’。如果单引号是字符串的组成部分,那就用两个单引号字符来表示,如字符串“it′s right”可表示为“it″s right”。
在SQL标准中,字符串上的相等运算是大小写敏感的,所以表达式“′comp.sci.′=′Comp.Sci.′”的结果是假。然而一些数据库系统,如MySQL和SQL Server,在匹配字符串时并不区分大小写,所以在这些数据库中“′comp.sci.′=′Comp.Sci.′”的结果可能是真。然而这种默认方式是可以在数据库级或特定属性级被修改的。
SQL还允许在字符串上有多种函数,例如串联(使用“‖”)、提取子串、计算字符串长度、大小写转换(用upper(s)将字符串s转换为大写或用lower(s)将字符串s转换为小写)、去掉字符串后面的空格(使用trim(s)),等等。不同数据库系统所提供的字符串函数集是不同的,请参阅你的数据库系统手册来获得它所支持的实际字符串函数的详细信息。
在字符串上可以使用like操作符来实现模式匹配。我们使用两个特殊的字符来描述模式:
百分号(%):匹配任意子串。
下划线(_):匹配任意一个字符。
模式是大小写敏感的,也就是说,大写字符与小写字符不匹配,反之亦然。为了说明模式匹配,考虑下列例子:
‘Intro%’匹配任何以“Intro”打头的字符串。
‘%Comp%’匹配任何包含“Comp”子串的字符串,例如‘Intro.to Computer Science’和‘Computational Biology’。
‘___’匹配只含三个字符的字符串。
‘___%’匹配至少含三个字符的字符串。
在SQL中用比较运算符like来表达模式。考虑查询“找出所在建筑名称中包含子串‘Watson’的所有系名”,该查询的写法如下:
select dept_name
from department
where building like ’%Watson%’;
为使模式中能够包含特殊模式的字符(即%和_),SQL允许定义转义字符。转义字符直接放在特殊字符的前面,表示该特殊字符被当成普通字符。我们在like比较运算中使用escape关键词来定义转义字符。为了说明这一用法,考虑以下模式,它使用反斜线(\)作为转义字符 :
like ‘ab\%cd%’ escape ‘\’ 匹配所有以“ab%cd”开头的字符串。
like ‘ab\\cd%’ escape ‘\’ 匹配所有以“ab\cd”开头的字符串。
SQL允许使用not like比较运算符搜寻不匹配项。一些数据库还提供like运算的变体,不区分大小写。
在SQL:1999中还提供similar to操作,它具备比like运算更强大的模式匹配能力。它的模式定义语法类似于UNIX中的正则表达式。
3.4.3select子句中的属性说明
星号“*”可以用在select子句中表示“所有的属性”,因而,如下查询的select子句中使用instructor.*:
select instructor.*
from instructor, teaches
where instructor.ID = teaches.ID;
表示instructor中的所有属性都被选中。形如select *的select子句表示from子句结果关系的所有属性都被选中。
3.4.4排列元组的显示次序
SQL为用户提供了一些对关系中元组显示次序的控制。order by子句就可以让查询结果中元组按排列顺序显示。为了按字母顺序列出在Physics系的所有教师,我们可以这样写:
select name
from instructor
where dept_name = ’Physics’
order by name;
order by子句默认使用升序。要说明排序顺序,我们可以用desc表示降序,或者用asc表示升序。此外,排序可在多个属性上进行。假设我们希望按salary的降序列出整个instructor关系。
如果有几位教师的工资相同,就将它们按姓名升序排列。我们用SQL将该查询表示如下:
select*
from instructor
order by salary desc, name asc;
3.4.5where子句谓词
为了简化where子句,SQL提供between比较运算符来说明一个值是小于或等于某个值,同时大于或等于另一个值的。如果我们想找出工资在90 000美元和100 000美元之间的教师的姓名,我们可以使用between比较运算符,如下所示:
select name
from instructor
where salary between 90000 and 100000;
它可以取代
select name
from instructor
where salary <=100000 and salary >=90000;
类似地,我们还可以使用not between比较运算符。
我们可以扩展前面看到过的查找教师名以及课程标识的查询,但考虑更复杂的情况,要求教师是生物系的:“查找 Biology系讲授了课程的所有教师的姓名和他们所讲授的课程”。为了写出这样的查询,我们可以在前面看到过的两个SQL查询的任意一个的基础上进行修改,在where子句中增加一个额外的条件。我们下面给出修改后的不使用自然连接的SQL查询形式:
select name, course_id
from instructor, teaches
where instructor.ID= teaches.ID and dept_name = ’Biology’;
SQL允许我们用记号(v1, v2, …, vn)来表示一个分量值分别为v1, v2, …, vn的n维元组。在元组上可以运用比较运算符,按字典顺序进行比较运算。例如,(a1, a2) <= (b1, b2)在a1 <= b1 且a2 <= b2时为真。类似地,当两个元组在所有属性上相等时,它们是相等的。这样,前述查询可被重写为如下形式:尽管这是SQL92标准的一部分,但某些SQL实现中可能不支持这种语法。
select name, course_id
from instructor, teaches
where (instructor.ID, dept_name)=(teaches.ID, ’Biology’);
3.5集合运算
SQL作用在关系上的union、intersect和except运算对应于数学集合论中的∪、∩和-运算。我们现在来构造包含在两个集合上使用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;
在我们后面的讨论中,将用c1和c2分别指代包含以上查询结果的两个关系,并在图39和图310中给出作用在如图26所示的section关系上的查询结果。注意c2包含两个对应于course_id为CS319的元组,因为该课程有两个课程段在2010年春季开课。
图39c1关系,列出2009年
秋季开设的课程
图310c2关系,列出2010年
春季开设的课程
3.5.1并运算
为了找出在2009年秋季开课,或者在2010年春季开课或两个学期都开课的所有课程,我们可写查询语句:我们在每条selectfromwhere语句上使用的括号是可省略的,但易于阅读。
图311c1 union c2
的结果
(select course_id
from section
where semester = ’Fall’ and year= 2009)
union
(select course_id
from section
where semester = ’Spring’ and year= 2010);
与select子句不同,union运算自动去除重复。这样,在如图26所示的section关系中,2010年春季开设CS319的两个课程段,CS101在2009年秋季和2010年春季学期各开设一个课程段,CS101和CS319在结果中只出现一次,如图311所示。
如果我们想保留所有重复,就必须用union all代替union:
(select course_id
from section
where semester = ’Fall’ and year= 2009)
union all
(select course_id
from section
where semester = ’Spring’ and year= 2010);
在结果中的重复元组数等于在c1和c2中出现的重复元组数的和。因此在上述查询中,每个CS319和CS101都将被列出两次。作为一个更深入的例子,如果存在这样一种情况:ECE101在2009年秋季学期开设4个课程段,在2010年春季学期开设2个课程段,那么在结果中将有6个ECE101元组。
3.5.2交运算
为了找出在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);
结果关系如图312所示,它只包括一个CS101元组。intersect运算自动去除重复。例如,如果存在这样的情况:ECE101在2009年秋季学期开设4个课程段,在2010年春季学期开设2个课程段,那么在结果中只有1个ECE101元组。
图312c1 intersect c2的结果
如果我们想保留所有重复,就必须用intersect all代替intersect:
(select course_id
from section
where semester = ’Fall’ and year= 2009)
intersect all
(select course_id
from section
where semester = ’Spring’ and year= 2010);
在结果中出现的重复元组数等于在c1和c2中出现的重复次数里最少的那个。例如,如果ECE101在2009年秋季学期开设4个课程段,在2010年春季学期开设2个课程段,那么在结果中有2个ECE101元组。
3.5.3差运算
为了找出在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);
该查询结果如图313所示。注意这正好是图39的c1关系减去不出现的CS101元组。except运算某些SQL实现,特别是Oracle,使用关键词minus代替except。 从其第一个输入中输出所有不出现在第二个输入中的元组,也即它执行集差操作。此运算在执行集差操作之前自动去除输入中的重复。例如,如果ECE101在2009年秋季学期开设4个课程段,在2010年春季学期开设2个课程段,那么在except运算的结果中将没有ECE101的任何拷贝。
图313c1 except c2的结果
如果我们想保留所有重复,就必须用except all代替except:
(select course_id
from section
where semester = ’Fall’ and year= 2009)
except all
(select course_id
from section
where semester = ’Spring’ and year= 2010);
结果中的重复元组数等于在c1中出现的重复元组数减去在c2中出现的重复元组数(前提是此差为正)。因此,如果ECE101在2009年秋季学期开设4个课程段,在2010年春季学期开设2个课程段,那么在结果中有2个ECE101元组。然而,如果ECE101在2009年秋季学期开设2个或更少的课程段,在2010年春季学期开设2个课程段,那么在结果中将不存在ECE101元组。
3.6空值
空值给关系运算带来了特殊的问题,包括算术运算、比较运算和集合运算。
如果算术表达式的任一输入为空,则该算术表达式(涉及诸如 +、-、*或/)结果为空。例如,如果查询中有一个表达式是r.A+5,并且对于某个特定的元组,r.A为空,那么对此元组来说,该表达式的结果也为空。
涉及空值的比较问题更多。例如,考虑比较运算“1 < null”。因为我们不知道空值代表的是什么,所以说上述比较为真可能是错误的。但是说上述比较为假也可能是错误的,如果我们认为比较为假,那么“not (1 < null)”就应该为真,但这是没有意义的。因而SQL将涉及空值的任何比较运算的结果视为unknown(既不是谓词is null,也不是is not null,我们在本节的后面介绍这两个谓词)。这创建了除true和false之外的第三个逻辑值。
由于在where子句的谓词中可以对比较结果使用诸如and、or和not的布尔运算,所以这些布尔运算的定义也被扩展到可以处理unknown值。
and:true and unknown的结果是unknown,false and unknown结果是 false,unknown and unknown 的结果是unknown。
or:true or unknown 的结果是true,false or unknown 结果是unknown,unknown or unknown 结果是unknown。
not:not unknown 的结果是unknown。
可以验证,如果r.A为空,那么“1 < r.A”和“not (1 < r.A)”结果都是unknown。
如果where子句谓词对一个元组计算出false或unknown,那么该元组不能被加入到结果集中。
SQL在谓词中使用特殊的关键词null测试空值。因而为找出instructor关系中salary为空值的所有教师,我们可以写成:
select name
from instructor
where salary is null;
如果谓词is not null所作用的值非空,那么它为真。
某些SQL实现还允许我们使用子句is unknown和is not unknown来测试一个表达式的结果是否为unknown,而不是true或false。
当一个查询使用select distinct子句时,重复元组将被去除。为了达到这个目的,当比较两个元组对应的属性值时,如果这两个值都是非空并且值相等,或者都是空,那么它们是相同的。所以诸如{(’A’,null),(’A’,null)}这样的两个元组拷贝被认为是相同的,即使在某些属性上存在空值。使用distinct子句会保留这样的相同元组的一份拷贝。注意上述对待空值的方式与谓词中对待空值的方式是不同的,在谓词中“null=null”会返回unknown,而不是true。
如果元组在所有属性上的取值相等,那么它们就被当作相同元组,即使某些值为空。上述方式还应用于集合的并、交和差运算。
3.7聚集函数
聚集函数是以值的一个集合(集或多重集)为输入、返回单个值的函数。SQL提供了五个固有聚集函数:
平均值:avg。
最小值:min。
最大值:max。
总和:sum。
计数:count。
sum和avg的输入必须是数字集,但其他运算符还可作用在非数字数据类型的集合上,如字符串。
3.7.1基本聚集
考虑查询“找出Computer Science系教师的平均工资”。我们书写该查询如下:
select avg (salary)
from instructor
where dept_name= ’Comp.Sci.’;
该查询的结果是一个具有单属性的关系,其中只包含一个元组,这个元组的数值对应Computer Science系教师的平均工资。数据库系统可以给结果关系的属性一个任意的名字,该属性是由聚集产生的。然而,我们可以用as子句给属性赋个有意义的名称,如下所示:
select avg (salary) as avg_salary
from instructor
where dept_name= ’Comp.Sci.’;
在图21的instructor关系中,Computer Science系的工资值是75 000美元、65 000美元和92 000美元,平均工资是232 000/3=77 333.33美元。
在计算平均值时保留重复元组是很重要的。假设Computer Science系增加了第四位教师,其工资正好是75 000美元。如果去除重复的话,我们会得到错误的答案(232 000/4=58 000美元),而正确的答案是76 750美元。
有些情况下在计算聚集函数前需先删掉重复元组。如果我们确实想删除重复元组,可在聚集表达式中使用关键词distinct。比方有这样一个查询示例“找出在2010年春季学期讲授一门课程的教师总数”,在该例中不论一个教师讲授了几个课程段,他只应被计算一次。所需信息包含在teaches关系中,我们书写该查询如下:
select count (distinct ID)
from teaches
where semester = ’Spring’ and year = 2010;
我们经常使用聚集函数count计算一个关系中元组的个数。SQL中该函数的写法是count(*)。因此,要找出course关系中的元组数,可写成:
select count(*)
from course;
由于在ID前面有关键字distinct,所以即使某位教师教了不止一门课程,在结果中他也仅被计数一次。
SQL不允许在用count(*)时使用distinct。在用max和min时使用distinct是合法的,尽管结果并无差别。我们可以使用关键词all替代distinct来说明保留重复元组,但是,既然all是默认的,就没必要这么做了。
3.7.2分组聚集
有时候我们不仅希望将聚集函数作用在单个元组集上,而且也希望将其作用到一组元组集上;在SQL中可用group by子句实现这个愿望。
group by子句中给出的一个或多个属性是用来构造分组的。在group by子句中的所有属性上取值相同的元组将被分在一个组中。
图314instructor关系的元组按照
dept_name属性分组
作为示例,考虑查询“找出每个系的平均工资”,该查询书写如下:
select dept_name, avg(salary) as avg_salary
from instructor
group by dept_name;
图314给出了instructor关系中的元组按照dept_name属性进行分组的情况,分组是计算查询结果的第一步。在每个分组上都要进行指定的聚集计算,查询结果如图315所示。
相反,考虑查询“找出所有教师的平均工资”。我们把此查询写作如下形式:
select avg (salary)
from instructor;
在这里省略了group by子句,因此整个关系被当作是一个分组。
图315查询“找出每个系的
平均工资”的结果关系
作为在元组分组上进行聚集操作的另一个例子,考虑查询“找出每个系在2010年春季学期讲授一门课程的教师人数”。有关每位教师在每个学期讲授每个课程段的信息在teaches关系中。但是,这些信息需要与来自instructor关系的信息进行连接,才能够得到每位教师所在的系名。这样,我们把此查询写做如下形式:
select dept_name, count (distinct ID) as instr_count
from instructor natural join teaches
where semester = ’Spring’ and year = 2010
group by dept_name;
其结果如图316所示。
图316查询“找出每个系在2010年
春季学期讲授一门课程的
教师人数”的结果关系
当SQL查询使用分组时,一个很重要的事情是需要保证出现在select语句中但没有被聚集的属性只能是出现在group by子句中的那些属性。换句话说,任何没有出现在group by子句中的属性如果出现在select子句中的话,它只能出现在聚集函数内部,否则这样的查询就是错误的。例如,下述查询是错误的,因为ID没有出现在group by子句中,但它出现在了select子句中,而且没有被聚集:
/* 错误查询 */
select dept_name, ID, avg (salary)
from instructor
group by dept_name;
在一个特定分组(通过dept_name定义)中的每位教师都有一个不同的ID,既然每个分组只输出一个元组,那就无法确定选哪个ID值作为输出。其结果是,SQL不允许这样的情况出现。
3.7.3having子句
有时候,对分组限定条件比对元组限定条件更有用。例如,我们也许只对教师平均工资超过42 000美元的系感兴趣。该条件并不针对单个元组,而是针对group by子句构成的分组。为表达这样的查询,我们使用SQL的having子句。having子句中的谓词在形成分组后才起作用,因此可以使用聚集函数。我们用SQL表达该查询如下:
select dept_name, avg (salary) as avg_salary
from instructor
group by dept_name
having avg (salary) > 42000;
其结果如图317所示。
图317查询“找出系平均工资超过
42 000美元的那些系中教师的平均
工资”的结果关系
与select子句的情况类似,任何出现在having子句中,但没有被聚集的属性必须出现在group by子句中,否则查询就被当成是错误的。
包含聚集、group by或having子句的查询的含义可通过下述操作序列来定义:
1.与不带聚集的查询情况类似,最先根据from子句来计算出一个关系。
2.如果出现了where子句,where子句中的谓词将应用到from子句的结果关系上。
3.如果出现了group by子句,满足where谓词的元组通过group by子句形成分组。如果没有group by子句,满足where谓词的整个元组集被当作一个分组。
4.如果出现了having子句,它将应用到每个分组上;不满足having子句谓词的分组将被抛弃。
5. select子句利用剩下的分组产生出查询结果中的元组,即在每个分组上应用聚集函数来得到单个结果元组。
为了说明在同一个查询中同时使用having子句和where子句的情况,我们考虑查询“对于在2009年讲授的每个课程段,如果该课程段有至少2名学生选课,找出选修该课程段的所有学生的总学分(tot_cred)的平均值”。
select course_id, semester, year, sec_id, avg (tot_cred)
from takes natural join student
where year = 2009
group by course_id, semester, year, sec_id
having count (ID) >= 2;
注意上述查询需要的所有信息来自关系takes和student,尽管此查询是关于课程段的,却并不需要与section进行连接。
3.7.4对空值和布尔值的聚集
空值的存在给聚集运算的处理带来了麻烦。例如,假设instructor关系中有些元组在salary上取空值。考虑以下计算所有工资总额的查询:
select sum(salary)
from instructor;
由于一些元组在salary上取空值,上述查询待求和的值中就包含了空值。SQL标准并不认为总和本身为null,而是认为sum运算符应忽略输入中的null值。
总而言之,聚集函数根据以下原则处理空值:除了count(*)外所有的聚集函数都忽略输入集合中的空值。由于空值被忽略,有可能造成参加函数运算的输入值集合为空集。规定空集的count运算值为0,其他所有聚集运算在输入为空集的情况下返回一个空值。在一些更复杂的SQL结构中空值的影响会更难以琢磨。
在SQL:1999中引入了布尔(boolean)数据类型,它可以取true、false、unknown三个值。有两个聚集函数:some和every,其含义正如直观意义一样,可用来处理布尔(boolean)值的集合。
3.8嵌套子查询
SQL提供嵌套子查询机制。子查询是嵌套在另一个查询中的selectfromwhere表达式。子查询嵌套在where子句中,通常用于对集合的成员资格、集合的比较以及集合的基数进行检查。从3.8.1到3.8.4节我们学习在where子句中嵌套子查询的用法。在3.8.5节我们学习在from子句中嵌套的子查询。在3.8.7节我们将看到一类被称作标量子查询的子查询是如何出现在一个表达式所返回的单个值可以出现的任何地方的。
3.8.1集合成员资格
SQL允许测试元组在关系中的成员资格。连接词in测试元组是否是集合中的成员,集合是由select子句产生的一组值构成的。连接词not in则测试元组是否不是集合中的成员。
作为示例,考虑查询“找出在2009年秋季和2010年春季学期同时开课的所有课程”。先前,我们通过对两个集合进行交运算来书写该查询,这两个集合分别是:2009年秋季开课的课程集合与2010年春季开课的课程集合。现在我们采用另一种方式,查找在2009年秋季开课的所有课程,看它们是否也是2010年春季开课的课程集合中的成员。很明显,这种方式得到的结果与前面相同,但我们可以用SQL中的in连接词书写该查询。我们从找出2010年春季开课的所有课程开始,写出子查询:
(select course_id
from section
where semester = ’Spring’ and year= 2010)
然后我们需要从子查询形成的课程集合中找出那些在2009年秋季开课的课程。为完成此项任务可将子查询嵌入外部查询的where子句中。最后的查询语句是:
select distinct 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);
该例说明了在SQL中可以用多种方法书写同一查询。这种灵活性是有好处的,因为它允许用户用最接近自然的方法去思考查询。我们将看到在SQL中有许多这样的冗余。
我们以与in结构类似的方式使用not in结构。例如,为了找出所有在2009年秋季学期开课,但不在2010年春季学期开课的课程,我们可写出:
select distinct 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);
in和not in操作符也能用于枚举集合。下面的查询找出既不叫“Mozart”,也不叫“Einstein”的教师的姓名:
select distinct name
from instructor
where name not in (’Mozart’, ’Einstein’);
在前面的例子中,我们是在单属性关系中测试成员资格。在SQL中测试任意关系的成员资格也是可以的。例如,我们可以这样来表达查询“找出(不同的)学生总数,他们选修了ID为10101的教师所讲授的课程段”:
select count (distinct ID)
from takes
where (course_id, sec_id, semester, year) in (select course_id, sec_id, semester, year
from teaches
where teaches.ID= 10101);
3.8.2集合的比较
作为一个说明嵌套子查询能够对集合进行比较的例子,考虑查询“找出满足下面条件的所有教师的姓名,他们的工资至少比Biology系某一个教师的工资要高”,在3.4.1节,我们将此查询写作:
select distinct T.name
from instructor as T, instructor as S
where T.salary> S.salary and S.dept_name = ’Biology’;
但是SQL提供另外一种方式书写上面的查询。短语“至少比某一个要大”在SQL中用>some表示。此结构允许我们用一种更贴近此查询的文字表达的形式重写上面的查询:
select name
from instructor
where salary> some (select salary
from instructor
where dept_name = ’Biology’);
子查询
(select salary
from instructor
where dept_name = ’Biology’)
产生Biology系所有教师的所有工资值的集合。当元组的salary值至少比Biology系教师的所有工资值集合中某一成员高时,外层select的where子句中>some的比较为真。
SQL也允许<some,<=some,>=some,=some和<>some的比较。作为练习,请验证=some等价于in,然而<>some并不等价于not in。在SQL中关键词any同义于some。早期SQL版本中仅允许使用any,后来的版本为了避免和英语中any一词在语言上的混淆,又添加了另一个可选择的关键词some。
现在我们稍微修改一下我们的查询。找出满足下面条件的所有教师的姓名,他们的工资值比Biology系每个教师的工资都高。结构>all对应于词组“比所有的都大”。使用该结构,我们写出查询如下:
select name
from instructor
where salary> all (select salary
from instructor
where dept_name = ’Biology’);
类似于some,SQL也允许<all,<=all,>=all,=all和<>all的比较。作为练习,请验证<>all等价于not in,但=all并不等价于in。
作为集合比较的另一个例子,考虑查询“找出平均工资最高的系”。我们首先写一个查询来找出每个系的平均工资,然后把它作为子查询嵌套在一个更大的查询中,以找出那些平均工资大于等于所有系平均工资的系。
select dept_name
from instructor
group by dept_name
having avg (salary) >= all (select avg (salary)
from instructor
group by dept_name);
3.8.3空关系测试
SQL还有一个特性可测试一个子查询的结果中是否存在元组。exists结构在作为参数的子查询非空时返回true值。使用exists结构,我们还能用另外一种方法书写查询“找出在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);
上述查询还说明了SQL的一个特性,来自外层查询的一个相关名称(上述查询中的S)可以用在where子句的子查询中。使用了来自外层查询相关名称的子查询被称作相关子查询(correlated subquery)。
在包含了子查询的查询中,在相关名称上可以应用作用域规则。根据此规则,在一个子查询中只能使用此子查询本身定义的,或者在包含此子查询的任何查询中定义的相关名称。如果一个相关名称既在子查询中定义,又在包含该子查询的查询中定义,则子查询中的定义有效。这条规则类似于编程语言中通用的变量作用域规则。
我们可以用not exists结构测试子查询结果集中是否不存在元组。我们可以使用not exists结构模拟集合包含(即超集)操作:我们可将“关系A包含关系B”写成“not exists(B except A)”。(尽管contains运算符并不是当前SQL标准的一部分,但这一运算符曾出现在某些早期的关系系统中。)为了说明not exists操作符,考虑查询“找出选修了Biology系开设的所有课程的学生”。使用except结构,我们可以书写此查询如下:
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));
这里,子查询
(select course_id
from course
where dept_name = ’Biology’)
找出Biology系开设的所有课程集合。子查询
(select T.course_id
from takes as T
where S.ID = T.ID)
找出S.ID选修的所有课程。这样,外层select对每个学生测试其选修的所有课程集合是否包含Biology系开设的所有课程集合。
3.8.4重复元组存在性测试
SQL提供一个布尔函数,用于测试在一个子查询的结果中是否存在重复元组。如果作为参数的子查询结果中没有重复的元组,unique结构此结构尚未被广泛实现。 将返回true值。我们可以用unique结构书写查询“找出所有在2009年最多开设一次的课程”,如下所示:
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);
注意如果某门课程不在2009年开设,那么子查询会返回一个空的结果,unique谓词在空集上计算出真值。
在不使用unique结构的情况下,上述查询的一种等价表达方式是:
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);
我们可以用not unique结构测试在一个子查询结果中是否存在重复元组。为了说明这一结构,考虑查询“找出所有在2009年最少开设两次的课程”,如下所示:
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);
形式化地,对一个关系的unique测试结果为假的定义是,当且仅当在关系中存在着两个元组t1和t2,且t1=t2。由于在t1或t2的某个域为空时,判断t1=t2为假,所以尽管一个元组有多个副本,只要该元组有一个属性为空,unique测试就有可能为真。
3.8.5from子句中的子查询
SQL允许在from子句中使用子查询表达式。在此采用的主要观点是:任何selectfromwhere表达式返回的结果都是关系,因而可以被插入到另一个selectfromwhere中任何关系可以出现的位置。
考虑查询“找出系平均工资超过42 000美元的那些系中教师的平均工资”。在3.7节我们使用了having子句来书写此查询。现在我们可以不用having子句来重写这个查询,而是通过如下这种在from子句中使用子查询的方式:
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子句,因为from子句中的子查询计算出了每个系的平均工资,早先在having子句中使用的谓词现在出现在外层查询的where子句中。
我们可以用as子句给此子查询的结果关系起个名字,并对属性进行重命名。如下所示:
select dept_name, avg_salary
from (select dept_name, avg (salary)
from instructor
group by dept_name)
as dept_avg (dept_name, avg_salary)
where avg_salary> 42000;
子查询的结果关系被命名为dept_avg,其属性名是dept_name和avg_salary。
很多(但并非全部)SQL实现都支持在from子句中嵌套子查询。请注意,某些SQL实现要求对每一个子查询结果关系都给一个名字,即使该名字从不被引用;Oracle允许对子查询结果关系命名(省略掉关键字as),但是不允许对关系中的属性重命名。
作为另一个例子,假设我们想要找出在所有系中工资总额最大的系。在此having子句是无能为力的,但我们可以用from子句中的子查询轻易地写出如下查询:
select max (tot_salary)
from (select dept_name, sum(salary)
from instructor
group by dept_name) as dept_total (dept_name, tot_salary);
我们注意到在from子句嵌套的子查询中不能使用来自from子句其他关系的相关变量。然而SQL:2003允许from子句中的子查询用关键词lateral作为前缀,以便访问from子句中在它前面的表或子查询中的属性。例如,如果我们想打印每位教师的姓名,以及他们的工资和所在系的平均工资,可书写查询如下:
select name, salary, avg_salary
from instructor I1, lateral (select avg(salary) as avg_salary
from instructor I2
where I2.dept_name= I1.dept_name);
没有lateral子句的话,子查询就不能访问来自外层查询的相关变量I1。目前只有少数SQL实现支持lateral子句,比如IBM DB2。
3.8.6with子句
with子句提供定义临时关系的方法,这个定义只对包含with子句的查询有效。考虑下面的查询,它找出具有最大预算值的系。
with max_budget (value) as
(select max(budget)
from department)
select budget
from department, max_budget
where department.budget = max_budget.value;
with子句定义了临时关系max_budget,此关系在随后的查询中马上被使用了。with子句是在SQL:1999中引入的,目前有许多(但并非所有)数据库系统都提供了支持。
我们也能用from子句或where子句中的嵌套子查询书写上述查询。但是,用嵌套子查询会使得查询语句晦涩难懂。with子句使查询在逻辑上更加清晰,它还允许在一个查询内的多个地方使用视图定义。
例如,假设我们要查出所有工资总额大于所有系平均工资总额的系,我们可以利用如下with子句写出查询:
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;
我们当然也可以不用with子句来建立等价的查询,但是那样会复杂很多,而且也不易看懂。作为练习,你可以把它转化为不用with子句的等价查询。
3.8.7标量子查询
SQL允许子查询出现在返回单个值的表达式能够出现的任何地方,只要该子查询只返回包含单个属性的单个元组;这样的子查询称为标量子查询(scalar subquery)。例如,一个子查询可以用到下面例子的select子句中,这个例子列出所有的系以及它们拥有的教师数:
select dept_name,
(select count(*)
from instructor
where department.dept_name = instructor.dept_name)
as num_instructors
from department;
上面例子中的子查询保证只返回单个值,因为它使用了不带group by的count(*)聚集函数。此例也说明了对相关变量的使用,即使用在外层查询的from子句中关系的属性,例如上例中的department.dept_name。
标量子查询可以出现在select、where和having子句中。也可以不使用聚集函数来定义标量子查询。在编译时并非总能判断一个子查询返回的结果中是否有多个元组,如果在子查询被执行后其结果中有不止一个元组,则产生一个运行时错误。
注意从技术上讲标量子查询的结果类型仍然是关系,尽管其中只包含单个元组。然而,当在表达式中使用标量子查询时,它出现的位置是单个值出现的地方,SQL就从该关系中包含单属性的单元组中取出相应的值,并返回该值。
3.9数据库的修改
目前为止我们的注意力集中在对数据库的信息抽取上。现在我们将展示如何用SQL来增加、删除和修改信息。
3.9.1删除
删除请求的表达与查询非常类似。我们只能删除整个元组,而不能只删除某些属性上的值。SQL用如下语句表示删除:
delete from r
where P;
其中P代表一个谓词,r代表一个关系。delete语句首先从r中找出所有使P(t)为真的元组t,然后把它们从r中删除。如果省略where子句,则r中所有元组将被删除。
注意delete命令只能作用于一个关系。如果我们想从多个关系中删除元组,必须在每个关系上使用一条delete命令。where子句中的谓词可以和select命令的where子句中的谓词一样复杂。在另一种极端情况下,where子句可以为空,请求
delete from instructor;
将删除instructor关系中的所有元组。instructor关系本身仍然存在,但它变成空的了。
下面是SQL删除请求的一些例子:
从instructor关系中删除与Finance系教师相关的所有元组。
delete from instructor
where dept_name= ’Finance’;
删除所有工资在13 000美元到15 000美元之间的教师。
delete from instructor
where salary between 13000 and 15000;
从instructor关系中删除所有这样的教师元组,他们在位于Watson大楼的系工作。
delete from instructor
where dept_name in (select dept_name
from department
where building = ’Watson’);
此delete请求首先找出所有位于Watson大楼的系,然后将属于这些系的instructor元组全部删除。
注意,虽然我们一次只能从一个关系中删除元组,但是通过在delete的where子句中嵌套selectfromwhere,我们可以引用任意数目的关系。delete请求可以包含嵌套的select,该select引用待删除元组的关系。例如,假设我们想删除工资低于大学平均工资的教师记录,可以写出如下语句:
delete from instructor
where salary < (select avg (salary)
from instructor);
该delete语句首先测试instructor关系中的每一个元组,检查其工资是否小于大学教师的平均工资。然后删除所有符合条件的元组,即所有低于平均工资的教师。在执行任何删除之前先进行所有元组的测试是至关重要的,因为若有些元组在其余元组未被测试前先被删除,则平均工资将会改变,这样delete的最后结果将依赖于元组被处理的顺序!
3.9.2插入
要往关系中插入数据,我们可以指定待插入的元组,或者写一条查询语句来生成待插入的元组集合。显然,待插入元组的属性值必须在相应属性的域中。同样,待插入元组的分量数也必须是正确的。
最简单的insert语句是单个元组的插入请求。假设我们想要插入的信息是Computer Science系开设的名为“Database Systems”的课程CS437,它有4个学分。我们可写成:
insert into course
values (’CS437’, ’Database Systems’, ’Comp.Sci.’, 4);
在此例中,元组属性值的排列顺序和关系模式中属性排列的顺序一致。考虑到用户可能不记得关系属性的排列顺序,SQL允许在insert语句中指定属性。例如,以下SQL insert语句与前述语句的功能相同。
insert into course (course_id, title, dept_name, credits)
values (’CS-437’, ’Database Systems’, ’Comp.Sci.’, 4);
insert into course (title, course_id, credits, dept_name)
values (’Database Systems’, ’CS-437’, 4, ’Comp.Sci.’);
更通常的情况是,我们可能想在查询结果的基础上插入元组。假设我们想让Music系每个修满144学分的学生成为Music系的教师,其工资为18 000美元。我们可写作:
insert into instructor
select ID, name, dept_name, 18000
from student
where dept_name = ’Music’ and tot_cred> 144;
和本节前面的例子不同的是,我们没有指定一个元组,而是用select选出一个元组集合。SQL先执行这条select语句,求出将要插入到instructor关系中的元组集合。每个元组都有ID、name、dept_name (Music)和工资(18 000美元)。
在执行插入之前先执行完select语句是非常重要的。如果在执行select语句的同时执行插入动作,如果在student上没有主码约束的话,像
insert into student
select*
from student;
这样的请求就可能会插入无数元组。如果没有主码约束,上述请求会重新插入student中的第一个元组,产生该元组的第二份拷贝。由于这个副本现在是student中的一部分,select语句可能找到它,于是第三份拷贝被插入到student中。第三份拷贝又可能被select语句发现,于是又插入第四份拷贝,如此等等,无限循环。在执行插入之前先完成select语句的执行可以避免这样的问题。这样,如果在student关系上没有主码约束,那么上述insert语句就只是把student关系中的每个元组都复制一遍。
在讨论insert语句时我们只考虑了这样的例子:待插入元组的每个属性都被赋了值。但是有可能待插入元组中只给出了模式中部分属性的值,那么其余属性将被赋空值,用null表示。考虑请求:
insert into student
values (’3003’, ’Green’, ’Finance’, null);
此请求所插入的元组代表了一个在Finance系、ID为“3003”的学生,但其tot_cred值是未知的。考虑查询:
select ID
from student
where tot_cred> 45;
既然“3003”号学生的tot_cred值未知,我们不能确定它是否大于45。
大部分关系数据库产品有特殊的“bulk loader”工具,它可以向关系中插入一个非常大的元组集合。这些工具允许从格式化文本文件中读出数据,且执行速度比同等目的的插入语句序列要快得多。
3.9.3更新
有些情况下,我们可能希望在不改变整个元组的情况下改变其部分属性的值。为达到这一目的,可以使用update语句。与使用insert﹑delete类似,待更新的元组可以用查询语句找到。
假设要进行年度工资增长,所有教师的工资将增长5%。我们写出:
update instructor
set salary= salary * 1.05;
上面的更新语句将在instructor关系的每个元组上执行一次。
如果只给那些工资低于70 000美元的教师涨工资,我们可以这样写:
update instructor
set salary = salary * 1.05
where salary < 70 000;
总之,update语句的where子句可以包含select语句的where子句中的任何合法结构(包括嵌套的select)。和insert、delete类似,update语句中嵌套的select可以引用待更新的关系。同样,SQL首先检查关系中的所有元组,看它们是否应该被更新,然后才执行更新。例如,请求“对工资低于平均数的教师涨5%的工资”可以写为如下形式:
update instructor
set salary = salary * 1.05
where salary < (select avg (salary)
from instructor);
我们现在假设给工资超过100 000美元的教师涨3%的工资,其余教师涨5%。我们可以写两条update语句:
update instructor
set salary = salary * 1.03
where salary> 100000;
update instructor
set salary = salary * 1.05
where salary <= 100000;
注意这两条update语句的顺序十分重要。假如我们改变这两条语句的顺序,工资略少于100 000美元的教师将增长8%的工资。
SQL提供case结构,我们可以利用它在一条update语句中执行前面的两种更新,避免更新次序引发的问题:
update instructor
set salary = case
when salary <= 100000 then salary * 1.05
else salary * 1.03
end
case语句的一般格式如下:
case
when pred1 then result1
when pred2 then result2
…
when predn then resultn
else result0
end
当i是第一个满足的pred1,pred2…predn时,此操作就会返回resulti;如果没有一个谓词可以满足,则返回result0。case语句可以用在任何应该出现值的地方。
标量子查询在SQL更新语句中也非常有用,它们可以用在set子句中。考虑这样一种更新:我们把每个student元组的tot_cred属性值设为该生成功学完的课程学分的总和。我们假设如果一个学生在某门课程上的成绩既不是’F’,也不是空,那么他成功学完了这门课程。我们需要使用set子句中的子查询来写出这种更新,如下所示:
update student S
set tot_cred = (
select sum(credits)
from takes natural join course
where S.ID= takes.ID and
takes.grade <> ’F’ and
takes.grade is not null);
注意子查询使用了来自update语句中的相关变量S。如果一个学生没有成功学完任何课程,上述更新语句将把其tot_cred属性值设为空。如果想把这样的属性值设为0的话,我们可以使用另一条update语句来把空值替换为0。更好的方案是把上述子查询中的“select sum(credits)”子句替换为如下使用case表达式的select子句:
select case
when sum(credits) is not null then sum(credits)
else 0
end
3.10总结
SQL是最有影响力的商用市场化的关系查询语言。SQL语言包括几个部分:
数据定义语言(DDL),它提供了定义关系模式、删除关系以及修改关系模式的命令。
数据操纵语言(DML),它包括查询语言,以及往数据库中插入元组、从数据库中删除元组和修改数据库中元组的命令。
SQL的数据定义语言用于创建具有特定模式的关系。除了声明关系属性的名称和类型之外,SQL还允许声明完整性约束,例如主码约束和外码约束。
SQL提供多种用于查询数据库的语言结构,其中包括select、from和where子句。SQL支持自然连接操作。
SQL还提供了对属性和关系重命名,以及对查询结果按特定属性进行排序的机制。
SQL支持关系上的基本集合运算,包括并、交和差运算,它们分别对应于数学集合论中的∪、∩和-运算。
SQL通过在通用真值true和false外增加真值“unknown”,来处理对包含空值的关系的查询。
SQL支持聚集,可以把关系进行分组,在每个分组上单独运用聚集。SQL还支持在分组上的集合运算。
SQL支持在外层查询的where和from子句中嵌套子查询。它还在一个表达式返回的单个值所允许出现的任何地方支持标量子查询。
SQL提供了用于更新、插入、删除信息的结构。
术语回顾
数据定义语言
数据操纵语言
数据库模式
数据库实例
关系模式
关系实例
主码
外码
参照关系
被参照关系
空值
查询语言
SQL查询结构
select子句
from子句
where子句
自然连接运算
as子句
order by子句
相关名称(相关变量,元组变量)
集合运算
union
intersect
except
空值
真值“unknown”
聚集函数
avg, min, max, sum, count
group by
having
嵌套子查询
集合比较
{<,<=,>,>=} {some, all}
exists
unique
lateral子句
with子句
标量子查询
数据库修改
删除
插入
更新
实践习题
3.1使用大学模式,用SQL写出如下查询。(建议在一个数据库上实际运行这些查询,使用我们在本书的Web网站dbbook.com上提供的样本数据,上述网站还提供了如何建立一个数据库和加载样本数据的说明。)
a.找出Comp.Sci.系开设的具有3个学分的课程名称。
b.找出名叫Einstein的教师所教的所有学生的标识,保证结果中没有重复。
c.找出教师的最高工资。
d.找出工资最高的所有教师(可能有不止一位教师具有相同的工资)。
e.找出2009年秋季开设的每个课程段的选课人数。
f.从2009年秋季开设的所有课程段中,找出最多的选课人数。
g.找出在2009年秋季拥有最多选课人数的课程段。
3.2假设给你一个关系grade_points(grad_e, points),它提供从takes关系中用字母表示的成绩等级到数字表示的得分之间的转换。例如,“A”等级可指定为对应于4分,“A-”对应于3.7分,“B+”对应于3.3分,“B”对应于3分,等等。学生在某门课程(课程段)上所获得的等级分值被定义为该课程段的学分乘以该生得到的成绩等级所对应的数字表示的得分。
给定上述关系和我们的大学模式,用SQL写出下面的每个查询。为简单起见,可以假设没有任何takes元组在grade上取null值。
a.根据ID为12345的学生所选修的所有课程,找出该生所获得的等级分值的总和。
b.找出上述学生等级分值的平均值(GPA),即用等级分值的总和除以相关课程学分的总和。
c.找出每个学生的ID和等级分值的平均值。
3.3使用大学模式,用SQL写出如下插入、删除和更新语句。
a.给Comp.Sci.系的每位教师涨10%的工资。
b.删除所有未开设过(即没有出现在section关系中)的课程。
c.把每个在tot_cred属性上取值超过100的学生作为同系的教师插入,工资为10 000美元。
3.4考虑图318中的保险公司数据库,其中加下划线的是主码。为这个关系数据库构造出如下SQL查询:
a.找出2009年其车辆出过交通事故的人员总数。
b.向数据库中增加一个新的事故,对每个必需的属性可以设定任意值。
c.删除“John Smith”拥有的马自达车(Mazda)。
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)
图318习题3.4和习题3.14的保险公司数据库
3.5假设有关系marks(ID, score),我们希望基于如下标准为学生评定等级:如果score<40得F;如果40≤score<60得C;如果60≤score<80得B;如果80≤score得A。写出SQL查询完成下列操作:
a.基于marks关系显示每个学生的等级。
b.找出各等级的学生数。
3.6SQL的like运算符是大小写敏感的,但字符串上的lower()函数可用来实现大小写不敏感的匹配。为了说明是怎么用的,写出这样一个查询:找出名称中包含了“sci”子串的系,忽略大小写。
3.7考虑SQL查询
select distinct p.a1
from p, r1, r2
where p.a1=r1.a1 or p.a1=r2.a1
在什么条件下这个查询选择的p.a1值要么在r1中,要么在r2中?仔细考察r1或r2可能为空的情况。
3.8考虑图319中的银行数据库,其中加下划线的是主码。为这个关系数据库构造出如下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, loan_number)
account (account_number, branch_name, balance )
depositor (customer_name, account_number)
图319习题3.8和习题3.15的银行数据库
3.9考虑图320的雇员数据库,其中加下划线的是主码。为下面每个查询写出SQL表达式:
a.找出所有为“First Bank Corporation”工作的雇员名字及其居住城市。
b.找出所有为“First Bank Corporation”工作且薪金超过10 000美元的雇员名字、居住街道和城市。
c.找出数据库中所有不为“First Bank Corporation”工作的雇员。
d.找出数据库中工资高于“Small Bank Corporation”的每个雇员的所有雇员。
e.假设一个公司可以在好几个城市有分部。找出位于“Small Bank Corporation”所有所在城市的所有公司。
f.找出雇员最多的公司。
g.找出平均工资高于“First Bank Corporation”平均工资的那些公司。
employee(employee_name, street, city)
works(employee_name, company_name, salary)
company(company_name, city)
managers(employee_name, manager_name)
图320习题3.9、习题3.10、习题3.16、习题3.17和习题3.20的雇员数据库
3.10考虑图320的关系数据库,给出下面每个查询的SQL表达式:
a.修改数据库使“Jones”现在居住在“Newtown”市。
b.为“First Bank Corporation”所有工资不超过100 000美元的经理增长10%的工资,对工资超过100 000美元的只增长3%。
习题
3.11使用大学模式,用SQL写出如下查询。
a.找出所有至少选修了一门Comp.Sci.课程的学生姓名,保证结果中没有重复的姓名。
b.找出所有没有选修在2009年春季之前开设的任何课程的学生的ID和姓名。
c.找出每个系教师的最高工资值。可以假设每个系至少有一位教师。
d.从前述查询所计算出的每个系最高工资中选出最低值。
3.12使用大学模式,用SQL写出如下查询。
a.创建一门课程“CS-001”,其名称为“Weekly Seminar”,学分为0。
b.创建该课程在2009年秋季的一个课程段,sec_id为1。
c.让Comp.Sci.系的每个学生都选修上述课程段。
d.删除名为Chavez的学生选修上述课程段的信息。
e.删除课程CS001。如果在运行此删除语句之前,没有先删除这门课程的授课信息(课程段),会发生什么事情?
f.删除课程名称中包含“database”的任意课程的任意课程段所对应的所有takes元组,在课程名的匹配中忽略大小写。
3.13写出对应于图318中模式的SQL DDL。在数据类型上做合理的假设,确保声明主码和外码。
3.14考虑图318中的保险公司数据库,其中加下划线的是主码。对这个关系数据库构造如下的SQL查询:
a.找出和“John Smith”的车有关的交通事故数量。
b.对事故报告编号为“AR2197”中的车牌是“AABB2000”的车辆损坏保险费用更新到3000美元。
3.15考虑图319中的银行数据库,其中加下划线的是主码。为这个关系数据库构造出如下SQL查询:
a.找出在“Brooklyn”的所有支行都有账户的所有客户。
b.找出银行的所有贷款额的总和。
c.找出总资产至少比位于Brooklyn的某一家支行要多的所有支行名字。
3.16考虑图320中的雇员数据库,其中加下划线的是主码。给出下面每个查询对应的SQL表达式:
a.找出所有为“First Bank Corporation”工作的雇员名字。
b.找出数据库中所有居住城市和公司所在城市相同的雇员。
c.找出数据库中所有居住的街道和城市与其经理相同的雇员。
d.找出工资高于其所在公司雇员平均工资的所有雇员。
e.找出工资总和最小的公司。
3.17考虑图320中的关系数据库。给出下面每个查询对应的SQL表达式:
a.为“First Bank Corporation”的所有雇员增长10%的工资。
b.为“First Bank Corporation”的所有经理增长10%的工资。
c.删除“Small Bank Corporation”的雇员在works关系中的所有元组。
3.18列出两个原因,说明为什么空值可能被引入到数据库中。
3.19证明在SQL中,<>all等价于not in。
3.20给出图320中雇员数据库的SQL模式定义。为每个属性选择合适的域,并为每个关系模式选择合适的主码。
3.21考虑图321中的图书馆数据库。用SQL写出如下查询:
a.打印借阅了任意由“McGrawHill”出版的书的会员名字。
b.打印借阅了所有由“McGrawHill”出版的书的会员名字。
c.对于每个出版商,打印借阅了多于五本由该出版商出版的书的会员名字。
d.打印每位会员借阅书籍数量的平均值。考虑这样的情况:如果某会员没有借阅任何书籍,那么该会员根本不会出现在borrowed关系中。
member(memb_no, name, age)
book(isbn, title, authors, publisher)
borrowed(memb_no, isbn, date)
图321习题3.21的图书馆数据库
3.22不使用unique结构,重写下面的where子句:
where unique (select title from course)
3.23考虑查询
select course_id, semester, year, sec_id, avg (tot_cred)
from takes natural join student
where year = 2009
group by course_id, semester, year, sec_id
having count (ID) >= 2;
解释为什么在from子句中还加上与section的连接不会改变查询结果。
3.24考虑查询
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;
不使用with结构,重写此查询。
工具
很多关系数据库系统可以从市场上购得,包括IBM DB2、IBM Informix、Oracle、Sybase,以及微软的SQL Server。另外还有几个数据库系统可以从网上下载并免费使用,包括PostgreSQL、MySQL(除几种特定的商业化使用外是免费的)和Oracle Express edition。
大多数数据库系统提供了命令行界面,用于提交SQL命令。此外,大多数数据库还提供了图形化的用户界面(GUI),它们简化了浏览数据库、创建和提交查询,以及管理数据库的任务。还有商品化的IDE,用于在多个数据库平台上运行SQL,包括Embarcadero的RAD Studio与Aqua Data Studio。
pgAdmin工具为PostgreSQL提供了GUI功能;phpMyAdmin为MySQL提供了GUI功能。NetBeans IDE提供了一个GUI前端,可以与很多不同的数据库交互,但其功能有限;Eclipse IDE通过几种不同插件支持类似的功能,这些插件包括Data Tools Platform(DTP)和JBuilder。
本书的Web网站dbbook.com提供了SQL模式定义和大学模式的样本数据。该Web网站还提供了如何建立和访问一些流行的数据库系统的说明。本章讨论的SQL结构是SQL标准的一部分,但一些特征可能没被某些数据库所支持。Web网站上列出了这些不相容的特征,在这些数据库上执行查询时需要多加考虑。
文献注解
SQL的最早版本Sequel 2由Chamberlin等[1976]描述。Sequel 2是从Square语言(Boyce等[1975]以及Chamberlin 和 Boyce [1974])派生出来的。美国国家标准SQL86在ANSI [1986]中描述。IBM系统应用体系结构对SQL的定义由IBM[1987]给出。SQL89和SQL92官方标准可分别从ANSI[1989]和ANSI[1992]获得。
介绍SQL92语言的教材包括Date 和 Darwen[1997]、Melton 和 Simon[1993]以及Cannan 和 Otten[1993]。Date和Darwen[1997]以及Date[1993a]都包含了从编程语言角度对SQL92的评论。
介绍SQL:1999的教程包括Melton和Simon[2001]以及Melton[2002]。Eisenberg 和 Melton[1999]提供了对SQL:1999的概览。Donahoo 和 Speegle[2005]从开发者的角度介绍了SQL。Eisenberg等[2004]提供了对SQL:2003的概览。
SQL:1999、SQL:2003、SQL:2006和SQL:2008标准都是作为ISO/IEC标准文档集被发布的。标准文档中塞满了大量的信息,非常难以阅读,主要用于数据库系统的实现。标准文档可以从Web网站http://webstore.ansi.org上购买。
很多数据库产品支持标准以外的SQL特性,还可能不支持标准中的某些特性。关于这些特性的更多信息可在各产品的SQL用户手册中找到。
SQL查询的处理,包括算法和性能等问题,将在第11章讨论。关于这些问题的参考文献也在那里。
正如以上所列举的,数据库已经成为当今几乎所有企业不可默认的组成部分,它不仅存储大多数企业都有的普通的信息,也存储各类企业特有的信息。
在20世纪最后的40年中,数据库的使用在所有的企业中都有所增长。在早期,很少有人直接和数据库系统打交道,尽管没有意识到这一点,他们还是与数据库间接地打着交道,比如,通过打印的报表(如信用卡的对账单)或者通过代理(如银行的出纳员和机票预订代理等)与数据库打交道。自动取款机的出现,使用户可以直接和数据库进行交互。计算机的电话界面(交互式语音应答系统)也使得用户可以直接和数据库进行交互,呼叫者可以通过拨号和按电话键来输入信息或选择可选项,来找出如航班的起降时间或注册大学的课程等。
20世纪90年代末的互联网革命急剧地增加了用户对数据库的直接访问。很多组织将他们的访问数据库的电话界面改为Web界面,并提供了大量的在线服务和信息。比如,当你访问一家在线书店,浏览一本书或一个音乐集时,其实你正在访问存储在某个数据库中的数据。当你确认了一个网上订购,你的订单也就保存在了某个数据库中。当你访问一个银行网站,检索你的账户余额和交易信息时,这些信息也是从银行的数据库系统中取出来的。当你访问一个网站时,关于你的一些信息可能会从某个数据库中取出,并且选择出那些适合显示给你的广告。此外,关于你访问网络的数据也可能会存储在一个数据库中。
因此,尽管用户界面隐藏了访问数据库的细节,大多数人甚至没有意识到他们正在和一个数据库打交道,然而访问数据库已经成为当今几乎每个人生活中不可默认的组成部分。
也可以从另一个角度来评判数据库系统的重要性。如今,像Oracle这样的数据库系统厂商是世界上最大的软件公司之一,并且在微软和IBM等这些有多样化产品的公司中,数据库系统也是其产品线的一个重要组成部分。
1.2数据库系统的目标
数据库系统作为商业数据计算机化管理的早期方法而产生。作为20世纪60年代这类方法的典型实例之一,考虑大学组织中的一个部分,除其他数据外,需要保存关于所有教师、学生、系和开设课程的信息。在计算机中保存这些信息的一种方法是将它们存放在操作系统文件中。为了使用户可以对信息进行操作,系统中应有一些对文件进行操作的应用程序,包括:
增加新的学生、教师和课程。
为课程注册学生,并产生班级花名册。
为学生填写成绩、计算绩点(GPA)、产生成绩单。
这些应用程序是由系统程序员根据大学的需求编写的。
随着需求的增长,新的应用程序被加入到系统中。例如,某大学决定创建一个新的专业(例如,计算机科学),那么这个大学就要建立一个新的系并创建新的永久性文件(或在现有文件中添加信息)来记录关于这个系中所有的教师、这个专业的所有学生、开设的课程、学位条件等信息。进而就有可能需要编写新的应用程序来处理这个新专业的特殊规则。也可能会需要编写新的应用程序来处理大学中的新规则。因此,随着时间的推移,越来越多的文件和应用程序就会加入到系统中。
以上所描述的典型的文件处理系统(fileprocessing system)是传统的操作系统所支持的。永久记录被存储在多个不同的文件中,人们编写不同的应用程序来将记录从有关文件中取出或加入到适当的文件中。在数据库系统(DBS)出现以前,各个组织通常都采用这样的系统来存储信息。
在文件处理系统中存储组织信息的主要弊端包括:
数据的冗余和不一致(data redundancy and inconsistency)。由于文件和程序是在很长的一段时间内由不同的程序员创建的,不同文件可能有不同的结构,不同程序可能采用不同的程序设计语言写成。此外,相同的信息可能在几个地方(文件)重复存储。例如,如果某学生有两个专业(例如,音乐和数学),该学生的地址和电话号码就可能既出现在包含音乐系学生记录的文件中,又出现在包含数学系学生记录的文件中。这种冗余除了导致存储和访问开销增大外,还可能导致数据不一致性(data inconsistency),即同一数据的不同副本不一致。例如,学生地址的更改可能在音乐系记录中得到反映而在系统的其他地方却没有。
数据访问困难(difficulty in accessing data)。假设大学的某个办事人员需要找出居住在某个特定邮编地区的所有学生的姓名,于是他要求数据处理部门生成这样的一个列表。由于原始系统的设计者并未预料到会有这样的需求,因此没有现成的应用程序去满足这个需求。但是,系统中却有一个产生所有学生列表的应用程序。这时该办事人员有两种选择:一种是取得所有学生的列表并从中手工提取所需信息,另一种是要求数据处理部门让某个程序员编写相应的应用程序。这两种方案显然都不太令人满意。假设编写了相应的程序,几天以后这个办事人员可能又需要将该列表减少到只列出至少选课60学时的那些学生。可以预见,产生这样一个列表的程序又不存在,这个职员就再次面临前面那两种都不尽如人意的选择。
这里需要指出的是,传统的文件处理环境不支持以一种方便而高效的方式去获取所需数据。我们需要开发通用的、能对变化的需求做出更快反应的数据检索系统。
数据孤立(data isolation)。由于数据分散在不同文件中,这些文件又可能具有不同的格式,因此编写新的应用程序来检索适当数据是很困难的。
完整性问题(integrity problem)。数据库中所存储数据的值必须满足某些特定的一致性约束(consistency constraint)。假设大学为每个系维护一个账户,并且记录各个账户的余额。我们还假设大学要求每个系的账户余额永远不能低于零。开发者通过在各种不同应用程序中加入适当的代码来强制系统中的这些约束。然而,当新的约束加入时,很难通过修改程序来体现这些新的约束。尤其是当约束涉及不同文件中的多个数据项时,问题就变得更加复杂了。
原子性问题(atomicity problem)。如同任何别的设备一样,计算机系统也会发生故障。一旦故障发生,数据就应被恢复到故障发生以前的一致的状态,
对很多应用来说,这样的保证是至关重要的。让我们看看把A系的账户余额中的500美元转入B系的账户余额中的这样一个程序。假设在程序的执行过程中发生了系统故障,很可能A系的余额中减去的500美元还没来得及存入B系的余额中,这就造成了数据库状态的不一致。显然,为了保证数据库的一致性,这里的借和贷两个操作必须是要么都发生,要么都不发生。也就是说,转账这个操作必须是原子的——它要么全部发生要么根本不发生。在传统的文件处理系统中,保持原子性是很难做到的。
并发访问异常(concurrentaccess anomaly)。为了提高系统的总体性能以及加快响应速度,许多系统允许多个用户同时更新数据。实际上,如今最大的互联网零售商每天就可能有来自购买者对其数据的数百万次访问。在这样的环境中,并发的更新操作可能相互影响,有可能导致数据的不一致。设A系的账户余额中有10 000美元,假如系里的两个职员几乎同时从系的账户中取款(例如分别取出500美元和100美元),这样的并发执行就可能使账户处于一种错误的(或不一致的)状态。假设每个取款操作对应执行的程序是读取原始账户余额,在其上减去取款的金额,然后将结果写回。如果两次取款的程序并发执行,可能它们读到的余额都是10 000美元,并将分别写回9500美元和9900美元。A系的账户余额中到底剩下9500美元还是9900美元视哪个程序后写回结果而定,而实际上正确的值应该是9400美元。为了消除这种情况发生的可能性,系统必须进行某种形式的管理。但是,由于数据可能被多个不同的应用程序访问,这些程序相互间事先又没有协调,管理就很难进行。
作为另一个例子,假设为确保注册一门课程的学生人数不超过上限,注册程序维护一个注册了某门课程的学生计数。当一个学生注册时,该程序读入这门课程的当前计数值,核实该计数还没有达到上限,给计数值加1,将计数存回数据库。假设两个学生同时注册,而此时的计数值是(例如)39。尽管两个学生都成功地注册了这门课程,计数值应该是41,然而两个程序执行可能都读到值39,然后都写回值40,导致不正确地只增加了1个注册人数。此外,假设该课程注册人数的上限是40;在上面的例子中,两个学生都成功注册,就导致违反了40个学生为注册上限的规定。
安全性问题(security problem)。并非数据库系统的所有用户都可以访问所有数据。例如在大学中,工资发放人员只需要看到数据库中关于财务信息的那个部分。他们不需要访问关于学术记录的信息。但是,由于应用程序总是即席地加入到文件处理系统中来,这样的安全性约束难以实现。
以上问题以及一些其他问题,促进了数据库系统的发展。接下来我们将看一看数据库系统为了解决上述在文件处理系统中存在的问题而提出的概念和算法。在本书的大部分篇幅中,我们在讨论典型的数据处理应用时总以大学作为实例。
1.3数据视图
数据库系统是一些互相关联的数据以及一组使得用户可以访问和修改这些数据的程序的集合。数据库系统的一个主要目的是给用户提供数据的抽象视图,也就是说,系统隐藏关于数据存储和维护的某些细节。
1.3.1数据抽象
一个可用的系统必须能高效地检索数据。这种高效性的需求促使设计者在数据库中使用复杂的数据结构来表示数据。由于许多数据库系统的用户并未受过计算机专业训练,系统开发人员通过如下几个层次上的抽象来对用户屏蔽复杂性,以简化用户与系统的交互:
物理层(physical level)。最低层次的抽象,描述数据实际上是怎样存储的。物理层详细描述复杂的底层数据结构。
逻辑层(logical level)。比物理层层次稍高的抽象,描述数据库中存储什么数据及这些数据间存在什么关系。这样逻辑层就通过少量相对简单的结构描述了整个数据库。虽然逻辑层的简单结构的实现可能涉及复杂的物理层结构,但逻辑层的用户不必知道这样的复杂性。这称作物理数据独立性(physical data independence)。数据库管理员使用抽象的逻辑层,他必须确定数据库中应该保存哪些信息。
视图层(view level)。最高层次的抽象,只描述整个数据库的某个部分。尽管在逻辑层使用了比较简单的结构,但由于一个大型数据库中所存信息的多样性,仍存在一定程度的复杂性。数据库系统的很多用户并不需要关心所有的信息,
图11数据抽象的三个层次
而只需要访问数据库的一部分。视图层抽象的定义正是为了使这样的用户与系统的交互更简单。系统可以为同一数据库提供多个视图。
这三层抽象的相互关系如图11所示。
通过与程序设计语言中数据类型的概念进行类比,我们可以弄清各层抽象间的区别。大多数高级程序设计语言支持结构化类型的概念。例如,我们可以定义如下记录实际的类型说明依赖于所使用的语言。C和C++使用struct说明。Java没有这样的说明,而可以定义一个简单的类来起到同样的作用。 :
type instructor = record
ID: char(5);
name: char(20);
dept_name: char(20);
salary: numeric(8,2);
end;
以上代码定义了一个具有四个字段的新记录instructor。每个字段有一个字段名和所属类型。对一个大学来说,可能包括几个这样的记录类型:
department,包含字段dept_name、building和budget。
course,包含字段course_id、 title、 dept_name 和credits。
student、 包含字段ID、 name、 dept_name 和tot_cred。
在物理层,一个instructor、department或student记录可能被描述为连续存储位置组成的存储块。
编译器为程序设计人员屏蔽了这一层的细节。与此类似,数据库系统为数据库程序设计人员屏蔽了许多最底层的存储细节。而数据库管理员可能需要了解数据物理组织的某些细节。
在逻辑层,每个这样的记录通过类型定义进行描述,正如前面的代码段所示。在逻辑层上同时还要定义这些记录类型的相互关系。程序设计人员正是在这个抽象层次上使用某种程序设计语言进行工作。与此类似,数据库管理员常常在这个抽象层次上工作。
最后,在视图层,计算机用户看见的是为其屏蔽了数据类型细节的一组应用程序。与此类似,视图层上定义了数据库的多个视图,数据库用户看到的是这些视图。除了屏蔽数据库的逻辑层细节以外,视图还提供了防止用户访问数据库的某些部分的安全性机制。例如,大学注册办公室的职员只能看见数据库中关于学生的那部分信息,而不能访问涉及教师工资的信息。
1.3.2实例和模式
随着时间的推移,信息会被插入或删除,数据库也就发生了改变。特定时刻存储在数据库中的信息的集合称作数据库的一个实例(instance)。而数据库的总体设计称作数据库模式(schema)。数据库模式即使发生变化,也不频繁。
数据库模式和实例的概念可以通过与用程序设计语言写出的程序进行类比来理解。数据库模式对应于程序设计语言中的变量声明(以及与之关联的类型的定义)。每个变量在特定的时刻会有特定的值,程序中变量在某一时刻的值对应于数据库模式的一个实例。
根据前面我们所讨论的不同的抽象层次,数据库系统可以分为几种不同的模式。物理模式(physical schema)在物理层描述数据库的设计,而逻辑模式(logical schema)则在逻辑层描述数据库的设计。数据库在视图层也可以有几种模式,有时称为子模式(subschema),它描述了数据库的不同视图。
在这些模式中,因为程序员使用逻辑模式来构造数据库应用程序,从其对应用程序的效果来看,逻辑模式是目前最重要的一种模式。物理模式隐藏在逻辑模式下,并且通常可以在应用程序丝毫不受影响的情况下被轻易地更改。应用程序如果不依赖于物理模式,它们就被称为是具有物理数据独立性(physical data independence),因此即使物理模式改变了它们也无需重写。
在介绍完下一节数据模型的概念后,我们将学习描述模式的语言。
1.3.3数据模型
数据库结构的基础是数据模型(data model)。数据模型是一个描述数据、数据联系、数据语义以及一致性约束的概念工具的集合。数据模型提供了一种描述物理层、逻辑层以及视图层数据库设计的方式。
下文中,我们将提到几种不同的数据模型。数据模型可被划分为四类:
关系模型(relational model)。关系模型用表的集合来表示数据和数据间的联系。每个表有多个列,每列有唯一的列名。关系模型是基于记录的模型的一种。基于记录的模型的名称的由来是因为数据库是由若干种固定格式的记录来构成的。每个表包含某种特定类型的记录。每个记录类型定义了固定数目的字段(或属性)。表的列对应于记录类型的属性。关系数据模型是使用最广泛的数据模型,当今大量的数据库系统都基于这种关系模型。第2~8章将详细介绍关系模型。
实体-联系模型(entityrelationship model)。实体-联系(ER)数据模型基于对现实世界的这样一种认识:现实世界由一组称作实体的基本对象以及这些对象间的联系构成。实体是现实世界中可区别于其他对象的一件“事情”或一个“物体”。实体-联系模型被广泛用于数据库设计。第7章将详细探讨该模型。
基于对象的数据模型(objectbased data model)。面向对象的程序设计(特别是Java、C++或C#)已经成为占主导地位的软件开发方法。这导致面向对象数据模型的发展,面向对象的数据模型可以看成是ER模型增加了封装、方法(函数)和对象标识等概念后的扩展。对象-关系数据模型结合了面向对象的数据模型和关系数据模型的特征。第14章将讲述对象-关系数据模型。
半结构化数据模型(semistructured data model)。半结构化数据模型允许那些相同类型的数据项含有不同的属性集的数据定义。这和早先提到的数据模型形成了对比:在那些数据模型中所有某种特定类型的数据项必须有相同的属性集。可扩展标记语言(eXtensible Markup Language,XML)被广泛地用来表示半结构化数据。这将在第15章中详述。
在历史上,网状数据模型(network data model)和层次数据模型(hierarchical data model)先于关系数据模型出现。这些模型和底层的实现联系很紧密,并且使数据建模复杂化。因此,除了在某些地方仍在使用的旧数据库中之外,如今它们已经很少被使用了。
1.4数据库语言
数据库系统提供数据定义语言(datadefinition language)来定义数据库模式,以及数据操纵语言(datamanipulation language)来表达数据库的查询和更新。而实际上,数据定义和数据操纵语言并不是两种分离的语言,相反地,它们简单地构成了单一的数据库语言(如广泛使用的SQL语言)的不同部分。
1.4.1数据操纵语言
数据操纵语言(DataManipulation Language,DML)是这样一种语言,它使得用户可以访问或操纵那些按照某种适当的数据模型组织起来的数据。有以下访问类型:
对存储在数据库中的信息进行检索。
向数据库中插入新的信息。
从数据库中删除信息。
修改数据库中存储的信息。
通常有两类基本的数据操纵语言:
过程化DML(procedural DML)要求用户指定需要什么数据以及如何获得这些数据。
声明式DML(declarative DML)(也称为非过程化DML)只要求用户指定需要什么数据,而不指明如何获得这些数据。
通常声明式DML比过程化DML易学易用。但是,由于用户不必指明如何获得数据,数据库系统必须找出一种访问数据的高效途径。
查询(query)是要求对信息进行检索的语句。DML中涉及信息检索的部分称作查询语言(query language)。实践中常把查询语言和数据操纵语言作为同义词使用,尽管从技术上来说这并不正确。
目前有很多商业性的或者实验性的数据库查询语言,我们在第3章、第4章和第5章中将学习最广泛使用的查询语言SQL。我们还会在第6章中学习一些其他的查询语言。
我们在1.3节讨论的抽象层次不仅可以用于定义或构造数据,而且还可以用于操纵数据。在物理层,我们必须定义可高效访问数据的算法;在更高的抽象层次,我们则强调易用性。目标是使人们能够更有效地和系统交互。数据库系统的查询处理器部件(我们将在第11章学习)将DML的查询语句翻译成数据库系统物理层的动作序列。
1.4.2数据定义语言
数据库模式是通过一系列定义来说明的,这些定义由一种称作数据定义语言(DataDefinition Language,DDL)的特殊语言来表达。DDL也可用于定义数据的其他特征。
数据库系统所使用的存储结构和访问方式是通过一系列特殊的DDL语句来说明的,这种特殊的DDL称作数据存储和定义(data storage and definition)语言。这些语句定义了数据库模式的实现细节,而这些细节对用户来说通常是不可见的。
存储在数据库中的数据值必须满足某些一致性约束(consistency constraint)。例如,假设大学要求一个系的账户余额必须不能为负值。DDL语言提供了指定这种约束的工具。每当数据库被更新时,数据库系统都会检查这些约束。通常,约束可以是关于数据库的任意谓词。然而,如果要测试任意谓词,可能代价比较高。因此,数据库系统实现可以以最小代价测试的完整性约束。
域约束(domain constraint)。每个属性都必须对应于一个所有可能的取值构成的域(例如,整数型、字符型、日期/时间型)。声明一种属性属于某种具体的域就相当于约束它可以取的值。域约束是完整性约束的最基本形式。每当有新数据项插入到数据库中,系统就能方便地进行域约束检测。
参照完整性(referential integrity)。我们常常希望,一个关系中给定属性集上的取值也在另一关系的某一属性集的取值中出现(参照完整性)。例如,每门课程所列出的系必须是实际存在的系。更准确地说,一个course记录中的dept_name值必须出现在department关系中的某个记录的dept_name属性中。数据库的修改会导致参照完整性的破坏。当参照完整性约束被违反时,通常的处理是拒绝执行导致完整性被破坏的操作。
断言(assertion)。一个断言就是数据库需要时刻满足的某一条件。域约束和参照完整性约束是断言的特殊形式。然而,还有许多约束不能仅用这几种特殊形式表达。例如,“每一学期每一个系必须至少开设5门课程”,必须表达成一个断言。断言创建以后,系统会检测其有效性。如果断言有效,则以后只有不破坏断言的数据库更新才被允许。
授权(authorization)。我们也许想对用户加以区别,对于不同的用户在数据库中的不同数据值上允许不同的访问类型。这些区别以授权来表达,最常见的是:读权限(read authorization),允许读取数据,但不能修改数据;插入权限(insert authorization),允许插入新数据,但不允许修改已有数据;更新权限(update authorization),允许修改,但不能删除数据;删除权限(delete authorization),允许删除数据。我们可以赋予用户所有的权限,或者没有或部分拥有这些权限。
正如其他任何程序设计语言一样,DDL以一些指令(语句)作为输入,生成一些输出。DDL的输出放在数据字典(data dictionary)中,数据字典包含了元数据(metadata),元数据是关于数据的数据。可把数据字典看作一种特殊的表,这种表只能由数据库系统本身(不是常规的用户)来访问和修改。在读取和修改实际的数据前,数据库系统先要参考数据字典。
图12关系数据库的一个实例
1.5关系数据库
关系数据库基于关系模型,使用一系列表来表达数据以及这些数据之间的联系。关系数据库也包括DML和DDL。在第2章中,我们简单介绍关系模型的基本概念。多数的商用关系数据库系统使用SQL语言,该语言将在第3章、第4章和第5章中详细介绍。第6章我们将讨论其他有影响的语言。
1.5.1表
每个表有多个列,每个列有唯一的名字。图12展示了一个关系数据库示例,它由两个表组成:其一给出大学教师的细节,其二给出大学中各个系的细节。
第一个表是instructor表,表示,例如,ID为22222的名叫Einstein的教师是物理系的成员,他的年薪为95 000美元。第二个表是department表,表示,例如,生物系在Watson大楼,经费预算为90 000美元。当然,一个现实世界的大学会有更多的系和教师。在本书中,我们使用小型的表来描述概念。相同模式的更大型的例子可以在联机的版本中得到。
关系模型是基于记录的模型的一个实例。基于记录的模型,之所以有此称谓,是因为数据库的结构是几种固定格式的记录。每个表包含一种特定类型的记录。每种记录类型定义固定数目的字段或属性。表的列对应记录类型的属性。
不难看出,表可以如何存储在文件中。例如,一个特殊的字符(比如逗号)可以用来分隔记录的不同属性,另一特殊的字符(比如换行符)可以用来分隔记录。对于数据库的开发者和用户,关系模型屏蔽了这些低层实现细节。
我们也注意到,在关系模型中,有可能创建一些有问题的模式,比如出现不必要的冗余信息。例如,假设我们把系的budget存储为instructor记录的一个属性。那么,每当一个经费预算的值(例如,物理系的经费预算)发生变化时,这个变化必须被反映在与物理系相关联的所有教员记录中。在第8章,我们将研究如何区分好的和不好的模式设计。
1.5.2数据操纵语言
SQL查询语言是非过程化的。它以几个表作为输入(也可能只有一个),总是仅返回一个表。下面是一个SQL查询的例子,它找出历史系的所有教员的名字:
select instructor.name
from instructor
where instructor.dept_name = ‘History’;
这个查询指定了从instructor表中要取回的是dept_name为History的那些行,并且这些行的name属性要显示出来。更具体点,执行本查询的结果是一个表,它有一列name和若干行,每一行都是dept_name为 History的一个教员的名字。如果这个查询运行在图12中的表上,那么结果将有两行,一个是名字El Said,另一个是名字Califieri。
查询可以涉及来自不止一个表的信息。例如,下面的查询将找出与经费预算超过95 000美元的系相关联的所有教员的ID和系名。
select instructor.ID,department.dept_name
from instructor, department
where instructor.dept_name=department.dept_name and
department.budget> 95000;
如果上述查询运行在图12中的表上,那么系统将会发现,有两个系的经费预算超过95 000美元——计算机科学系和金融系;这些系里有5位教员。于是,结果将由一个表组成,这个表有两列(ID,dept_name)和五行(12121, Finance)、(45565, Computer Science)、(10101, Computer Science)、(83821, Computer Science)、(76543, Finance)。
1.5.3数据定义语言
SQL提供了一个丰富的DDL语言,通过它,我们可以定义表、完整性约束、断言,等等。
例如,以下的SQL DDL语句定义了department表:
create table department
(dept_name char(20),
building char(15),
budget numeric(12,2));
上面的DDL语句执行的结果就是创建了department表,该表有3个列:dept_name、building和budget,每个列有一个与之相关联的数据类型。在第3章我们将更详细地讨论数据类型。另外,DDL语句还更新了数据字典,它包含元数据(见1.4.2节)。表的模式就是元数据的一个例子。
1.5.4来自应用程序的数据库访问
SQL不像一个通用的图灵机那么强大,即有一些计算可以用通用的程序设计语言来表达,但无法通过SQL来表达。SQL还不支持诸如从用户那儿输入、输出到显示器,或者通过网络通信这样的动作。这样的计算和动作必须用一种宿主语言来写,比如C、C++或Java,在其中使用嵌入式的SQL查询来访问数据库中的数据。应用程序(application program)在这里是指以这种方式与数据库进行交互的程序。在大学系统的例子中,就是那些使学生能够注册课程、产生课程花名册、计算学生的GPA、产生工资支票等的程序。
为了访问数据库,DML语句需要由宿主语言来执行。有两种途径可以做到这一点:
一种是通过提供应用程序接口(过程集),它可以用来将DML和DDL的语句发送给数据库,再取回结果。
与C语言一起使用的开放数据库连接(ODBC)标准,是一种常用的应用程序接口标准。Java数据库连接(JDBC)标准为Java语言提供了相应的特性。
另一种是通过扩展宿主语言的语法,在宿主语言的程序中嵌入DML调用。通常用一个特殊字符作为DML调用的开始,并且通过预处理器,称为DML预编译器(DML precompiler),来将DML语句转变成宿主语言中的过程调用。
1.6数据库设计
数据库系统被设计用来管理大量的信息。这些大量的信息并不是孤立存在的,而是企业行为的一部分;企业的终端产品可以是从数据库中得到的信息,或者是某种设备或服务,数据库对它们起到支持的作用。
数据库设计的主要内容是数据库模式的设计。为设计一个满足企业需求模型的完整的数据库应用环境还要考虑更多的问题。在本书中,我们先着重讨论数据库查询语句的书写以及数据库模式的设计,第9章将讨论应用设计的整个过程。
1.6.1设计过程
高层的数据模型为数据库设计者提供了一个概念框架,去说明数据库用户的数据需求,以及将怎样构造数据库结构以满足这些需求。因此,数据库设计的初始阶段是全面刻画预期的数据库用户的数据需求。为了完成这个任务,数据库设计者有必要和领域专家、数据库用户广泛地交流。这个阶段的成果是制定出用户需求的规格文档。
下一步,设计者选择一个数据模型,并运用该选定的数据模型的概念,将那些需求转换成一个数据库的概念模式。在这个概念设计(conceptualdesign)阶段开发出来的模式提供了企业的详细概述。设计者再复审这个模式,确保所有的数据需求都满足并且相互之间没有冲突,在检查过程中设计者也可以去掉一些冗余的特性。这一阶段的重点是描述数据以及它们之间的联系,而不是指定物理的存储细节。
从关系模型的角度来看,概念设计阶段涉及决定数据库中应该包括哪些属性,以及如何将这些属性组织到多个表中。前者基本上是商业的决策,在本书中我们不进一步讨论。而后者主要是计算机科学的问题,解决这个问题主要有两种方法:一种是使用实体-联系模型(见1.6.3节),另一种是引入一套算法(通称为规范化),这套算法将所有属性集作为输入,生成一组关系表(见1.6.4节)。
一个开发完全的概念模式还将指出企业的功能需求。在功能需求说明(specification of functional requirement)中,用户描述数据之上的各种操作(或事务),例如更新数据、检索特定的数据、删除数据等。在概念设计的这个阶段,设计者可以对模式进行复审,确保它满足功能需求。
现在,将抽象数据模型转换到数据库实现进入最后两个设计阶段。在逻辑设计阶段(logicaldesign phrase),设计者将高层的概念模式映射到要使用的数据库系统的实现数据模型上;然后设计者将得到的特定于系统的数据库模式用到物理设计阶段(physicaldesign phrase)中,在这个阶段中指定数据库的物理特性,这些特性包括文件组织的形式以及内部的存储结构,这些内容将在第10章中讨论。
1.6.2大学机构的数据库设计
为了阐明设计过程,我们来看如何为大学做数据库设计。初始的用户需求说明可以基于与数据库用户的交流以及设计者自己对大学机构的分析。这个设计阶段中的需求描述是制定数据库的概念结构的基础。以下是大学的主要特性:
大学分成多个系。每个系由自己唯一的名字(dept_name)来标识,坐落在特定的建筑物(building)中,有它的经费预算(budget)。
每一个系有一个开设课程列表。每门课程有课程号(course_id)、课程名(title)、系名(dept_name)和学分(credits),还可能有先修要求(prerequisites)。
教师由个人唯一的标识号(ID)来标识。每位教师有姓名(name)、所在的系(dept_name)和工资(salary)。
学生由个人唯一的标识号(ID)来标识。每位学生有姓名(name)、主修的系(dept_name)和已修学分数(tot_cred)。
大学维护一个教室列表,详细说明楼名(building)、房间号(room_number)和容量(capacity)。
大学维护开设的所有课程(开课)的列表。每次开课由课程号(course_id)、开课号(sec_id)、年(year)和学期(semester)来标识,与之相关联的有学期(semester)、年(year)、楼名(building)、房间号(room_number)和时段号(time_slot_id,即上课的时间)。
系有一个教学任务列表,说明每位教师的授课情况。
大学有一个所有学生课程注册的列表,说明每位学生在哪些课程的哪次开课中注册了。
一个真正的大学数据库会比上述的设计复杂得多。然而,我们就用这个简化了的模型来帮助你理解概念思想,避免你迷失在复杂设计的细节中。
1.6.3实体-联系模型
实体-联系(ER)数据模型使用一组称作实体的基本对象,以及这些对象间的联系。实体是现实世界中可区别于其他对象的一件“事情”或一个“物体”。例如,每个人是一个实体,每个银行账户也是一个实体。
数据库中实体通过属性(attribute)集合来描述。例如, 属性dept_name、building与budget可以描述大学中的一个系,并且它们也组成了department实体集的属性。类似地,属性ID、name和salary可以描述instructor实体。机敏的读者会注意到我们从描述instructor实体集的属性集中丢掉了dept_name属性。在第7章中我们将详细解释为什么这样做。
我们用额外的属性ID来唯一标识教师(因为可能存在两位教师有相同的名字和相同的工资)。必须给每位教师分配唯一的教师标识。在美国,许多机构用一个人的社会保障号(它是美国政府分配给每个美国人的一个唯一的号码)作为他的唯一标识。
联系(relationship)是几个实体之间的关联。例如,member联系将一位教师和她所在的系关联在一起。同一类型的所有实体的集合称作实体集(entity set),同一类型的所有联系的集合称作联系集(relationship set)。
数据库的总体逻辑结构(模式)可以用实体-联系图(entityrelationship diagram,ER图)进行图形化表示。有几种方法来画这样的图。最常用的方法之一是采用统一建模语言(Unified Modeling Language,UML)。在我们使用的基于UML的符号中,ER图如下表示:
实体集用矩形框表示,实体名在头部,属性名列在下面。
联系集用连接一对相关的实体集的菱形表示,联系名放在菱形内部。
作为例子,我们来看一下大学数据库中包括教师和系以及它们之间的关联的部分。对应的ER图如图13所示。ER图表示出有instructor和department这两个实体集,它们具有先前已经列出的一些属性。这个图还指明了在教师和系之间的member联系。
图13ER图示例
除了实体和联系外,ER模型还描绘了数据库必须遵守的对其内容的某些约束。一个重要的约束是映射基数(mapping cardinality),它表示通过某个联系集能与一实体进行关联的实体数目。例如,如果一位教师只能属于一个系,ER模型就能表达出这种约束。
实体-联系模型在数据库设计中使用广泛,在第7章中将详细研究。
1.6.4规范化
设计关系数据库所用到的另外一种方法是通常被称为规范化的过程。它的目标是生成一个关系模式集合,使我们存储信息时没有不必要的冗余,同时又能很轻易地检索数据。这种方法是设计一种符合适当的范式(normal form)的模式,为确定一个关系模式是否符合想要的范式,我们需要额外的关于用数据库建模的现实世界中机构的信息。最常用的方法是使用函数依赖(functional dependency),我们将在8.4节讨论。
为了理解规范化的必要性,我们看一看在不好的数据库设计中会发生什么问题。一个不好的设计可能会包括如下不良特性:
信息重复。
缺乏表达某些信息的能力。
我们在对大学例子修改后的数据库设计中讨论这些问题。
假设不将表instructor和department分开,我们使用单个表faculty,它将两个表的数据合并在一起(见图14)。注意到在表faculty中有两行包含重复的关于历史系的信息,具体地说,是系所在的大楼和经费预算。这种修改后的设计中出现了我们不想要的信息重复。信息重复浪费存储空间,而且使得更新数据库变得复杂。假设我们想将历史系的经费预算从50 000美元改成46 800美元,这个修改必须在两行中都体现出来;这与原来的设计不同,原来只需要修改一行就可以了。因此,改变后的设计中更新操作的代价比原来的设计中大了。当我们在改变设计的数据库中进行更新时,我们必须保证与历史系有关的每个元组都被更新,否则我们的数据库将会显示出历史系有两个不同的经费预算数。
图14faculty 表
现在我们再看一看缺乏表达某些信息的能力的问题。假设我们在大学里建立一个新的系。在上述改变了的数据库设计中,我们不能直接表示关于一个系的信息(dept_name, building, budget),除非那个系在大学里至少有一位教员。这是因为faculty表中的行需要ID、name和 salary的值。这意味着我们不能记录新建立的系的信息,直到这个新的系聘用了第一位教员。
这个问题的一个解决办法是引入空值(null)。空值表示这个值不存在(或者未知),未知值可能是缺失(该值确实存在,但我们没有得到它)或不知道(我们不知道该值是否存在)。正如我们后面要看到的那样,空值很难处理,所以最好不要用它。如果我们不愿意处理空值,我们可以仅当一个系有至少一位教员时才为它建立系的信息项。而且,当系的最后一位教员离开时,我们必须删除系的信息。很明显,这种情况不是我们想要的,因为在我们原来的数据库设计里,不管一个系有没有教员,这个系的信息都是可以保存的,也不必使用空值。
规范化的详尽理论已经研究形成,它有助于形式化地定义什么样的数据库设计是不好的,以及如何得到我们想要的设计。第8章将讨论关系数据库设计,包括规范化。
1.7数据存储和查询
数据库系统划分为不同的模块,每个模块完成整个系统的一个功能。数据库系统的功能部件大致可分为存储管理器和查询处理部件。
存储管理非常重要,因为数据库常常需要大量存储空间。企业的大型数据库的大小达到数百个gigabyte,甚至达到terabyte。一个gigabyte大约等于1000个(实际上是1024个)megabyte(十亿字节),一个terabyte等于一百万个megabyte(一万亿个字节)。由于计算机主存不可能存储这么多信息,所以信息被存储在磁盘上。需要时数据在主存和磁盘间移动。由于相对于中央处理器的速度来说数据出入磁盘的速度很慢,因此数据库系统对数据的组织必须满足使磁盘和主存之间数据的移动最小化。
查询处理也非常重要,因为它帮助数据库系统简化和方便了数据的访问。查询处理器使得数据库用户能够获得很高的性能,同时可以在视图的层次上工作,不必承受了解系统实现的物理层次细节的负担。将在逻辑层编写的更新和查询转变成物理层的高效操作序列,这是数据库系统的任务。
1.7.1存储管理器
存储管理器是数据库系统中负责在数据库中存储的低层数据与应用程序以及向系统提交的查询之间提供接口的部件。存储管理器负责与文件管理器进行交互。原始数据通过操作系统提供的文件系统存储在磁盘上。存储管理器将各种DML语句翻译为底层文件系统命令。因此,存储管理器负责数据库中数据的存储、检索和更新。
存储管理部件包括:
权限及完整性管理器(authorization and integrity manager),它检测是否满足完整性约束,并检查试图访问数据的用户的权限。
事务管理器(transaction manager),它保证即使发生了故障,数据库也保持在一致的(正确的)状态,并保证并发事务的执行不发生冲突。
文件管理器(file manager),它管理磁盘存储空间的分配,管理用于表示磁盘上所存储信息的数据结构。
缓冲区管理器(buffer manager),它负责将数据从磁盘上取到内存中来,并决定哪些数据应被缓冲存储在内存中。缓冲区管理器是数据库系统中的一个关键部分,因为它使数据库可以处理比内存更大的数据。
存储管理器实现了几种数据结构,作为系统物理实现的一部分:
数据文件(data files),存储数据库自身。
数据字典(data dictionary),存储关于数据库结构的元数据,尤其是数据库模式。
索引(index),提供对数据项的快速访问。和书中的索引一样,数据库索引提供了指向包含特定值的数据的指针。例如,我们可以运用索引找到具有特定的ID的instructor记录,或者具有特定的name的所有instructor记录。散列是另外一种索引方式,在某些情况下速度更快,但不是在所有情况下都这样。
我们在第10章讨论存储介质、文件结构和缓冲区管理,以及通过索引和散列高效访问数据的方法。
1.7.2查询处理器
查询处理器组件包括:
DDL解释器(DDL interpreter),它解释DDL语句并将这些定义记录在数据字典中。
DML编译器(DML compiler),将查询语言中的DML语句翻译为一个执行方案,包括一系列查询执行引擎能理解的低级指令。
一个查询通常可被翻译成多种等价的具有相同结果的执行方案的一种。DML编译器还进行查询优化(query optimization),也就是从几种选择中选出代价最小的一种。
查询执行引擎(query evaluation engine),执行由DML编译器产生的低级指令。
第11章将介绍查询执行,以及查询优化器选择合适的执行策略的方法。
1.8事务管理
通常,对数据库的几个操作合起来形成一个逻辑单元。如1.2节所示的例子是一个资金转账,其中一个系(A系)的账户进行取出操作,而另一个系(B系)的账户进行存入操作。显然,这两个操作必须保证要么都发生要么都不发生。也就是说,资金转账必须完成或根本不发生。这种要么完成要么不发生的要求称为原子性(atomicity)。除此以外,资金转账还必须保持数据库的一致性。也就是说,A和B的余额之和应该是保持不变的。这种正确性的要求称作一致性(consistency)。最后,当资金转账成功结束后,即使发生系统故障,账户A和账户B的余额也应该保持转账成功结束后的新值。这种保持的要求称作持久性(durability)。
事务(transaction)是数据库应用中完成单一逻辑功能的操作集合。每一个事务是一个既具原子性又具一致性的单元。因此,我们要求事务不违反任何的数据库一致性约束,也就是说,如果事务启动时数据库是一致的,那么当这个事务成功结束时数据库也应该是一致的。然而,在事务执行过程中,必要时允许暂时的不一致,因为无论是A取出的操作在前还是B存入的操作在前,这两个操作都必然有一个先后次序。这种暂时的不一致虽然是必需的,但在故障发生时,很可能导致问题的产生。
适当地定义各个事务是程序员的职责,事务的定义应使之能保持数据库的一致性。例如,资金从A系的账户转到B系的账户这个事务可以被定义为由两个单独的程序组成:一个对账户A执行取出操作,另一个对账户B执行存入操作。这两个程序的依次执行可以保持一致性。但是,这两个程序自身都不是把数据库从一个一致的状态转入一个新的一致的状态,因此它们都不是事务。
原子性和持久性的保证是数据库系统自身的职责,确切地说,是恢复管理器(recovery manager)的职责。在没有故障发生的情况下,所有事务均成功完成,这时要保证原子性很容易。但是,由于各种各样的故障,事务并不总能成功执行完毕。为了保证原子性,失败的事务必须对数据库状态不产生任何影响。因此,数据库必须被恢复到该失败事务开始执行以前的状态。这种情况下数据库系统必须进行故障恢复(failure recovery),即检测系统故障并将数据库恢复到故障发生以前的状态。
最后,当多个事务同时对数据库进行更新时,即使每个单独的事务都是正确的,数据的一致性也可能被破坏。并发控制管理器(concurrencycontrol manager)控制并发事务间的相互影响,保证数据库一致性。事务管理器(transaction manager)包括并发控制管理器和恢复管理器。
第12章介绍事务处理的基本概念,以及并发事务的管理和故障恢复。
事务的概念已经广泛应用在数据库系统和应用当中。虽然最初是在金融应用中使用事务,现在事务已经使用在电信业的实时应用中,以及长时间的活动如产品设计和工作流管理中。
1.9数据库体系结构
现在我们可以给出一个数据库系统各个部分以及它们之间联系的图了(见图15)。
图15系统体系结构
数据库系统的体系结构很大程度上取决于数据库系统所运行的计算机系统。数据库系统可以是集中式的、客户/服务器式的(一台服务器为多个客户机执行任务);也可以针对并行计算机体系结构设计数据库系统;分布式数据库包含地理上分离的多台计算机。
1.9.1客户/服务器系统
今天数据库系统的大多数用户并不直接面对数据库系统,而是通过网络与其相连。因此我们可区分远程数据库用户工作用的客户机(client)和运行数据库系统的服务器(server)。
数据库应用通常可分为两或三个部分,如图16所示。在一个两层体系结构(twotier architecture)中,应用程序驻留在客户机上,通过查询语言表达式来调用服务器上的数据库系统功能。像ODBC和JDBC这样的应用程序接口标准被用于进行客户端和服务器的交互。
图16两层和三层体系结构
而在一个三层体系结构(threetier architecture)中,客户机只作为一个前端并且不包含任何直接的数据库调用。客户端通常通过一个表单界面与应用服务器(application server)进行通信。而应用服务器与数据库系统通信以访问数据。应用程序的业务逻辑(business logic),也就是说在何种条件下做出何种反应,被嵌入到应用服务器中,而不是分布在多个客户机上。三层结构的应用更适合大型应用和互联网上的应用。
1.9.2并行数据库系统
并行系统通过并行地使用多个处理器和磁盘来提高处理速度和I/O速度。并行计算机正变得越来越普及,相应地使得并行数据库系统的研究变得更加重要。有些应用需要查询非常大型的数据库(TB数量级,即1012字节),有些应用需要在每秒钟里处理很大数量的事务(每秒数千个事务),这些应用的需求推动了并行数据库系统的发展。集中式数据库系统和客户-服务器数据库系统的能力不够强大,不足以处理这样的应用。
在并行处理中,许多操作是同时执行的,而不是串行处理的(即按顺序执行各个计算步骤)。粗粒度(coarsegrain)并行机由少量能力强大的处理器组成;而
大规模并行(massive parallel)机或细粒度并行(finegrain parallel)机使用数千个更小的处理器。当今所有的高端计算机都提供了一定程度的粗粒度并行性,它们至少具有2个或4个处理器。大规模并行计算机与粗粒度并行机的区别在于它支持的并行程度要高得多。市场上可以买到具有数百个处理器和磁盘的并行计算机。
对数据库系统性能的度量有两种主要方式。(1)吞吐量(throughput):在给定时间段内所能完成任务的数量。(2)响应时间(response time):单个任务从提交到完成所需的时间。对于处理大量小事务的系统,通过并行地处理多个事务可以提高它的吞吐量。对于处理大事务的系统,通过并行地执行每个事务中的子任务可以缩短它的响应时间,同时提高它的吞吐量。
1.9.3分布式数据库系统
在分布式数据库系统(distributed database system)中,数据库存储在几台计算机中。分布式系统中的计算机之间通过诸如高速私有网络或因特网那样的通信媒介相互通信。它们不共享主存储器或磁盘。分布式系统中的计算机在规模和功能上是可变的,小到工作站,大到大型机系统。
对于分布式系统中的计算机有多种不同的称呼,例如站点(site)或结点(node)。分布式系统的通用结构如图17所示。
图17分布式系统
建立分布式数据库系统有几个原因,包括数据共享、自治性和可用性。
数据共享(sharing data)。建立分布式数据库系统的主要优点是,它提供了一个环境使一个站点上的用户可以访问存放在其他站点上的数据。例如,在一个分布式大学系统中,每个校区存储与该校区相关的数据。一个校区的用户可以访问另一个校区的数据。如果没有这种能力,那么要把学生记录从一个校区传送到另一个校区,就需要借助于与现有系统相互关联的外部机制。
自治性(autonomy)。通过数据分布的方式来共享数据的主要优点在于,每个站点可以对本地存储的数据保持一定程度的控制。在集中式系统中,中心站点的数据库管理员对数据库进行控制。在分布式系统中,有一个全局数据库管理员负责整个系统。有一部分职责被委派给每个站点的本地数据库管理员。每个管理员可以有不同程度的局部自治(local autonomy),其程度的不同依赖于分布式数据库系统的设计。可以进行本地自治通常是分布式数据库的一个主要优势。
可用性(availability)。在分布式系统中,如果一个站点发生故障,其他站点可能还能继续运行。特别地,如果数据项在几个站点上进行了复制(replicate),需要某个特定数据项的事务可以在这几个站点中的任何一个上找到该数据项。于是,一个站点的故障不一定意味着整个系统停止运转。
1.10数据挖掘与信息检索
数据挖掘(data mining)这个术语指半自动地分析大型数据库并从中找出有用的模式的过程。和人工智能中的知识发现(也称为机器学习(machine learning))或者统计分析一样,数据挖掘试图从数据中寻找规则或模式。但是,数据挖掘和机器学习、统计分析不一样的地方在于它处理大量的主要存储在磁盘上的数据。也就是说,数据挖掘就是在数据库中发现知识。
从数据库中发现的某些类型的知识可以用一套规则(rule)表示。下面是一条规则的例子,非形式化地描述为:“年收入高于50 000美元的年轻女性是最可能购买小型运动车的人群”。当然这条规则并不是永远正确的,但它有一定的“支持度”和“置信度”。其他类型的知识表达方式有联系不同变量的方程式,或者通过其他机制根据某些已知的变量来预测输出。
还有很多其他类型的有用模式以及发现不同模式的技术。在第13章我们将研究一些模式的例子以及如何自动地从数据库中得出这些模式。
通常在数据挖掘中还需要人参与,包括数据预处理使数据变为适合算法的格式,在已发现模式的后处理中找到新奇的有用模式。给定一个数据库,可能有不止一种类型的模式,需要人工交互挑选有用类型的模式。由于这个原因,现实中的数据挖掘是一个半自动的过程。但是,在我们的描述中,主要介绍挖掘的自动处理过程的部分。
商业上已经开始利用蓬勃发展的联机数据来支持对于业务活动的更好的决策,例如储备哪些物品,如何更好地锁定目标客户以提高销售额。但是,它们的许多查询都相当复杂,有些类型的信息甚至使用SQL都不能抽取出来。
目前有几种技术和工具可用于帮助做决策支持。一些数据分析的工具让分析人员能够从不同的角度观察数据。其他的分析工具提前计算出大量数据的汇总信息,以更快响应查询。现在的SQL标准也增加了支持数据分析的成分。
大型企业有各种不同的可用于业务决策的数据来源。要在这些各种各样的数据上高效地执行查询,企业建立了数据仓库(data warehouse)。数据仓库从多个来源收集数据,建立统一的模式,驻留在单个节点上。于是,就为用户提供了单个统一的数据界面。
文本数据也爆炸式增长。文本数据是非结构化的,与关系数据库中严格的结构化数据不同。查询非结构化的文本数据被称为信息检索(information retrieval)。信息检索系统和数据库系统很大程度上是相同的——特别是基于辅助存储器的数据存储和检索。但是信息系统领域与数据库系统所强调的重点是不同的,信息系统重点强调基于关键词的查询,文档与查询的相似度,以及文档的分析、分类和索引。第13章我们将讨论决策支持,包括联机分析处理、数据挖掘、数据仓库和信息检索。
1.11特种数据库
数据库系统的一些应用领域受到关系数据模型的限制。其结果是,研究人员开发了几种数据模型来处理这些领域的应用,包括基于对象的数据模型和半结构化数据模型。
1.11.1基于对象的数据模型
面向对象程序设计已经成为占统治地位的软件开发方法学。这导致面向对象数据模型(objectbased data model)的发展,面向对象模型可以看作ER模型的扩展,增加了封装、方法(函数)和对象标识。继承、对象标识和信息封装(信息隐蔽),以及对外提供方法作为访问对象的接口,这些是面向对象程序设计的关键概念,现在在数据建模中也找到了应用。面向对象数据模型还支持丰富的类型系统,包括结构和集合类型。在20世纪80年代,开发了好几个基于面向对象数据模型的数据库系统。
现在主要的数据库厂商都支持对象-关系数据模型(objectrelational data model),这是一个将面向对象数据模型和关系数据模型的特点结合在一起的数据模型。它扩展了传统的关系模型,增加了新的特征如结构和集合类型,以及面向对象特性。第14章介绍对象-关系数据模型。
1.11.2半结构化数据模型
半结构化数据模型允许那些相同类型的数据项有不同的属性集的数据说明。这和早先提到的数据模型形成了对比:在那些数据模型中所有某种特定类型的数据项必须有相同的属性集。
XML语言设计的初衷是为文本文档增加标签信息,但由于它在数据交换中的应用而变得日益重要。XML提供了表达含有嵌套结构的数据的方法,能够灵活组织数据结构,这对于一些非传统数据来说非常重要。第15章简单介绍XML语言、XML格式的数据的各种查询表示方法,以及不同XML数据格式之间的转换。
1.12数据库用户和管理员
数据库系统的一个主要目标是从数据库中检索信息和往数据库中存储新信息。使用数据库的人员可分为数据库用户和数据库管理员。
1.12.1数据库用户和用户界面
根据所期望的与系统交互方式的不同,数据库系统的用户可以分为四种不同类型。系统为不同类型的用户设计了不同类型的用户界面。
无经验的用户(nave user)是默认经验的用户,他们通过激活事先已经写好的应用程序同系统进行交互。例如,大学的一位职员需要往A 系中添加一位新的教师时,激活一个叫做new_hire的程序。该程序要求这位职员输入新教师的名字、她的新ID、系的名字(即A)以及她的工资额。
此类用户的典型用户界面是表格界面,用户只需填写表格的相应项就可以了。无经验的用户也可以很简单地阅读数据库产生的报表。
作为另外一个例子,我们考虑一个学生,他在课程注册的过程中想通过Web界面来注册一门课程。应用程序首先验证该用户的身份,然后允许她去访问一个表格,她可以在表格中填入想填的信息。表格信息被送回给服务器上的Web应用程序,然后应用程序确定该课程是否还有空额(通过从数据库中检索信息),如果有,就把这位学生的信息添加到数据库中的该课程花名册中。
应用程序员(application programmer)是编写应用程序的计算机专业人员。有很多工具可以供应用程序员选择来开发用户界面。快速应用开发(Rapid Application Development, RAD)工具是使应用程序员能够尽量少编写程序就可以构造出表格和报表的工具。
老练的用户(sophisticated user)不通过编写程序来同系统交互,而是用数据库查询语言或数据分析软件这样的工具来表达他们的要求。分析员通过提交查询来研究数据库中的数据,所以属于这一类用户。
专门的用户(specialized user)是编写专门的、不适合于传统数据处理框架的数据库应用的富有经验的用户。这样的应用包括:计算机辅助设计系统、知识库和专家系统、存储复杂结构数据(如图形数据和声音数据)的系统,以及环境建模系统。在第14章中我们将要讨论几个这样的应用。
1.12.2数据库管理员
使用DBS的一个主要原因是可以对数据和访问这些数据的程序进行集中控制。对系统进行集中控制的人称作数据库管理员(DataBase Administrator, DBA)。DBA的作用包括:
模式定义(schema definition)。DBA通过用DDL书写的一系列定义来创建最初的数据库模式。
存储结构及存取方法定义(storage structure and accessmethod definition)。
模式及物理组织的修改(schema and physicalorganization modification)。由数据库管理员(DBA)对模式和物理组织进行修改,以反映机构的需求变化,或为提高性能选择不同的物理组织。
数据访问授权(granting of authorization for data access)。通过授予不同类型的权限,数据库管理员可以规定不同的用户各自可以访问的数据库的部分。授权信息保存在一个特殊的系统结构中,一旦系统中有访问数据的要求,数据库系统就去查阅这些信息。
日常维护(routine maintenance)。数据库管理员的日常维护活动有:
定期备份数据库,或者在磁带上或者在远程服务器上,以防止像洪水之类的灾难发生时数据丢失。
确保正常运转时所需的空余磁盘空间,并且在需要时升级磁盘空间。
监视数据库的运行,并确保数据库的性能不因一些用户提交了花费时间较多的任务就下降很多。
1.13数据库系统的历史
从商业计算机的出现开始,数据处理就一直推动着计算机的发展。事实上,数据处理自动化早于计算机的出现。Herman Hollerith发明的穿孔卡片,早在20世纪初就用来记录美国的人口普查数据,并且用机械系统来处理这些卡片和列出结果。穿孔卡片后来被广泛用作将数据输入计算机的一种手段。
数据存储和处理技术发展的年表如下:
20世纪50年代和20世纪60年代初:磁带被用于数据存储。诸如工资单这样的数据处理已经自动化了,数据存储在磁带上。数据处理包括从一个或多个磁带上读取数据,并将数据写回到新的磁带上。数据也可以由一叠穿孔卡片输入,而输出到打印机上。例如,工资增长的处理是通过将增长表示到穿孔卡片上,在读入一叠穿孔卡片时同步地读入保存主要工资细节的磁带。记录必须有相同的排列顺序。工资的增加额将被加入到从主磁带读出的工资中,并被写到新的磁带上,新磁带将成为新的主磁带。
磁带(和卡片组)都只能顺序读取,数据规模可以比内存大得多,因此,数据处理程序被迫以一种特定的顺序来对数据进行处理,读取和合并来自磁带和卡片组的数据。
20世纪60年代末和20世纪70年代:20世纪60年代末硬盘的广泛使用极大地改变了数据处理的情况,因为硬盘允许直接对数据进行访问。数据在磁盘上的位置是无关紧要的,因为磁盘上的任何位置都可在几十毫秒内访问到。数据由此摆脱了顺序访问的限制。有了磁盘,我们就可以创建网状和层次的数据库,它可以将表和树这样的数据结构保存在磁盘上。程序员可以构建和操作这些数据结构。
由Codd[1970]撰写的一篇具有里程碑意义的论文定义了关系模型和在关系模型中查询数据的非过程化方法,由此关系型数据库诞生了。关系模型的简单性和能够对程序员屏蔽所有实现细节的能力具有真正的诱惑力。随后,Codd因其所做的工作获得了声望很高的ACM图灵奖。
20世纪80年代:尽管关系模型在学术上很受重视,但是最初并没有实际的应用,这是因为它被认为性能不好;关系型数据库在性能上还不能和当时已有的网状和层次数据库相提并论。这种情况直到System R的出现才得以改变,这是IBM研究院的一个突破性项目,它开发了能构造高效的关系型数据库系统的技术。Astrahan等[1976]和Chamberlin等[1981]给出了关于System R的很好的综述。完整功能的System R原型导致了IBM的第一个关系型数据库产品SQL/DS的出现。与此同时,加州大学伯克利分校开发了Ingres系统。它后来发展成具有相同名字的商品化关系数据库系统。最初的商品化关系型数据库系统,如IBM DB2、Oracle、Ingres和DEC Rdb,在推动高效处理声明性查询的技术上起到了主要的作用。到了20世纪80年代初期,关系型数据库已经可以在性能上与网状和层次型数据库进行竞争了。关系型数据库是如此简单易用,以至于最后它完全取代了网状/层次型数据库,因为程序员在使用后者时,必须处理许多底层的实现细节,并且不得不将他们要做的查询任务编码成过程化的形式。更重要的,他们在设计应用程序时还要时时考虑效率问题,而这需要付出很大的努力。相反,在关系型数据库中,几乎所有的底层工作都由数据库自动来完成,使得程序员可以只考虑逻辑层的工作。自从在20世纪80年代取得了统治地位以来,关系模型在数据模型中一直独占鳌头。
在20世纪80年代人们还对并行和分布式数据库进行了很多研究,同样在面向对象数据库方面也有初步的工作。
20世纪90年代初:SQL语言主要是为决策支持应用设计的,这类应用是查询密集的;而20世纪80年代数据库的支柱是事务处理应用,它们是更新密集的。决策支持和查询再度成为数据库的一个主要应用领域。分析大量数据的工具有了很大的发展。
在这个时期许多数据库厂商推出了并行数据库产品。数据库厂商还开始在他们的数据库中加入对象-关系的支持。
20世纪90年代:最重大的事件就是互联网的爆炸式发展。数据库比以前有了更加广泛的应用。现在数据库系统必须支持很高的事务处理速度,而且还要有很高的可靠性和24×7的可用性(一天24小时,一周7天都可用,也就是没有进行维护的停机时间)。数据库系统还必须支持对数据的Web接口。
21世纪第一个十年:21世纪的最初五年中,我们看到了XML的兴起以及与之相关联的XQuery查询语言成为了新的数据库技术。虽然XML广泛应用于数据交换和一些复杂数据类型的存储,但关系数据库仍然构成大多数大型数据库应用系统的核心。在这个时期,我们还见证了“自主计算/自动管理”技术的成长,其目的是减少系统管理开销;我们还看到了开源数据库系统应用的显著增长,特别是PostgreSQL和MySQL。
在21世纪第一个十年的后几年中,用于数据分析的专门的数据库有很大增长,特别是将一个表的每一个列高效地存储为一个单独的数组的列存储,以及为非常大的数据集的分析而设计的高度并行的数据库系统。有几个新颖的分布式数据存储系统被构建出来,以应对非常大的Web节点如Amazon、Facebook、Google、Microsoft和Yahoo!的数据管理需求,并且这些系统中的某些现在可以作为Web服务提供给应用开发人员使用。在管理和分析流数据如股票市场报价数据或计算机网络监测数据方面也有重要的工作。数据挖掘技术现在被广泛部署应用,应用实例包括基于Web的产品推荐系统和Web页面上的相关广告自动布放。
1.14总结
数据库系统(DataBase System, DBS)由相互关联的数据集合以及一组用于访问这些数据的程序组成。数据描述某特定的企业。
DBS的主要目标是为人们提供方便、高效的环境来存储和检索数据。
如今数据库系统无所不在,很多人每天直接或间接地与数据库系统打交道。
数据库系统设计用来存储大量的信息。数据的管理既包括信息存储结构的定义,也包括提供处理信息的机制。另外数据库系统还必须提供所存储信息的安全性,以处理系统崩溃或者非授权访问企图,如果数据在多个用户之间共享,系统必须避免可能的异常结果。
数据库系统的一个主要目的是为用户提供数据的抽象视图,也就是说,系统隐藏数据存储和维护的细节。
数据库结构的基础是数据模型(data model):一个用于描述数据、数据之间的联系、数据语义和数据约束的概念工具的集合。
关系数据模型是最广泛使用的将数据存储到数据库中的模型。其他的数据模型有面向对象模型、对象-关系模型和半结构化数据模型。
数据操纵语言(DataManipulation Language, DML)是使得用户可以访问和操纵数据的语言。当今广泛使用的是非过程化的DML,它只需要用户指明需要什么数据,而不需指明如何获得这些数据。
数据定义语言(DataDefinition Language, DDL)是说明数据库模式和数据的其他特性的语言。
数据库设计主要包括数据库模式的设计。实体-联系(ER)数据模型是广泛用于数据库设计的数据模型,它提供了一种方便的图形化的方式来观察数据、联系和约束。
数据库系统由几个子系统构成:
存储管理器(storage manager)子系统在数据库中存储的低层数据与应用程序和向系统提交的查询之间提供接口。
查询处理器(query processor)子系统编译和执行DDL和DML语句。
事务管理(transaction management)负责保证不管是否有故障发生,数据库都要处于一致的(正确的)状态。事务管理器还保证并发事务的执行互不冲突。
数据库系统的体系结构受支持其运行的计算机系统的影响很大。数据库系统可以是集中式的或者客户/服务器方式的,即一个服务器机器为多个客户机执行工作。数据库系统还可以设计成具有能充分利用并行计算机系统结构的能力。分布式数据库跨越多个地理上分布的互相分离的计算机。
典型地,数据库应用可被分为运行在客户机上的前端和运行在后端的部分。在两层的体系结构中,前端直接和后端运行的数据库进行通信。在三层结构中,后端又被分为应用服务器和数据库服务器。
知识发现技术试图自动地从数据中发现统计规律和模式。数据挖掘(data mining)领域将人工智能和统计分析研究人员创造的知识发现技术,与使得知识发现技术能够在极大的数据库上高效实现的技术结合起来。
有4种不同类型的数据库用户,按用户期望与数据库进行交互的不同方式来区分他们。为不同类的用户设计了不同的用户界面。
术语回顾
数据库系统(DBS)
数据库系统应用
文件处理系统
数据不一致性
一致性约束
数据抽象
实例
模式
物理模式
逻辑模式
物理数据独立性
数据模型
实体-联系模型
关系数据模型
基于对象的数据模型
半结构化数据模型
数据库语言
数据定义语言
数据操纵语言
查询语言
元数据
应用程序
规范化
数据字典
存储管理器
查询处理器
事务
原子性
故障恢复
并发控制
两层和三层数据库体系结构
数据挖掘
数据库管理员(DBA)
实践习题
1.1这一章讲述了数据库系统的几个主要的优点。它有哪两个不足之处?
1.2列出Java或C++之类的语言中的类型说明系统与数据库系统中使用的数据定义语言的5个不同之处。
1.3列出为一个企业建立数据库的六个主要步骤。
1.4除1.6.2节中已经列出的之外,请列出大学要维护的至少3种不同类型的信息。
1.5假设你想要建立一个类似于YouTube的视频节点。考虑1.2节中列出的将数据保存在文件系统中的各个缺点,讨论每一个缺点与存储实际的视频数据和关于视频的元数据(诸如标题、上传它的用户、标签、观看它的用户)的关联。
1.6在Web查找中使用的关键字查询与数据库查询很不一样。请列出这两者之间在查询表达方式和查询结果是什么方面的主要差异。
习题
1.7列出四个你使用过的很可能使用了数据库来存储持久数据的应用。
1.8列出文件处理系统和DBS的四个主要区别。
1.9解释物理数据独立性的概念,以及它在数据库系统中的重要性。
1.10列出数据库管理系统的五个职责。对每个职责,说明当它不能被履行时会产生什么样的问题。
1.11请给出至少两种理由说明为什么数据库系统使用声明性查询语言,如SQL,而不是只提供C或者C++的函数库来执行数据操作。
1.12解释用图14中的表来设计会导致哪些问题。
1.13数据库管理员的五种主要作用是什么?
1.14解释两层和三层体系结构之间的区别。对Web应用来说哪一种更合适?为什么?
1.15描述可能被用于存储一个社会网络系统如Facebook中的信息的至少3个表。
工具
如今已有大量的商业数据库系统投入使用,主要的有:IBM DB2(www.ibm.com/software/data/db2)、Oracle(www.oracle.com)、Microsoft SQL Server (www.microsoft.com/sql)、Sybase(www.sybase.com) 和IBM Informix(www.ibm.com/software/data/informix)。其中一些对个人或者非商业使用或开发是免费的,但是对实际的部署是不免费的。
也有不少免费/公开的数据库系统,使用很广泛的有MySQL(www.mysql.com)和PostgreSQL(www. postgresql.org)。
在本书的主页www.dbbook.com上可以获得更多的厂商网址的链接和其他信息。
文献注解
我们在下面列出了关于数据库的通用书籍、研究论文集和Web节点。后续各章提供了本章略述的每个主题的资料参考。
Codd[1970]的具有里程碑意义的论文引入了关系模型。
关于数据库系统的教科书有Abiteboul等[1995]、ONeil和 ONeil[2000]、Ramakrishnan 和 Gehrke [2002]、 Date [2003]、 Kifer 等[2005]、Elmasri 和 Navathe [2006], 以及 GarciaMolina 等 [2008]。涵盖事务处理的教科书有Bernstein和Newcomer[1997]以及Gray和Reuter[1993]。有一本书中包含了关于数据库管理的研究论文的汇集,这本书是Hellerstein和Stonebraker [2005]。
Silberschatz等[1990]、Silberschatz等[1996]、Bernstein等[1998],以及Abiteboul等[2003]给出了关于数据库管理已有成果和未来研究挑战的综合评述。ACM的数据管理兴趣组的主页(www.acm.org/sigmod)提供了关于数据库研究的大量信息。数据库厂商的网址(参看上面的工具部分)提供了他们各自产品的细节。
第一部分
Part 1
关系数据库
数据模型是描述数据、数据联系、数据语义以及一致性约束的概念工具的集合。在这一部分中,我们集中学习关系模型。
关系模型利用表的集合来表示数据和数据间的联系,第2章将专门介绍关系模型。其概念上的简单性使得它被广泛采纳;今天大量的数据库产品都是基于关系模型的。关系模型在逻辑层和视图层描述数据,使用户不必关注数据存储的底层细节。在后面第二部分的第7章中讨论的实体-联系模型是一种更高层的数据模型,被广泛用于数据库设计。
为了让用户可以使用关系数据库中的数据,我们需要解决几个问题。最重要的问题是用户如何说明对数据的检索和更新请求,为此已经开发了好几种查询语言。第二个问题也很重要,就是数据完整性和数据保护。无论用户有意或无意地破坏数据,数据库都要保护数据,使其免遭破坏。
第3章、第4章和第5章讲述当今应用最普遍的一种查询语言——SQL语言。第3章和第4章介绍SQL及其中等程度的应用知识。第4章还将介绍通过数据库施加完整性约束以及授权机制,用来控制用户发出的哪些访问和更新操作是可以执行的。第5章介绍更为深入的主题,包括如何在编程语言中使用SQL,如何利用SQL进行数据分析。
第6章介绍三种形式的查询语言:关系代数、元组关系演算和域关系演算,它们是基于数学逻辑的声明式查询语言。这些形式语言构成了SQL,以及另外两种用户友好的语言QBE和Datalog的基础。
第2章
Database System Concepts,6E
关系模型介绍
在商用数据处理应用中,关系模型已经成为当今主要的数据模型。之所以占据主要位置,是因为和早期的数据模型如网络模型或层次模型相比,关系模型以其简易性简化了编程者的工作。
本章我们先学习关系模型的基础知识。关系数据库具有坚实的理论基础,我们在第6章学习关系数据库理论中与查询相关的部分,从第7章到第8章我们将考察其中用于关系数据库模式设计的部分,在第11章我们将讨论高效处理查询的理论。
2.1关系数据库的结构
关系数据库由表(table)的集合构成,每个表有唯一的名字。例如,图21中的instructor表记录了有关教师的信息,它有四个列首:ID、name、dept_name和salary。该表中每一行记录了一位教师的信息,包括该教师的ID、name、dept_name以及salary。类似地,图22中的course表存放了关于课程的信息,包括每门课程的course_id、title、dept_name和credits。注意,每位教师通过ID列的取值进行标识,而每门课程则通过course_id列的取值来标识。
图21instructor关系
图23给出的第三个表是prereq,它存放了每门课程的先修课程信息。该表具有course_id和prereq_id两列,每一行由一个课程对组成,这个课程对表示了第二门课程是第一门课程的先修课。
由此,prereq表中的每行表示了两门课程之间的联系:其中一门课程是另一门课程的先修课。作为另一个例子,我们考察instructor表,表中的行可被认为是代表了从一个特定的ID到相应的name、dept_name和salary值之间的联系。
一般说来,表中一行代表了一组值之间的一种联系。由于一个表就是这种联系的一个集合,表这个概念和数学上的关系这个概念是密切相关的,这也正是关系数据模型名称的由来。在数学术语中,元组(tuple)只是一组值的序列(或列表)。在n个值之间的一种联系可以在数学上用关于这些值的一个n元组(ntuple)来表示,换言之,n元组就是一个有n个值的元组,它对应于表中的一行。
图22course关系
这样,在关系模型的术语中,关系(relation)用来指代表,而元组(tuple)用来指代行。类似地,属性(attribute)指代的是表中的列。
图23prereq关系
考察图21,我们可以看出instructor关系有四个属性:ID、name、dept_name和salary。
我们用关系实例(relation instance)这个术语来表示一个关系的特定实例,也就是所包含的一组特定的行。图21所示的instructor的实例有12个元组,对应于12个教师。
本章我们将使用多个不同的关系来说明作为关系数据模型基础的各种概念。这些关系代表一个大学的一部分。它们并没有包含真实的大学数据库中的所有数据,这主要是为了简化表示。在第7章和第8章里我们将详细讨论如何判断适当的关系结构的相关准则。
由于关系是元组集合,所以元组在关系中出现的顺序是无关紧要的。因此,无论关系中的元组是像图21那样被排序后列出,还是像图24那样无序的,都没有关系;在上述两图中的关系是一样的,因为它们具有同样的元组集合。为便于说明,当我们在显示关系时,大多数情况下都按其第一个属性排序。
图24instructor关系的无序显示
对于关系的每个属性,都存在一个允许取值的集合,称为该属性的域(domain)。这样instructor关系的salary属性的域就是所有可能的工资值的集合,而name属性的域是所有可能的教师名字的集合。
我们要求对所有关系r而言,r的所有属性的域都是原子的。如果域中元素被看作是不可再分的单元,则域是原子的(atomic)。例如,假设instructor表上有一个属性phone_number,它存放教师的一组联系电话号码。那么phone_number的域就不是原子的,因为其中的元素是一组电话号码,是可以被再分为单个电话号码这样的子成分的。
重要的问题不在于域本身是什么,而在于我们怎样在数据库中使用域中元素。现在假设phone_number属性存放单个电话号码。即便如此,如果我们把电话号码的属性值拆分成国家编号、地区编号以及本地号码,那么我们还是把它作为非原子值来对待。如果我们把每个电话号码作为不可再分的单元,那么phone_number属性才会有原子的域。
在本章以及第3章~第6章,我们假设所有属性的域都是原子的。在第14章中,我们将讨论对关系数据模型进行扩展以便允许非原子域。
空(null)值是一个特殊的值,表示值未知或不存在。如前所述,如果我们在关系instructor中包括属性phone_number,则可能某教师根本没有电话号码,或者电话号码未提供。这时我们就只能使用空值来强调该值未知或不存在。以后我们会看到,空值给数据库访问和更新带来很多困难,因此应尽量避免使用空值。我们先假设不存在空值,然后在3.6节中我们将描述空值对不同操作的影响。
2.2数据库模式
当我们谈论数据库时,我们必须区分数据库模式(database schema)和数据库实例(database instance),前者是数据库的逻辑设计,后者是给定时刻数据库中数据的一个快照。
关系的概念对应于程序设计语言中变量的概念,而关系模式(relation schema)的概念对应于程序设计语言中类型定义的概念。
一般说来,关系模式由属性序列及各属性对应域组成。等第3章讨论SQL语言时,我们才去关心每个属性的域的精确定义。
关系实例的概念对应于程序设计语言中变量的值的概念。给定变量的值可能随时间发生变化;类似地,当关系被更新时,关系实例的内容也随时间发生了变化。相反,关系的模式是不常变化的。
尽管知道关系模式和关系实例的区别非常重要,我们常常使用同一个名字,比如instructor,既指代模式,也指代实例。在需要的时候,我们会显示地指明模式或实例。例如“instructor模式”或“instructor关系的一个实例”。然而,在模式或实例的含义清楚的情况下,我们就简单地使用关系的名字。
图25department关系
考察图25中的department关系,该关系的模式是:
department (dept_name, building, budget)
请注意属性dept_name既出现在instructor模式中,又出现在department模式中。这样的重复并不是一种巧合。实际上,在关系模式中使用相同属性正是将不同关系的元组联系起来的一种方法。例如,假设我们希望找出在Watson大楼工作的所有教师的相关信息。我们首先在department关系中找出所有位于Watson的系的dept_name。接着,对每一个这样的系,我们在instructor关系中找出与dept_name对应的教师信息。
我们继续看大学数据库的例子。
大学里的每门课程可能要讲授多次,可以在不同学期授课,甚至可能在同一个学期授课。我们需要一个关系来描述每次课的授课情况或分段情况。该关系模式为:
section (course_id, sec_id, semester, year, building, room_number, time_slot_id)
图26给出了section关系的一个示例。
我们需要一个关系来描述教师和他们所讲授的课程段之间的联系。描述此联系的关系模式是:
teaches (ID, course_id, sec_id, semester, year)
图26section关系
图27teaches关系
图27给出了teaches关系的一个示例。
正如你可以料想的,在一个真正的大学数据库中还维护了更多的关系。除了我们已经列出的这些关系:instructor、department、course、section、prereq和teaches,在本书中我们还要使用下列关系:
student (ID, name, dept_name, tot_cred)
advisor (s_id, i_id)
takes (ID, course_id, sec_id, semester, year, grade)
classroom (building, room_number, capacity)
time_slot (time_slot_id, day, start_time, end_time)
2.3码
我们必须有一种能区分给定关系中的不同元组的方法。这用它们的属性来表明。也就是说,一个元组的属性值必须是能够唯一区分元组的。换句话说,一个关系中没有两个元组在所有属性上的取值都相同。
超码(superkey)是一个或多个属性的集合,这些属性的组合可以使我们在一个关系中唯一地标识一个元组。例如,instructor关系的ID属性足以将不同的教师元组区分开来,因此,ID是一个超码。另一方面,instructor的name属性却不是一个超码,因为几个教师可能同名。
形式化地描述,设R表示关系r模式中的属性集合。如果我们说R的一个子集K是r的一个超码,则限制了关系r中任意两个不同元组不会在K的所有属性上取值完全相等,即如果t1和t2在r中且t1≠t2,则t1.K≠t2.K。
超码中可能包含无关紧要的属性。例如,ID和name的组合是关系instructor的一个超码。如果K是一个超码,那么K的任意超集也是超码。我们通常只对这样的一些超码感兴趣,它们的任意真子集都不能成为超码。这样的最小超码称为候选码(candidate key)。
几个不同的属性集都可以做候选码的情况是存在的。假设name和dept_name的组合足以区分instructor关系的各个成员,那么{ID}和{name, dept_name}都是候选码。虽然属性ID和name一起能区分instructor元组,但它们的组合{ID, name }并不能成为候选码,因为单独的属性ID已是候选码。
我们用主码(primary key)这个术语来代表被数据库设计者选中的、主要用来在一个关系中区分不同元组的候选码。码(不论是主码、候选码或超码)是整个关系的一种性质,而不是单个元组的性质。关系中的任意两个不同的元组都不允许同时在码属性上具有相同的值。码的指定代表了被建模的事物在现实世界中的约束。
主码的选择必须慎重。正如我们所注意到的那样,人名显然是不足以作主码的,因为可能有多个人重名。在美国,人的社会保障号可以作候选码。而非美国居民通常不具有社会保障号,所以跨国企业必须设置他们自己的唯一标识符。另外也可以使用另一些属性的唯一组合作为码。
主码应该选择那些值从不或极少变化的属性。例如,一个人的地址就不应该作为主码的一部分,因为它很可能变化。另一方面,社会保障号却可以保证决不变化。企业产生的唯一标识符通常不变,除非两个企业合并了,这种情况下可能在两个公司中会使用相同的标识符,因此需要重新分配标识符以确保其唯一性。
习惯上把一个关系模式的主码属性列在其他属性前面;例如,department中的dept_name属性最先列出,因为它是主码。主码属性还加上了下划线。
一个关系模式(如r1)可能在它的属性中包括另一个关系模式(如r2)的主码。这个属性在r1上称作参照r2的外码(foreign key)。关系r1也称为外码依赖的参照关系(referencing relation),r2叫做外码的被参照关系(referenced relation)。例如,instructor中的dept_name属性在instructor上是外码,它参照department,因为dept_name是department的主码。在任意的数据库实例中,从instructor关系中任取一个元组,比如ta,在department关系中必定存在某个元组,比如tb,使得ta在dept_name属性上的取值与tb在主码dept_name上的取值相同。
现在考察section和teaches关系。如下需求是合理的:如果一门课程是分段授课的,那么它必须至少由一位教师来讲授;当然它可能由不止一位教师来讲授。为了施加这种约束,我们需要保证如果一个特定的(course_id, sec_id, semester, year)组合出现在section中,那么该组合也必须出现在teaches中。可是,这组值并不构成teaches的主码,因为不止一位教师可能讲授同一个这样的课程段。其结果是,我们不能声明从section到teaches的外码约束(尽管我们可以在相反的方向上声明从teaches到section的外码约束)。
从section到teaches的约束是参照完整性约束(referential integrity constraint)的一个例子。参照完整性约束要求在参照关系中任意元组在特定属性上的取值必然等于被参照关系中某个元组在特定属性上的取值。
2.4模式图
一个含有主码和外码依赖的数据库模式可以用模式图(schema diagram)来表示。图28展示了我们大学组织的模式图。每一个关系用一个矩形来表示,关系的名字显示在矩形上方,矩形内列出各属性。主码属性用下划线标注。外码依赖用从参照关系的外码属性到被参照关系的主码属性之间的箭头来表示。
图28大学数据库的模式图
除外码约束之外,模式图中没有显示表示出参照完整性约束。在后面第7章,我们将学习一种不同的、称作实体-联系图的图形化表示。实体-联系图有助于我们表示几种约束,包括通用的参照完整性约束。
很多数据库系统提供图形化用户界面设计工具来建立模式图。我们将在第7章详细讨论模式的图形化表示。
在后面的章节中我们使用大学作为例子。图29给出了我们在例子中使用的关系模式,其中主码属性被标上了下划线。正如我们将在第3章中看到的一样,这对应于在SQL的数据定义语言中定义关系的方法。
图29大学数据库模式
2.5关系查询语言
查询语言(query language)是用户用来从数据库中请求获取信息的语言。这些语言通常比标准的程序设计语言层次更高。查询语言可以分为过程化的和非过程化的。在过程化语言(procedural language)中,用户指导系统对数据库执行一系列操作以计算出所需结果。在非过程化语言(nonprocedural language)中,用户只需描述所需信息,而不用给出获取该信息的具体过程。
实际使用的查询语言既包含过程化方式的成分,又包含非过程化方式的成分。我们从第3章到第5章学习被广泛应用的查询语言SQL。
有一些“纯”查询语言:关系代数是过程化的,而元组关系演算和域关系演算是非过程化的。这些语言简洁且形式化,默认商用语言的“句法修饰”,但它们说明了从数据库中提取数据的基本技术。在第6章,我们详细研究关系代数和关系演算的两种形式,即元组关系演算和域关系演算。关系代数包括一个运算的集合,这些运算以一个或两个关系为输入,产生一个新的关系作为结果。关系演算使用谓词逻辑来定义所需的结果,但不需给出获取结果的特定代数过程。
2.6关系运算
所有的过程化关系查询语言都提供了一组运算,这些运算要么施加于单个关系上,要么施加于一对关系上。这些运算具有一个很好的,并且也是所需的性质:运算结果总是单个的关系。这个性质使得人们可以模块化的方式来组合几种这样的运算。特别是,由于关系查询的结果本身也是关系,所以关系运算可施加到查询结果上,正如施加到给定关系集上一样。
在不同语言中,特定的关系运算的表示是不同的,但都符合我们在本章所描述的通用结构。在第3章,我们将给出在SQL中表达关系运算的特殊方式。
最常用的关系运算是从单个关系(如instructor)中选出满足一些特定谓词(如salary> 85 000美元)的特殊元组。其结果是一个新关系,它是原始关系(instructor)的一个子集。例如,如果我们从图21的instructor关系中选择满足谓词“工资大于85 000美元”的元组,我们得到的结果如图210所示。
图210选择工资大于85 000美元的
instructor元组的查询结果
另一个常用的运算是从一个关系中选出特定的属性(列)。其结果是一个只包含那些被选择属性的新关系。例如,假设我们从图21的instructor关系中只希望列出教师的ID和工资,但不列出name和dept_name的值,那么其结果有ID和salary两个属性,如图211所示。结果中的每个元组都是从instructor关系中的某个元组导出的,不过只具有被选中的属性。
连接运算可以通过下述方式来结合两个关系:把分别来自两个关系的元组对合并成单个元组。
图211从instructor
关系中选取属性ID
和salary的查询结果
有几种不同的方式来对关系进行连接(正如我们将在第3章中看到的)。图212显示了一个连接来自instructor和department表中元组的例子,新元组给出了有关每个教师及其工作所在系的信息。此结果是通过把instructor关系中的每个元组和department关系中对应于教师所在系的元组合并形成的。
图212所示的连接被称作自然连接,在这种连接形式中,对于来自instructor关系的一个元组与department关系中的一个元组来说,如果它们在dept_name属性上的取值相同,那它们就是匹配的。所有这样匹配的元组对都会在连接结果中出现。通常说来,两个关系上的自然连接运算所匹配的元组在两个关系共有的所有属性上取值相同。
笛卡儿积运算从两个关系中合并元组,但不同于连接运算的是,其结果包含来自两个关系元组的所有对,无论它们的属性值是否匹配。
图212instructor关系和department关系的自然连接结果
因为关系是集合,所以我们可以在关系上施加标准的集合运算。并运算在两个“相似结构”的表(比如一个所有毕业生的表和一个所有大学生的表)上执行集合并。例如,我们可以得到一个系中所有学生的集合。另外的集合运算如交和集合差也都可以被执行。
正如我们此前讲到的,我们可以在查询结果上施加运算。例如,如果我们想找出工资超过85 000美元的那些教师的ID和salary,我们可以执行上述例子中的前两种运算。首先我们从instructor关系中选出salary值大于85 000美元的元组,然后从结果中选出ID和salary两个属性,结果关系如图213所示,由ID和salary构成。在此例中,我们可以任一次序来执行运算,但正如我们将看到的,并非在所有情况下均可如此。
图213选择工资大于
85 000美元的教师的ID
和salary属性的结果
有时候,查询结果中包含重复的元组。例如,如果我们从instructor关系中选出dept_name属性,就会有好几种重复的情况,其中包括“Comp.Sci.”,它出现了三次。一些关系语言严格遵守集合的数学定义,去除了重复。另一些考虑到从大的结果关系中去除重复需要大量相关的处理,就保留了重复。在后面这类情况中,关系并非是纯粹数学意义上的真正关系。
当然,数据库中的数据是随时间而改变的。关系可以随新元组的插入、已有元组的删除或更改元组在特定属性上的值而更新。整个关系可被删除,新的关系可被创建。
从第3章到第5章,我们将讨论如何使用SQL语言来表示关系的查询和更新。
关 系 代 数
关系代数定义了在关系上的一组运算,对应于作用在数字上的普通代数运算,如加法、减法或乘法。正如作用在数字上的代数运算以一个或多个数字作为输入,返回一个数字作为输出,关系代数运算通常以一个或两个关系作为输入,返回一个关系作为输出。
第6章将详细介绍关系代数,下面我们给出几个运算的概述:
符号(名字)使用示例
σ(选择)
(投影)
(自然连接)
×(笛卡儿积)
∪(并)
σsalary>=85 000(instructor)
返回输入关系中满足谓词的行
∏ID,salary(instructor)
对输入关系的所有行输出指定的属性。从输出中去除重复元组
instructordepartment
从两个输入关系中输出这样的元组对:它们在具有相同名字的所有属性上取值相同
instructor×department
从两个输入关系中输出所有的元组对(无论它们在共同属性上的取值是否相同)
∏name(instructor)∪∏name(student)
输出两个输入关系中元组的并
2.7总结
关系数据模型(relational data model)建立在表的集合的基础上。数据库系统的用户可以对这些表进行查询,可以插入新元组、删除元组以及更新(修改)元组。表达这些操作的语言有几种。
关系的模式(schema)是指它的逻辑设计,而关系的实例(instance)是指它在特定时刻的内容。数据库的模式和实例的定义是类似的。关系的模式包括它的属性,还可能包括属性类型和关系上的约束,比如主码和外码约束。
关系的超码(superkey)是一个或多个属性的集合,这些属性上的取值保证可以唯一识别出关系中的元组。候选码是一个最小的超码,也就是说,它是一组构成超码的属性集,但这组属性的任意子集都不是超码。关系的一个候选码被选作主码(primary key)。
在参照关系中的外码(foreign key)是这样的一个属性集合:对于参照关系中的每个元组来说,它在外码属性上的取值肯定等于被参照关系中某个元组在主码上的取值。
模式图(schema diagram)是数据库中模式的图形化表示,它显示了数据库中的关系,关系的属性、主码和外码。
关系查询语言(relational query language)定义了一组运算集,这些运算可作用于表上,并输出表作为结果。这些运算可以组合成表达式,表达所需的查询。
关系代数(relational algebra)提供了一组运算,它们以一个或多个关系为输入,返回一个关系作为输出。诸如SQL这样的实际查询语言是基于关系代数的,但增加了一些有用的句法特征。
术语回顾
表
关系
元组
空值
数据库模式
数据库实例
关系模式
关系实例
码
超码
候选码
主码
外码
参照关系
被参照关系
属性
域
原子域
参照完整性约束
模式图
查询语言
过程化语言
非过程化语言
关系运算
选择元组
选择属性
自然连接
笛卡儿积
集合运算
关系代数
实践习题
2.1考虑图214所示关系数据库。这些关系上适当的主码是什么?
2.2考虑从instructor的dept_name属性到department关系的外码约束,给出对这些关系的插入和删除示例,使得它们破坏外码约束。
2.3考虑time_slot关系。假设一个特定的时间段可以在一周之内出现多次,解释为什么day和start_time是该关系主码的一部分,而end_time却不是。
2.4在图21所示instructor的实例中,没有两位教师同名。我们是否可以据此断定name可用来作为instructor的超码(或主码)?
2.5先执行student和advisor的笛卡儿积,然后在结果上执行基于谓词s_id=ID的选择运算,最后的结果是什么?(采用关系代数的符号表示,此查询可写作σs_id=ID(student×advisor)。)
employee(personname, street, city)
works(personname, companyname, salary)
company(companyname, city)
图214习题2.1、习题2.7和习题2.12的关系数据库
2.6考虑下面的表达式,哪些使用了关系代数运算的结果来作为另一个运算的输入?对于每个表达式,说明表达式的含义。
a.(σyear≥2009(takes) student)
b.(σyear≥2009(takes student)
c.∏ID,name,course_id (student takes)
2.7考虑图214所示关系数据库。给出关系代数表达式来表示下列每一个查询:
a.找出居住在“Miami”城市的所有员工姓名。
b.找出工资在100 000美元以上的所有员工姓名。
c.找出居住在“Miami”并且工资在100 000美元以上的所有员工姓名。
2.8考虑图215所示银行数据库。对于下列每个查询,给出一个关系代数表达式:
a.找出位于“Chicago”的所有支行名字。
b.找出在支行“Downtown”有贷款的所有贷款人姓名。
branch(branch_name, branch_city, assets)
customer (customer_name, customer_street, customer_city)
loan (loan_number, branch_name, amount)
borrower (customer_name, loan_number)
account (account_number, branch_name, balance)
depositor (customer_name, account_number)
图215习题2.8、习题2.9和习题2.13的银行数据库
习题
2.9考虑图215所示银行数据库。
a.适当的主码是什么?
b.给出你选择的主码,确定适当的外码。
假定支行名字和客户名字分别唯一地标识各支行和各客户,但贷款和账户可以与一个以上客户相联系。
2.10考虑图28所示advisor关系,advisor的主码是s_id。假设一个学生可以有多位指导老师。那么,s_id还是advisor关系的主码吗?如果不是,advisor的主码会是什么呢?
2.11解释术语关系和关系模式在意义上的区别。
2.12考虑图214所示关系数据库。给出关系代数表达式来表示下列每一个查询:
a.找出为“First Bank Corporation”工作的所有员工姓名。
b.找出为“First Bank Corporation”工作的所有员工的姓名和居住城市。
c.找出为“First Bank Corporation”工作且挣钱超过10 000美元的所有员工的姓名、街道地址和居住城市。
2.13考虑图215所示银行数据库。对于下列每个查询,给出一个关系代数表达式:
a.找出贷款额度超过10 000美元的所有贷款号。
b.找出所有这样的存款人姓名,他拥有一个存款额大于6000美元的账户。
c.找出所有这样的存款人姓名,他在“Uptown”支行拥有一个存款额大于6000美元的账户。
2.14列出在数据库中引入空值的两个原因。
2.15讨论过程化和非过程化语言的相对优点。
文献注解
IBM San Jose研究实验室的E.F.Codd于20世纪60年代末提出了关系模型(Codd [1970])。这一工作使Codd在1981年获得了声望很高的ACM图灵奖(Codd[1982])。
在Codd最初的论文发表之后,几个研究项目开始进行,它们的目标是构造实际的关系数据库系统,其中包括IBM San Jose研究实验室的System R、加州大学Berkeley分校的Ingres,以及IBM T.J.Watson 研究实验中心的QuerybyExample。
大量关系数据库产品现在可以从市场上购得。其中包括IBM的DB2以及Informix、Oracle、Sybase和微软的SQL Server。开源关系数据库系统包括MySQL和PostgreSQL。微软的Access是一个单用户的数据库产品,它作为微软Office套件的一部分。
Atzeni和Antonellis[1993]、Maier[1983]以及Abiteboul等[1995]是专门讨论关系数据模型的文献。
第3章
Database System Concepts,6E
SQL
商业性使用或实验性使用的数据库查询语言有好几种。在本章以及第4章和第5章,我们学习使用最为广泛的查询语言:SQL。
尽管我们说SQL语言是一种“查询语言”,但是除了数据库查询,它还具有很多别的功能,它可以定义数据结构、修改数据库中的数据以及说明安全性约束条件等。
我们的目的并不是提供一个完整的SQL用户手册,而是介绍SQL的基本结构和概念。SQL的各种实现可能在一些细节上有所不同,或者只支持整个语言的一个子集。
3.1SQL查询语言概览
SQL最早的版本是由IBM开发的,它最初被叫做Sequel,在20世纪70年代早期作为System R项目的一部分。Sequel语言一直发展至今,其名称已变为SQL(结构化查询语言)。现在有许多产品支持SQL语言,SQL已经很明显地确立了自己作为标准的关系数据库语言的地位。
1986年美国国家标准化组织(ANSI)和国际标准化组织(ISO)发布了SQL标准:SQL86。1989年ANSI发布了一个SQL的扩充标准:SQL89。该标准的下一个版本是SQL92标准,接着是SQL:1999,SQL:2003,SQL:2006,最新的版本是SQL:2008。文献注解中提供了关于这些标准的参考文献。
SQL语言有以下几个部分:
数据定义语言(DataDefinition Language, DDL):SQL DDL提供定义关系模式、删除关系以及修改关系模式的命令。
数据操纵语言(DataManipulation Language, DML):SQL DML提供从数据库中查询信息,以及在数据库中插入元组、删除元组、修改元组的能力。
完整性(integrity):SQL DDL包括定义完整性约束的命令,保存在数据库中的数据必须满足所定义的完整性约束。破坏完整性约束的更新是不允许的。
视图定义(view definition):SQL DDL包括定义视图的命令。
事务控制(transaction control):SQL包括定义事务的开始和结束的命令。
嵌入式SQL和动态SQL(embedded SQL and dynamic SQL):嵌入式和动态SQL定义SQL语句如何嵌入到通用编程语言,如C、C++和Java中。
授权(authorization):SQL DDL包括定义对关系和视图的访问权限的命令。
本章我们给出对SQL的基本DML和DDL特征的概述。在此描述的特征自SQL92以来就一直是SQL标准的部分。
在第4章我们提供对SQL查询语言更详细的介绍,包括:(a) 各种连接的表达;(b) 视图;(c) 事务;(d) 完整性约束;(e) 类型系统;(f) 授权。
在第5章我们介绍SQL语言更高级的特征,包括:(a) 允许从编程语言中访问SQL的机制;(b) SQL函数和过程;(c) 触发器;(d) 递归查询;(e) 高级聚集特征;(f) 为数据分析设计的一些特征,它们在SQL:1999中引入,并在SQL的后续版本中使用。
尽管大多数SQL实现支持我们在此描述的标准特征,读者还是应该意识到不同SQL实现之间的差异。大多数SQL实现还支持一些非标准的特征,但不支持一些更高级的特征。万一你发现在此描述的一些语言特征在你使用的系统中不起作用,请参考你的数据库系统用户手册,看看它所支持的特征究竟是什么。
3.2SQL数据定义
数据库中的关系集合必须由数据定义语言(DDL)指定给系统。SQL的DDL不仅能够定义一组关系,还能够定义每个关系的信息,包括:
每个关系的模式。
每个属性的取值类型。
完整性约束。
每个关系维护的索引集合。
每个关系的安全性和权限信息。
每个关系在磁盘上的物理存储结构。
我们在此只讨论基本模式定义和基本类型,对SQL DLL其他特征的讨论将放到第4章和第5章进行。
3.2.1基本类型
SQL标准支持多种固有类型,包括:
char(n):固定长度的字符串,用户指定长度n。也可以使用全称character。
varchar(n):可变长度的字符串,用户指定最大长度n,等价于全称character varying。
int:整数类型(和机器相关的整数的有限子集),等价于全称integer。
smallint:小整数类型(和机器相关的整数类型的子集)。
numeric(p,d):定点数,精度由用户指定。这个数有p位数字(加上一个符号位),其中d位数字在小数点右边。所以在一个这种类型的字段上,numeric(3,1)可以精确储存44.5,但不能精确存储444.5或0.32这样的数。
real,double precision:浮点数与双精度浮点数,精度与机器相关。
float(n):精度至少为n位的浮点数。
更多类型将在4.5节介绍。
每种类型都可能包含一个被称作空值的特殊值。空值表示一个缺失的值,该值可能存在但并不为人所知,或者可能根本不存在。在可能的情况下,我们希望禁止加入空值,正如我们马上将看到的那样。
char数据类型存放固定长度的字符串。例如,属性A的类型是char(10)。如果我们为此属性存入字符串“Avi”,那么该字符串后会追加7个空格来使其达到10个字符的串长度。反之,如果属性B的类型是varchar(10),我们在属性B中存入字符串“Avi”,则不会增加空格。当比较两个char类型的值时,如果它们的长度不同,在比较之前会自动在短值后面加上额外的空格以使它们的长度一致。
当比较一个char类型和一个varchar类型的时候,也许读者会期望在比较之前会自动在varchar类型后面加上额外的空格以使长度一致;然而,这种情况可能发生也可能不发生,这取决于数据库系统。其结果是,即便上述属性A和B中存放的是相同的值“Avi”,A=B的比较也可能返回假。我们建议始终使用varchar类型而不是char类型来避免这样的问题。
SQL也提供nvarchar类型来存放使用Unicode表示的多语言数据。然而,很多数据库甚至允许在varchar类型中存放Unicode(采用UTF8表示)。
3.2.2基本模式定义
我们用create table命令定义SQL关系。下面的命令在数据库中创建了一个department关系。
create table department
(dept_name varchar (20),
building varchar (15),
budget numeric (12,2),
primary key (dept_name));
上面创建的关系具有三个属性,dept_name是最大长度为20的字符串,building是最大长度为15的字符串,budget是一个12位的数,其中2位数字在小数点后面。create table命令还指明了dept_name属性是department关系的主码。
create table命令的通用形式是:
create table r
(A1D1,
A2D2,
…,
AnDn,
<完整性约束1>,
…,
< 完整性约束k>);
其中r是关系名,每个Ai是关系r模式中的一个属性名,Di是属性Ai的域,也就是说Di指定了属性Ai的类型以及可选的约束,用于限制所允许的Ai取值的集合。
create table命令后面用分号结束,本章后面的其他SQL语句也是如此,在很多SQL实现中,分号是可选的。
SQL支持许多不同的完整性约束。在本节我们只讨论其中少数几个:
primary key(Aj1,Aj2,…,A jm):primarykey声明表示属性Aj1,Aj2,…,A jm构成关系的主码。主码属性必须非空且唯一,也就是说没有一个元组在主码属性上取空值,关系中也没有两个元组在所有主码属性上取值相同。虽然主码的声明是可选的,但为每个关系指定一个主码通常会更好。
foreign key(Ak1 , Ak2, …, Akn)references :foreign key声明表示关系中任意元组在属性(Ak1 , Ak2, …, Akn)上的取值必须对应于关系s中某元组在主码属性上的取值。
图31给出了我们在书中使用的大学数据库的部分SQL DDL定义。course表的定义中声明了“foreign key(dept_name) references department”。此外码声明表明对于每个课程元组来说,该元组所表示的系名必然存在于department关系的主码属性(dept_name)中。没有这个约束的话,就可能有某门课程指定了一个不存在的系名。图31还给出了表section、instructor和teaches上的外码约束。
create table department
(dept_name varchar(20),
building varchar (15),
budget numeric (12,2),
primary key (dept_name));
create table course
(course_id varchar (7),
title varchar (50),
dept_name varchar (20),
credits numeric (2,0),
primary key (course_id),
foreign key (dept_name) references department);
create table instructor
(ID varchar (5),
name varchar (20) not null,
dept_name varchar (20),
salary numeric (8,2),
primary key (ID),
foreign key (dept_name) references department);
create table section
(course_id varchar (8),
sec_id varchar (8),
semester varchar (6),
year numeric (4,0),
building varchar (15),
room_number varchar (7),
time_slot_id varchar (4),
primary key (course_id, sec_id, semester, year),
foreign key (course_id) references course);
create table teaches
(ID varchar (5),
course_id varchar (8),
sec_id varchar (8),
semester varchar (6),
year numeric (4,0),
primary key (ID, course_id, sec_id, semester, year),
foreign key (course_id, sec_id, semester, year) references section,
foreign key (ID) references instructor);
图31大学数据库的部分SQL数据定义
not null:一个属性上的not null约束表明在该属性上不允许空值。换句话说,此约束把空值排除在该属性域之外。例如在图31中,instructor关系的name属性上的not null约束保证了教师的姓名不会为空。
有关外码约束的更多细节以及create table命令可能包含的其他完整性约束将在后面4.4节介绍。
SQL禁止破坏完整性约束的任何数据库更新。例如,如果关系中一条新插入或新修改的元组在任意一个主码属性上有空值,或者元组在主码属性上的取值与关系中的另一个元组相同,SQL将标记一个错误,并阻止更新。类似地,如果插入的course元组在dept_name上的取值没有出现在department关系中,就会破坏course上的外码约束,SQL会阻止这种插入的发生。
一个新创建的关系最初是空的。我们可以用insert命令将数据加载到关系中。例如,如果我们希望插入如下事实:在Biology系有一个名叫Smith的教师,其instructor_id为10211,工资为66 000美元,可以这样写:
insert into instructor
values (10211, ’Smith’, ’Biology’, 66000);
值被给出的顺序应该遵循对应属性在关系模式中列出的顺序。插入命令有很多有用的特性,后面将在3.9.2节进行更详细的介绍。
我们可以使用delete命令从关系中删除元组。命令
delete from student;
将从student关系中删除所有元组。其他格式的删除命令允许指定待删除的元组;我们将在3.9.1节对删除命令进行更详细的介绍。
如果要从SQL数据库中去掉一个关系,我们使用drop table命令。drop table命令从数据库中删除关于被去掉关系的所有信息。命令
drop table r;
是比
delete from r;
更强的语句。后者保留关系r,但删除r中的所有元组。前者不仅删除r的所有元组,还删除r的模式。一旦r被去掉,除非用create table命令重建r,否则没有元组可以插入到r中。
我们使用alter table命令为已有关系增加属性。关系中的所有元组在新属性上的取值将被设为null。alter table命令的格式为:
alter table r add A D;
其中r是现有关系的名字,A是待添加属性的名字,D是待添加属性的域。我们可以通过命令
alter table r drop A;
从关系中去掉属性。其中r是现有关系的名字,A是关系的一个属性的名字。很多数据库系统并不支持去掉属性,尽管它们允许去掉整个表。
图32“select name from
instructor”的结果
3.3SQL查询的基本结构
SQL查询的基本结构由三个子句构成:select、from和where。查询的输入是在from子句中列出的关系,在这些关系上进行where和select子句中指定的运算,然后产生一个关系作为结果。我们通过例子介绍SQL的语法,后面再描述SQL查询的通用结构。
3.3.1单关系查询
我们考虑使用大学数据库例子的一个简单查询:“找出所有教师的名字”。教师的名字可以在instructor关系中找到,因此我们把该关系放到from子句中。教师的名字出现在name属性中,因此我们把它放到select子句中。
select name
from instructor;
其结果是由属性名为name的单个属性构成的关系。如果instructor关系如图21所示,那么上述查询的结果关系如图32所示。
现在考虑另一个查询:“找出所有教师所在的系名”,此查询可写为:
select dept_name
from instructor;
因为一个系有多个教师,所以在instructor关系中,一个系的名称可以出现不止一次。上述查询的结果是一个包含系名的关系,如图33所示。
图33“select dept_name
from instructor”的结果
在关系模型的形式化数学定义中,关系是一个集合。因此,重复的元组不会出现在关系中。在实践中,去除重复是相当费时的,所以SQL允许在关系以及SQL表达式结果中出现重复。因此,在上述SQL查询中,每个系名在instructor关系的元组中每出现一次,都会在查询结果中列出一次。
有时候我们想要强行删除重复,可在select后加入关键词distinct。如果我们想去除重复,可将上述查询重写为:
select distinct dept_name
from instructor;
在上述查询的结果中,每个系名最多只出现一次。
SQL允许我们使用关键词all来显式指明不去除重复:
select all dept_name
from instructor;
既然保留重复元组是默认的,在例子中我们将不再使用all。为了保证在我们例子的查询结果中删除重复元组,我们将在所有必要的地方使用distinct。
select子句还可带含有+、-、*、/运算符的算术表达式,运算对象可以是常数或元组的属性。例如,查询
select ID, name, dept_name, salary* 1.1
from instructor;
返回一个与instructor一样的关系,只是属性salary的值是原来的1.1倍。这显示了如果我们给每位教师增长10%的工资的结果。注意这并不导致对instructor关系的任何改变。
SQL还提供了一些特殊数据类型,如各种形式的日期类型,并允许一些作用于这些类型上的算术函数。我们在4.5.1节进一步讨论这个问题。
where子句允许我们只选出那些在from子句的结果关系中满足特定谓词的元组。考虑查询“找出所有在Computer Science系并且工资超过70 000美元的教师的姓名”,该查询用SQL可以写为:
select name
from instructor
where dept_name = ‘Comp.Sci.’ and salary> 70000;
如果instructor关系如图21所示,那么上述查询的结果关系如图34所示。
图34“找出所有在Computer
Science系并且工资超过70 000
美元的教师的姓名”的结果
SQL允许在where子句中使用逻辑连词and、or和not。逻辑连词的运算对象可以是包含比较运算符<、<=、>、>=、=和<>的表达式。SQL允许我们使用比较运算符来比较字符串、算术表达式以及特殊类型,如日期类型。
在本章的后面,我们将研究where子句谓词的其他特征。
3.3.2多关系查询
到此为止我们的查询示例都是基于单个关系的。通常查询需要从多个关系中获取信息。我们现在来学习如何书写这样的查询。
作为一个示例,假设我们想回答这样的查询:“找出所有教师的姓名,以及他们所在系的名称和系所在建筑的名称”。
考虑instructor关系的模式,我们发现可以从dept_name属性得到系名,但是系所在建筑的名称是在department关系的building属性中给出的。为了回答查询,instructor关系中的每个元组必须与department关系中的元组匹配,后者在dept_name上的取值相配于instructor元组在dept_name上的取值。
为了在SQL中回答上述查询,我们把需要访问的关系都列在from子句中,并在where子句中指定匹配条件。上述查询可用SQL写为:
select name, instructor.dept_name, building
from instructor, department
where instructor.dept_name = department.dept_name;
如果instructor和department关系分别如图21和图25所示,那么此查询的结果关系如图35所示。
图35“找出所有教师的姓名,以及他们
所在系的名称和系所在建筑的名称”的结果
注意dept_name属性既出现在instructor关系中,也出现在department中,关系名被用作前缀(在instructor.dept_name和department.dept_name中)来说明我们使用的是哪个属性。相反,属性name和building只出现在一个关系中,因而不需要把关系名作为前缀。
这种命名惯例需要出现在from子句中的关系具有可区分的名字。在某些情况下这样的要求会引发问题,比如当需要把来自同一个关系的两个不同元组的信息进行组合的时候。在3.4.1节,我们将看到如何使用更名运算来避免这样的问题。
现在我们考虑涉及多个关系的SQL查询的通用形式。正如我们前面已经看到的,一个SQL查询可以包括三种类型的子句:select子句、from子句和where子句。每种子句的作用如下:
select子句用于列出查询结果中所需要的属性。
from子句是一个查询求值中需要访问的关系列表。
where子句是一个作用在from子句中关系的属性上的谓词。
一个典型的SQL查询具有如下形式:
select A1, A2, … , An
from r1, r2, … , rm
where P;
每个Ai代表一个属性,每个ri代表一个关系。P是一个谓词。如果省略where子句,则谓词P为true。
尽管各子句必须以select、from、where的次序写出,但理解查询所代表运算的最容易的方式是以运算的顺序来考察各子句:首先是from,然后是where,最后是select实践中,SQL也许会将表达式转换成更高效执行的等价形式。我们将把效率问题推迟到第11章中探讨。 。
通过from子句定义了一个在该子句中所列出关系上的笛卡儿积。它可以用集合理论来形式化地定义,但最好通过下面的迭代过程来理解,此过程可为from子句的结果关系产生元组。
for each 元组 t1 in 关系 r1
for each 元组 t2 in 关系 r2
…
for each 元组 tm in 关系 rm
把t1, t2, …, tm 连接成单个元组 t
把 t加入结果关系中
此结果关系具有来自from子句中所有关系的所有属性。由于在关系ri和rj中可能出现相同的属性名,正如我们此前所看到的,我们在属性名前加上关系名作为前缀,表示该属性来自于哪个关系。
例如,关系instructor和teaches的笛卡儿积的关系模式为:
(instructor.ID, instructor.name, instructor.dept_name, instructor.salary
teaches.ID, teaches.course_id, teaches.sec_id, teaches.semester, teaches.year)
有了这个模式,我们可以区分出instructor.ID和teaches.ID。对于那些只出现在单个模式中的属性,我们通常去掉关系名前缀。这种简化并不会造成任何混淆。这样我们可以把关系模式写为:
(instructor.ID, name, dept_name, salary
teaches.ID, course_id, sec_id, semester, year)
为举例说明,考察图21中的instructor关系和图27中的teaches关系。它们的笛卡儿积如图36所示,图中只包括了构成笛卡儿积结果的一部分元组。注意为了减小图36中表的宽度,我们把instructor.ID更名为inst.ID。
图36instructor关系和teaches关系的笛卡儿积
通过笛卡儿积把来自instructor和teaches中相互没有关联的元组组合起来。instructor中的每个元组和teaches中的所有元组都要进行组合,即使是那些代表不同教师的元组。其结果可能是一个非常庞大的关系,创建这样的笛卡儿积通常是没有意义的。
反之,where子句中的谓词用来限制笛卡儿积所建立的组合,只留下那些对所需答案有意义的组合。我们希望有个涉及instructor和teaches的查询,它把instructor中的特定元组t只与teaches中表示跟t相同教师的元组进行组合。也就是说,我们希望把teaches元组只和具有相同ID值的instructor元组进行匹配。下面的SQL查询满足这个条件,从这些匹配元组中输出教师名和课程标识。
select name, course_id
from instructor, teaches
where instructor.ID = teaches.ID;
注意上述查询只输出讲授了课程的教师,不会输出那些没有讲授任何课程的教师。如果我们希望输出那样的元组,可以使用一种被称作外连接的运算,外连接将在4.1.2节讲述。
如果instructor关系如图21所示,teaches关系如图27所示,那么前述查询的结果关系如图37所示。注意教师Gold、Califieri和Singh,由于他们没有讲授任何课程,就不出现在上述结果中。
图37“对于大学中所有讲授课程
的教师,找出他们的姓名以及所
讲述的所有课程标识”的结果
如果我们只希望找出Computer Science系的教师名和课程标识,我们可以在where子句中增加另外的谓词,如下所示:
select name, course_id
from instructor, teaches
where instructor.ID = teaches.ID and instructor.dept_name = ’Comp.Sci.’;
注意既然dept_name属性只出现在instructor关系中,我们在上述查询中可以只使用dept_name来替代instructor.dept_name。
通常说来,一个SQL查询的含义可以理解如下:
1.为from子句中列出的关系产生笛卡儿积。
2.在步骤1的结果上应用where子句中指定的谓词。
3.对于步骤2结果中的每个元组,输出select子句中指定的属性(或表达式的结果)。
上述步骤的顺序有助于明白一个SQL查询的结果应该是什么样的,而不是这个结果是怎样被执行的。在SQL的实际实现中不会执行这种形式的查询,它会通过(尽可能)只产生满足where子句谓词的笛卡儿积元素来进行优化执行。我们在后面第11章学习那样的实现技术。
当书写查询时,需要小心设置合适的where子句条件。如果在前述SQL查询中省略where子句条件,就会输出笛卡儿积,那是一个巨大的关系。对于图21中的instructor样本关系和图27中的teaches样本关系,它们的笛卡儿积具有12×13=156个元组,比我们在书中能显示的还要多!在更糟的情况下,假设我们有比图中所示样本关系更现实的教师数量,比如200个教师。假使每位教师讲授3门课程,那么我们在teaches关系中就有600个元组。这样上述迭代过程会产生出200×600=120 000个元组作为结果。
3.3.3自然连接
在我们的查询示例中,需要从instructor和teaches表中组合信息,匹配条件是需要instructor.ID等于teaches.ID。这是在两个关系中具有相同名称的所有属性。实际上这是一种通用的情况,也就是说,from子句中的匹配条件在最通常的情况下需要在所有匹配名称的属性上相等。
为了在这种通用情况下简化SQL编程者的工作,SQL支持一种被称作自然连接的运算,下面我们就来讨论这种运算。事实上SQL还支持几种另外的方式使得来自两个或多个关系的信息可以被连接(join)起来。我们已经见过怎样利用笛卡儿积和where子句谓词来连接来自多个关系的信息。连接来自多个关系信息的其他方式在4.1节介绍。
自然连接(natural join)运算作用于两个关系,并产生一个关系作为结果。不同于两个关系上的笛卡儿积,它将第一个关系的每个元组与第二个关系的所有元组都进行连接;自然连接只考虑那些在两个关系模式中都出现的属性上取值相同的元组对。因此,回到instructor和teaches关系的例子上,instructor和teaches的自然连接计算中只考虑这样的元组对:来自instructor的元组和来自teaches的元组在共同属性ID上的取值相同。
结果关系如图38所示,只有13个元组,它们给出了关于每个教师以及该教师实际讲授的课程的信息。注意我们并没有重复列出那些在两个关系模式中都出现的属性,这样的属性只出现一次。还要注意列出属性的顺序:先是两个关系模式中的共同属性,然后是那些只出现在第一个关系模式中的属性,最后是那些只出现在第二个关系模式中的属性。
图38instructor关系和teaches关系的自然连接
考虑查询“对于大学中所有讲授课程的教师,找出他们的姓名以及所讲述的所有课程标识”,此前我们曾把该查询写为:
select name, course_id
from instructor, teaches
where instructor.ID = teaches.ID;
该查询可以用SQL的自然连接运算更简洁地写作:
select name, course_id
from instructor natural join teaches;
以上两个查询产生相同的结果。
正如我们此前所见,自然连接运算的结果是关系。从概念上讲,from子句中的“instructor natural join teaches”表达式可以替换成执行该自然连接后所得到的关系。其结果是不可能用包含了原始关系名的属性名来指代自然连接结果中的属性,例如instructor.name或teaches.course_id,但是我们可以使用诸如name和course_id那样的属性名,而不带关系名。 然后在这个关系上执行where和select子句,就如我们在前面3.3.2节所看到的那样。
在一个SQL查询的from子句中,可以用自然连接将多个关系结合在一起,如下所示:
select A1, A2, …, An
from r1 natural join r2 natural join …natural join rm
where P;
更为一般地,from子句可以为如下形式:
from E1, E2, …, En
其中每个Ei可以是单个关系或一个包含自然连接的表达式。例如,假设我们要回答查询“列出教师的名字以及他们所讲授课程的名称”。此查询可以用SQL写为:
select name, title
from instructor natural join teaches, course
where teaches.course_id= course.course_id;
先计算instructor和teaches的自然连接,正如我们此前所见,再计算该结果和course的笛卡儿积,where子句从这个结果中提取出这样的元组:来自连接结果的课程标识与来自course关系的课程标识相匹配。注意where子句中的teaches.course_id表示自然连接结果中的course_id域,因为该域最终来自teaches关系。
相反,下面的SQL查询不会计算出相同的结果:
select name, title
from instructor natural join teaches natural join course;
为了说明原因,注意instructor和teaches的自然连接包括属性(ID, name, dept_name, salary, course_id, sec_id),而course关系包含的属性是(course_id, title, dept_name, credits)。作为这二者自然连接的结果,需要来自这两个输入的元组既要在属性dept_name上取值相同,还要在course_id上取值相同。该查询将忽略所有这样的(教师姓名,课程名称)对:其中教师所讲授的课程不是他所在系的课程。而前一个查询会正确输出这样的对。
为了发扬自然连接的优点,同时避免不必要的相等属性带来的危险,SQL提供了一种自然连接的构造形式,允许用户来指定需要哪些列相等。下面的查询说明了这个特征:
select name, title
from (instructor natural join teaches) join course using (course_id);
join…using运算中需要给定一个属性名列表,其两个输入中都必须具有指定名称的属性。考虑运算r1 join r2 using(A1, A2),它与r1和r2的自然连接类似,只不过在t1.A1=t2.A1并且t1.A2=t2.A2成立的前提下,来自r1的元组t1和来自r2的元组t2就能匹配,即使r1和r2都具有名为A3的属性,也不需要t1.A3=t2.A3成立。
这样,在前述SQL查询中,连接构造允许teaches.dept_name和course.dept_name是不同的,该SQL查询给出了正确的答案。
3.4附加的基本运算
SQL中还支持几种附加的基本运算。
3.4.1更名运算
重新考察我们此前使用过的查询:
select name, course_id
from instructor, teaches
where instructor.ID = teaches.ID;
此查询的结果是一个具有下列属性的关系:
name, course_id
结果中的属性名来自from子句中关系的属性名。
但我们不能总是用这个方法派生名字,其原因有几点:首先,from子句的两个关系中可能存在同名属性,在这种情况下,结果中就会出现重复的属性名;其次,如果我们在select子句中使用算术表达式,那么结果属性就没有名字;再次,尽管如上例所示,属性名可以从基关系导出,但我们也许想要改变结果中的属性名字。因此,SQL提供了一个重命名结果关系中属性的方法。即使用如下形式的as子句:
oldname as newname
as子句既可出现在select子句中,也可出现在from子句中。
其结果是,有可能在某些系统中不能用包含了原始关系名的属性名来指代自然连接结果中的属性,例如instructor.name或teaches.course_id。在某些系统中允许这样做,某些系统中不允许,某些系统中对于除连接属性(即在两个关系模式中同时出现的属性)之外的其他属性允许。然而,我们可以不带关系名地使用诸如name和course_id这样的属性名。
例如,如果我们想用名字instructor_name来代替属性名name,我们可以重写上述查询如下:
select name as instructor_name, course_id
from instructor, teaches
where instructor.ID= teaches.ID;
as子句在重命名关系时特别有用。重命名关系的一个原因是把一个长的关系名替换成短的,这样在查询的其他地方使用起来就更为方便。为了说明这一点,我们重写查询“对于大学中所有讲授课程的教师,找出他们的姓名以及所讲述的所有课程标识”:
select T.name, S.course_id
from instructor as T, teaches as S
where T.ID = S.ID;
重命名关系的另一个原因是为了适用于需要比较同一个关系中的元组的情况。为此我们需要把一个关系跟它自身进行笛卡儿积运算,如果不重命名的话,就不可能把一个元组与其他元组区分开来。假设我们希望写出查询:“找出满足下面条件的所有教师的姓名,他们的工资至少比Biology系某一个教师的工资要高”,我们可以写出这样的SQL表达式:
select distinct T.name
from instructor as T, instructor as S
where T.salary> S.salary and S.dept_name = ’Biology’;
注意我们不能使用instructor.salary这样的写法,因为这样并不清楚到底是希望引用哪一个instructor。
在上述查询中,T和S可以被认为是instructor关系的两个拷贝,但更准确地说是被声明为instructor关系的别名,也就是另外的名字。像T和S那样被用来重命名关系的标识符在SQL标准中被称作相关名称(correlation name),但通常也被称作表别名(table alias),或者相关变量(correlation variable),或者元组变量(tuple variable)。
注意用文字表达上述查询更好的方式是:“找出满足下面条件的所有教师的姓名,他们比Biology系教师的最低工资要高”。我们早先的表述更符合我们所写的SQL,但后面的表述更直观,事实上它可以直接用SQL来表达,正如我们将在3.8.2节看到的那样。
3.4.2字符串运算
SQL使用一对单引号来标示字符串,例如‘Computer’。如果单引号是字符串的组成部分,那就用两个单引号字符来表示,如字符串“it′s right”可表示为“it″s right”。
在SQL标准中,字符串上的相等运算是大小写敏感的,所以表达式“′comp.sci.′=′Comp.Sci.′”的结果是假。然而一些数据库系统,如MySQL和SQL Server,在匹配字符串时并不区分大小写,所以在这些数据库中“′comp.sci.′=′Comp.Sci.′”的结果可能是真。然而这种默认方式是可以在数据库级或特定属性级被修改的。
SQL还允许在字符串上有多种函数,例如串联(使用“‖”)、提取子串、计算字符串长度、大小写转换(用upper(s)将字符串s转换为大写或用lower(s)将字符串s转换为小写)、去掉字符串后面的空格(使用trim(s)),等等。不同数据库系统所提供的字符串函数集是不同的,请参阅你的数据库系统手册来获得它所支持的实际字符串函数的详细信息。
在字符串上可以使用like操作符来实现模式匹配。我们使用两个特殊的字符来描述模式:
百分号(%):匹配任意子串。
下划线(_):匹配任意一个字符。
模式是大小写敏感的,也就是说,大写字符与小写字符不匹配,反之亦然。为了说明模式匹配,考虑下列例子:
‘Intro%’匹配任何以“Intro”打头的字符串。
‘%Comp%’匹配任何包含“Comp”子串的字符串,例如‘Intro.to Computer Science’和‘Computational Biology’。
‘___’匹配只含三个字符的字符串。
‘___%’匹配至少含三个字符的字符串。
在SQL中用比较运算符like来表达模式。考虑查询“找出所在建筑名称中包含子串‘Watson’的所有系名”,该查询的写法如下:
select dept_name
from department
where building like ’%Watson%’;
为使模式中能够包含特殊模式的字符(即%和_),SQL允许定义转义字符。转义字符直接放在特殊字符的前面,表示该特殊字符被当成普通字符。我们在like比较运算中使用escape关键词来定义转义字符。为了说明这一用法,考虑以下模式,它使用反斜线(\)作为转义字符 :
like ‘ab\%cd%’ escape ‘\’ 匹配所有以“ab%cd”开头的字符串。
like ‘ab\\cd%’ escape ‘\’ 匹配所有以“ab\cd”开头的字符串。
SQL允许使用not like比较运算符搜寻不匹配项。一些数据库还提供like运算的变体,不区分大小写。
在SQL:1999中还提供similar to操作,它具备比like运算更强大的模式匹配能力。它的模式定义语法类似于UNIX中的正则表达式。
3.4.3select子句中的属性说明
星号“*”可以用在select子句中表示“所有的属性”,因而,如下查询的select子句中使用instructor.*:
select instructor.*
from instructor, teaches
where instructor.ID = teaches.ID;
表示instructor中的所有属性都被选中。形如select *的select子句表示from子句结果关系的所有属性都被选中。
3.4.4排列元组的显示次序
SQL为用户提供了一些对关系中元组显示次序的控制。order by子句就可以让查询结果中元组按排列顺序显示。为了按字母顺序列出在Physics系的所有教师,我们可以这样写:
select name
from instructor
where dept_name = ’Physics’
order by name;
order by子句默认使用升序。要说明排序顺序,我们可以用desc表示降序,或者用asc表示升序。此外,排序可在多个属性上进行。假设我们希望按salary的降序列出整个instructor关系。
如果有几位教师的工资相同,就将它们按姓名升序排列。我们用SQL将该查询表示如下:
select*
from instructor
order by salary desc, name asc;
3.4.5where子句谓词
为了简化where子句,SQL提供between比较运算符来说明一个值是小于或等于某个值,同时大于或等于另一个值的。如果我们想找出工资在90 000美元和100 000美元之间的教师的姓名,我们可以使用between比较运算符,如下所示:
select name
from instructor
where salary between 90000 and 100000;
它可以取代
select name
from instructor
where salary <=100000 and salary >=90000;
类似地,我们还可以使用not between比较运算符。
我们可以扩展前面看到过的查找教师名以及课程标识的查询,但考虑更复杂的情况,要求教师是生物系的:“查找 Biology系讲授了课程的所有教师的姓名和他们所讲授的课程”。为了写出这样的查询,我们可以在前面看到过的两个SQL查询的任意一个的基础上进行修改,在where子句中增加一个额外的条件。我们下面给出修改后的不使用自然连接的SQL查询形式:
select name, course_id
from instructor, teaches
where instructor.ID= teaches.ID and dept_name = ’Biology’;
SQL允许我们用记号(v1, v2, …, vn)来表示一个分量值分别为v1, v2, …, vn的n维元组。在元组上可以运用比较运算符,按字典顺序进行比较运算。例如,(a1, a2) <= (b1, b2)在a1 <= b1 且a2 <= b2时为真。类似地,当两个元组在所有属性上相等时,它们是相等的。这样,前述查询可被重写为如下形式:尽管这是SQL92标准的一部分,但某些SQL实现中可能不支持这种语法。
select name, course_id
from instructor, teaches
where (instructor.ID, dept_name)=(teaches.ID, ’Biology’);
3.5集合运算
SQL作用在关系上的union、intersect和except运算对应于数学集合论中的∪、∩和-运算。我们现在来构造包含在两个集合上使用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;
在我们后面的讨论中,将用c1和c2分别指代包含以上查询结果的两个关系,并在图39和图310中给出作用在如图26所示的section关系上的查询结果。注意c2包含两个对应于course_id为CS319的元组,因为该课程有两个课程段在2010年春季开课。
图39c1关系,列出2009年
秋季开设的课程
图310c2关系,列出2010年
春季开设的课程
3.5.1并运算
为了找出在2009年秋季开课,或者在2010年春季开课或两个学期都开课的所有课程,我们可写查询语句:我们在每条selectfromwhere语句上使用的括号是可省略的,但易于阅读。
图311c1 union c2
的结果
(select course_id
from section
where semester = ’Fall’ and year= 2009)
union
(select course_id
from section
where semester = ’Spring’ and year= 2010);
与select子句不同,union运算自动去除重复。这样,在如图26所示的section关系中,2010年春季开设CS319的两个课程段,CS101在2009年秋季和2010年春季学期各开设一个课程段,CS101和CS319在结果中只出现一次,如图311所示。
如果我们想保留所有重复,就必须用union all代替union:
(select course_id
from section
where semester = ’Fall’ and year= 2009)
union all
(select course_id
from section
where semester = ’Spring’ and year= 2010);
在结果中的重复元组数等于在c1和c2中出现的重复元组数的和。因此在上述查询中,每个CS319和CS101都将被列出两次。作为一个更深入的例子,如果存在这样一种情况:ECE101在2009年秋季学期开设4个课程段,在2010年春季学期开设2个课程段,那么在结果中将有6个ECE101元组。
3.5.2交运算
为了找出在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);
结果关系如图312所示,它只包括一个CS101元组。intersect运算自动去除重复。例如,如果存在这样的情况:ECE101在2009年秋季学期开设4个课程段,在2010年春季学期开设2个课程段,那么在结果中只有1个ECE101元组。
图312c1 intersect c2的结果
如果我们想保留所有重复,就必须用intersect all代替intersect:
(select course_id
from section
where semester = ’Fall’ and year= 2009)
intersect all
(select course_id
from section
where semester = ’Spring’ and year= 2010);
在结果中出现的重复元组数等于在c1和c2中出现的重复次数里最少的那个。例如,如果ECE101在2009年秋季学期开设4个课程段,在2010年春季学期开设2个课程段,那么在结果中有2个ECE101元组。
3.5.3差运算
为了找出在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);
该查询结果如图313所示。注意这正好是图39的c1关系减去不出现的CS101元组。except运算某些SQL实现,特别是Oracle,使用关键词minus代替except。 从其第一个输入中输出所有不出现在第二个输入中的元组,也即它执行集差操作。此运算在执行集差操作之前自动去除输入中的重复。例如,如果ECE101在2009年秋季学期开设4个课程段,在2010年春季学期开设2个课程段,那么在except运算的结果中将没有ECE101的任何拷贝。
图313c1 except c2的结果
如果我们想保留所有重复,就必须用except all代替except:
(select course_id
from section
where semester = ’Fall’ and year= 2009)
except all
(select course_id
from section
where semester = ’Spring’ and year= 2010);
结果中的重复元组数等于在c1中出现的重复元组数减去在c2中出现的重复元组数(前提是此差为正)。因此,如果ECE101在2009年秋季学期开设4个课程段,在2010年春季学期开设2个课程段,那么在结果中有2个ECE101元组。然而,如果ECE101在2009年秋季学期开设2个或更少的课程段,在2010年春季学期开设2个课程段,那么在结果中将不存在ECE101元组。
3.6空值
空值给关系运算带来了特殊的问题,包括算术运算、比较运算和集合运算。
如果算术表达式的任一输入为空,则该算术表达式(涉及诸如 +、-、*或/)结果为空。例如,如果查询中有一个表达式是r.A+5,并且对于某个特定的元组,r.A为空,那么对此元组来说,该表达式的结果也为空。
涉及空值的比较问题更多。例如,考虑比较运算“1 < null”。因为我们不知道空值代表的是什么,所以说上述比较为真可能是错误的。但是说上述比较为假也可能是错误的,如果我们认为比较为假,那么“not (1 < null)”就应该为真,但这是没有意义的。因而SQL将涉及空值的任何比较运算的结果视为unknown(既不是谓词is null,也不是is not null,我们在本节的后面介绍这两个谓词)。这创建了除true和false之外的第三个逻辑值。
由于在where子句的谓词中可以对比较结果使用诸如and、or和not的布尔运算,所以这些布尔运算的定义也被扩展到可以处理unknown值。
and:true and unknown的结果是unknown,false and unknown结果是 false,unknown and unknown 的结果是unknown。
or:true or unknown 的结果是true,false or unknown 结果是unknown,unknown or unknown 结果是unknown。
not:not unknown 的结果是unknown。
可以验证,如果r.A为空,那么“1 < r.A”和“not (1 < r.A)”结果都是unknown。
如果where子句谓词对一个元组计算出false或unknown,那么该元组不能被加入到结果集中。
SQL在谓词中使用特殊的关键词null测试空值。因而为找出instructor关系中salary为空值的所有教师,我们可以写成:
select name
from instructor
where salary is null;
如果谓词is not null所作用的值非空,那么它为真。
某些SQL实现还允许我们使用子句is unknown和is not unknown来测试一个表达式的结果是否为unknown,而不是true或false。
当一个查询使用select distinct子句时,重复元组将被去除。为了达到这个目的,当比较两个元组对应的属性值时,如果这两个值都是非空并且值相等,或者都是空,那么它们是相同的。所以诸如{(’A’,null),(’A’,null)}这样的两个元组拷贝被认为是相同的,即使在某些属性上存在空值。使用distinct子句会保留这样的相同元组的一份拷贝。注意上述对待空值的方式与谓词中对待空值的方式是不同的,在谓词中“null=null”会返回unknown,而不是true。
如果元组在所有属性上的取值相等,那么它们就被当作相同元组,即使某些值为空。上述方式还应用于集合的并、交和差运算。
3.7聚集函数
聚集函数是以值的一个集合(集或多重集)为输入、返回单个值的函数。SQL提供了五个固有聚集函数:
平均值:avg。
最小值:min。
最大值:max。
总和:sum。
计数:count。
sum和avg的输入必须是数字集,但其他运算符还可作用在非数字数据类型的集合上,如字符串。
3.7.1基本聚集
考虑查询“找出Computer Science系教师的平均工资”。我们书写该查询如下:
select avg (salary)
from instructor
where dept_name= ’Comp.Sci.’;
该查询的结果是一个具有单属性的关系,其中只包含一个元组,这个元组的数值对应Computer Science系教师的平均工资。数据库系统可以给结果关系的属性一个任意的名字,该属性是由聚集产生的。然而,我们可以用as子句给属性赋个有意义的名称,如下所示:
select avg (salary) as avg_salary
from instructor
where dept_name= ’Comp.Sci.’;
在图21的instructor关系中,Computer Science系的工资值是75 000美元、65 000美元和92 000美元,平均工资是232 000/3=77 333.33美元。
在计算平均值时保留重复元组是很重要的。假设Computer Science系增加了第四位教师,其工资正好是75 000美元。如果去除重复的话,我们会得到错误的答案(232 000/4=58 000美元),而正确的答案是76 750美元。
有些情况下在计算聚集函数前需先删掉重复元组。如果我们确实想删除重复元组,可在聚集表达式中使用关键词distinct。比方有这样一个查询示例“找出在2010年春季学期讲授一门课程的教师总数”,在该例中不论一个教师讲授了几个课程段,他只应被计算一次。所需信息包含在teaches关系中,我们书写该查询如下:
select count (distinct ID)
from teaches
where semester = ’Spring’ and year = 2010;
我们经常使用聚集函数count计算一个关系中元组的个数。SQL中该函数的写法是count(*)。因此,要找出course关系中的元组数,可写成:
select count(*)
from course;
由于在ID前面有关键字distinct,所以即使某位教师教了不止一门课程,在结果中他也仅被计数一次。
SQL不允许在用count(*)时使用distinct。在用max和min时使用distinct是合法的,尽管结果并无差别。我们可以使用关键词all替代distinct来说明保留重复元组,但是,既然all是默认的,就没必要这么做了。
3.7.2分组聚集
有时候我们不仅希望将聚集函数作用在单个元组集上,而且也希望将其作用到一组元组集上;在SQL中可用group by子句实现这个愿望。
group by子句中给出的一个或多个属性是用来构造分组的。在group by子句中的所有属性上取值相同的元组将被分在一个组中。
图314instructor关系的元组按照
dept_name属性分组
作为示例,考虑查询“找出每个系的平均工资”,该查询书写如下:
select dept_name, avg(salary) as avg_salary
from instructor
group by dept_name;
图314给出了instructor关系中的元组按照dept_name属性进行分组的情况,分组是计算查询结果的第一步。在每个分组上都要进行指定的聚集计算,查询结果如图315所示。
相反,考虑查询“找出所有教师的平均工资”。我们把此查询写作如下形式:
select avg (salary)
from instructor;
在这里省略了group by子句,因此整个关系被当作是一个分组。
图315查询“找出每个系的
平均工资”的结果关系
作为在元组分组上进行聚集操作的另一个例子,考虑查询“找出每个系在2010年春季学期讲授一门课程的教师人数”。有关每位教师在每个学期讲授每个课程段的信息在teaches关系中。但是,这些信息需要与来自instructor关系的信息进行连接,才能够得到每位教师所在的系名。这样,我们把此查询写做如下形式:
select dept_name, count (distinct ID) as instr_count
from instructor natural join teaches
where semester = ’Spring’ and year = 2010
group by dept_name;
其结果如图316所示。
图316查询“找出每个系在2010年
春季学期讲授一门课程的
教师人数”的结果关系
当SQL查询使用分组时,一个很重要的事情是需要保证出现在select语句中但没有被聚集的属性只能是出现在group by子句中的那些属性。换句话说,任何没有出现在group by子句中的属性如果出现在select子句中的话,它只能出现在聚集函数内部,否则这样的查询就是错误的。例如,下述查询是错误的,因为ID没有出现在group by子句中,但它出现在了select子句中,而且没有被聚集:
/* 错误查询 */
select dept_name, ID, avg (salary)
from instructor
group by dept_name;
在一个特定分组(通过dept_name定义)中的每位教师都有一个不同的ID,既然每个分组只输出一个元组,那就无法确定选哪个ID值作为输出。其结果是,SQL不允许这样的情况出现。
3.7.3having子句
有时候,对分组限定条件比对元组限定条件更有用。例如,我们也许只对教师平均工资超过42 000美元的系感兴趣。该条件并不针对单个元组,而是针对group by子句构成的分组。为表达这样的查询,我们使用SQL的having子句。having子句中的谓词在形成分组后才起作用,因此可以使用聚集函数。我们用SQL表达该查询如下:
select dept_name, avg (salary) as avg_salary
from instructor
group by dept_name
having avg (salary) > 42000;
其结果如图317所示。
图317查询“找出系平均工资超过
42 000美元的那些系中教师的平均
工资”的结果关系
与select子句的情况类似,任何出现在having子句中,但没有被聚集的属性必须出现在group by子句中,否则查询就被当成是错误的。
包含聚集、group by或having子句的查询的含义可通过下述操作序列来定义:
1.与不带聚集的查询情况类似,最先根据from子句来计算出一个关系。
2.如果出现了where子句,where子句中的谓词将应用到from子句的结果关系上。
3.如果出现了group by子句,满足where谓词的元组通过group by子句形成分组。如果没有group by子句,满足where谓词的整个元组集被当作一个分组。
4.如果出现了having子句,它将应用到每个分组上;不满足having子句谓词的分组将被抛弃。
5. select子句利用剩下的分组产生出查询结果中的元组,即在每个分组上应用聚集函数来得到单个结果元组。
为了说明在同一个查询中同时使用having子句和where子句的情况,我们考虑查询“对于在2009年讲授的每个课程段,如果该课程段有至少2名学生选课,找出选修该课程段的所有学生的总学分(tot_cred)的平均值”。
select course_id, semester, year, sec_id, avg (tot_cred)
from takes natural join student
where year = 2009
group by course_id, semester, year, sec_id
having count (ID) >= 2;
注意上述查询需要的所有信息来自关系takes和student,尽管此查询是关于课程段的,却并不需要与section进行连接。
3.7.4对空值和布尔值的聚集
空值的存在给聚集运算的处理带来了麻烦。例如,假设instructor关系中有些元组在salary上取空值。考虑以下计算所有工资总额的查询:
select sum(salary)
from instructor;
由于一些元组在salary上取空值,上述查询待求和的值中就包含了空值。SQL标准并不认为总和本身为null,而是认为sum运算符应忽略输入中的null值。
总而言之,聚集函数根据以下原则处理空值:除了count(*)外所有的聚集函数都忽略输入集合中的空值。由于空值被忽略,有可能造成参加函数运算的输入值集合为空集。规定空集的count运算值为0,其他所有聚集运算在输入为空集的情况下返回一个空值。在一些更复杂的SQL结构中空值的影响会更难以琢磨。
在SQL:1999中引入了布尔(boolean)数据类型,它可以取true、false、unknown三个值。有两个聚集函数:some和every,其含义正如直观意义一样,可用来处理布尔(boolean)值的集合。
3.8嵌套子查询
SQL提供嵌套子查询机制。子查询是嵌套在另一个查询中的selectfromwhere表达式。子查询嵌套在where子句中,通常用于对集合的成员资格、集合的比较以及集合的基数进行检查。从3.8.1到3.8.4节我们学习在where子句中嵌套子查询的用法。在3.8.5节我们学习在from子句中嵌套的子查询。在3.8.7节我们将看到一类被称作标量子查询的子查询是如何出现在一个表达式所返回的单个值可以出现的任何地方的。
3.8.1集合成员资格
SQL允许测试元组在关系中的成员资格。连接词in测试元组是否是集合中的成员,集合是由select子句产生的一组值构成的。连接词not in则测试元组是否不是集合中的成员。
作为示例,考虑查询“找出在2009年秋季和2010年春季学期同时开课的所有课程”。先前,我们通过对两个集合进行交运算来书写该查询,这两个集合分别是:2009年秋季开课的课程集合与2010年春季开课的课程集合。现在我们采用另一种方式,查找在2009年秋季开课的所有课程,看它们是否也是2010年春季开课的课程集合中的成员。很明显,这种方式得到的结果与前面相同,但我们可以用SQL中的in连接词书写该查询。我们从找出2010年春季开课的所有课程开始,写出子查询:
(select course_id
from section
where semester = ’Spring’ and year= 2010)
然后我们需要从子查询形成的课程集合中找出那些在2009年秋季开课的课程。为完成此项任务可将子查询嵌入外部查询的where子句中。最后的查询语句是:
select distinct 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);
该例说明了在SQL中可以用多种方法书写同一查询。这种灵活性是有好处的,因为它允许用户用最接近自然的方法去思考查询。我们将看到在SQL中有许多这样的冗余。
我们以与in结构类似的方式使用not in结构。例如,为了找出所有在2009年秋季学期开课,但不在2010年春季学期开课的课程,我们可写出:
select distinct 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);
in和not in操作符也能用于枚举集合。下面的查询找出既不叫“Mozart”,也不叫“Einstein”的教师的姓名:
select distinct name
from instructor
where name not in (’Mozart’, ’Einstein’);
在前面的例子中,我们是在单属性关系中测试成员资格。在SQL中测试任意关系的成员资格也是可以的。例如,我们可以这样来表达查询“找出(不同的)学生总数,他们选修了ID为10101的教师所讲授的课程段”:
select count (distinct ID)
from takes
where (course_id, sec_id, semester, year) in (select course_id, sec_id, semester, year
from teaches
where teaches.ID= 10101);
3.8.2集合的比较
作为一个说明嵌套子查询能够对集合进行比较的例子,考虑查询“找出满足下面条件的所有教师的姓名,他们的工资至少比Biology系某一个教师的工资要高”,在3.4.1节,我们将此查询写作:
select distinct T.name
from instructor as T, instructor as S
where T.salary> S.salary and S.dept_name = ’Biology’;
但是SQL提供另外一种方式书写上面的查询。短语“至少比某一个要大”在SQL中用>some表示。此结构允许我们用一种更贴近此查询的文字表达的形式重写上面的查询:
select name
from instructor
where salary> some (select salary
from instructor
where dept_name = ’Biology’);
子查询
(select salary
from instructor
where dept_name = ’Biology’)
产生Biology系所有教师的所有工资值的集合。当元组的salary值至少比Biology系教师的所有工资值集合中某一成员高时,外层select的where子句中>some的比较为真。
SQL也允许<some,<=some,>=some,=some和<>some的比较。作为练习,请验证=some等价于in,然而<>some并不等价于not in。在SQL中关键词any同义于some。早期SQL版本中仅允许使用any,后来的版本为了避免和英语中any一词在语言上的混淆,又添加了另一个可选择的关键词some。
现在我们稍微修改一下我们的查询。找出满足下面条件的所有教师的姓名,他们的工资值比Biology系每个教师的工资都高。结构>all对应于词组“比所有的都大”。使用该结构,我们写出查询如下:
select name
from instructor
where salary> all (select salary
from instructor
where dept_name = ’Biology’);
类似于some,SQL也允许<all,<=all,>=all,=all和<>all的比较。作为练习,请验证<>all等价于not in,但=all并不等价于in。
作为集合比较的另一个例子,考虑查询“找出平均工资最高的系”。我们首先写一个查询来找出每个系的平均工资,然后把它作为子查询嵌套在一个更大的查询中,以找出那些平均工资大于等于所有系平均工资的系。
select dept_name
from instructor
group by dept_name
having avg (salary) >= all (select avg (salary)
from instructor
group by dept_name);
3.8.3空关系测试
SQL还有一个特性可测试一个子查询的结果中是否存在元组。exists结构在作为参数的子查询非空时返回true值。使用exists结构,我们还能用另外一种方法书写查询“找出在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);
上述查询还说明了SQL的一个特性,来自外层查询的一个相关名称(上述查询中的S)可以用在where子句的子查询中。使用了来自外层查询相关名称的子查询被称作相关子查询(correlated subquery)。
在包含了子查询的查询中,在相关名称上可以应用作用域规则。根据此规则,在一个子查询中只能使用此子查询本身定义的,或者在包含此子查询的任何查询中定义的相关名称。如果一个相关名称既在子查询中定义,又在包含该子查询的查询中定义,则子查询中的定义有效。这条规则类似于编程语言中通用的变量作用域规则。
我们可以用not exists结构测试子查询结果集中是否不存在元组。我们可以使用not exists结构模拟集合包含(即超集)操作:我们可将“关系A包含关系B”写成“not exists(B except A)”。(尽管contains运算符并不是当前SQL标准的一部分,但这一运算符曾出现在某些早期的关系系统中。)为了说明not exists操作符,考虑查询“找出选修了Biology系开设的所有课程的学生”。使用except结构,我们可以书写此查询如下:
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));
这里,子查询
(select course_id
from course
where dept_name = ’Biology’)
找出Biology系开设的所有课程集合。子查询
(select T.course_id
from takes as T
where S.ID = T.ID)
找出S.ID选修的所有课程。这样,外层select对每个学生测试其选修的所有课程集合是否包含Biology系开设的所有课程集合。
3.8.4重复元组存在性测试
SQL提供一个布尔函数,用于测试在一个子查询的结果中是否存在重复元组。如果作为参数的子查询结果中没有重复的元组,unique结构此结构尚未被广泛实现。 将返回true值。我们可以用unique结构书写查询“找出所有在2009年最多开设一次的课程”,如下所示:
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);
注意如果某门课程不在2009年开设,那么子查询会返回一个空的结果,unique谓词在空集上计算出真值。
在不使用unique结构的情况下,上述查询的一种等价表达方式是:
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);
我们可以用not unique结构测试在一个子查询结果中是否存在重复元组。为了说明这一结构,考虑查询“找出所有在2009年最少开设两次的课程”,如下所示:
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);
形式化地,对一个关系的unique测试结果为假的定义是,当且仅当在关系中存在着两个元组t1和t2,且t1=t2。由于在t1或t2的某个域为空时,判断t1=t2为假,所以尽管一个元组有多个副本,只要该元组有一个属性为空,unique测试就有可能为真。
3.8.5from子句中的子查询
SQL允许在from子句中使用子查询表达式。在此采用的主要观点是:任何selectfromwhere表达式返回的结果都是关系,因而可以被插入到另一个selectfromwhere中任何关系可以出现的位置。
考虑查询“找出系平均工资超过42 000美元的那些系中教师的平均工资”。在3.7节我们使用了having子句来书写此查询。现在我们可以不用having子句来重写这个查询,而是通过如下这种在from子句中使用子查询的方式:
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子句,因为from子句中的子查询计算出了每个系的平均工资,早先在having子句中使用的谓词现在出现在外层查询的where子句中。
我们可以用as子句给此子查询的结果关系起个名字,并对属性进行重命名。如下所示:
select dept_name, avg_salary
from (select dept_name, avg (salary)
from instructor
group by dept_name)
as dept_avg (dept_name, avg_salary)
where avg_salary> 42000;
子查询的结果关系被命名为dept_avg,其属性名是dept_name和avg_salary。
很多(但并非全部)SQL实现都支持在from子句中嵌套子查询。请注意,某些SQL实现要求对每一个子查询结果关系都给一个名字,即使该名字从不被引用;Oracle允许对子查询结果关系命名(省略掉关键字as),但是不允许对关系中的属性重命名。
作为另一个例子,假设我们想要找出在所有系中工资总额最大的系。在此having子句是无能为力的,但我们可以用from子句中的子查询轻易地写出如下查询:
select max (tot_salary)
from (select dept_name, sum(salary)
from instructor
group by dept_name) as dept_total (dept_name, tot_salary);
我们注意到在from子句嵌套的子查询中不能使用来自from子句其他关系的相关变量。然而SQL:2003允许from子句中的子查询用关键词lateral作为前缀,以便访问from子句中在它前面的表或子查询中的属性。例如,如果我们想打印每位教师的姓名,以及他们的工资和所在系的平均工资,可书写查询如下:
select name, salary, avg_salary
from instructor I1, lateral (select avg(salary) as avg_salary
from instructor I2
where I2.dept_name= I1.dept_name);
没有lateral子句的话,子查询就不能访问来自外层查询的相关变量I1。目前只有少数SQL实现支持lateral子句,比如IBM DB2。
3.8.6with子句
with子句提供定义临时关系的方法,这个定义只对包含with子句的查询有效。考虑下面的查询,它找出具有最大预算值的系。
with max_budget (value) as
(select max(budget)
from department)
select budget
from department, max_budget
where department.budget = max_budget.value;
with子句定义了临时关系max_budget,此关系在随后的查询中马上被使用了。with子句是在SQL:1999中引入的,目前有许多(但并非所有)数据库系统都提供了支持。
我们也能用from子句或where子句中的嵌套子查询书写上述查询。但是,用嵌套子查询会使得查询语句晦涩难懂。with子句使查询在逻辑上更加清晰,它还允许在一个查询内的多个地方使用视图定义。
例如,假设我们要查出所有工资总额大于所有系平均工资总额的系,我们可以利用如下with子句写出查询:
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;
我们当然也可以不用with子句来建立等价的查询,但是那样会复杂很多,而且也不易看懂。作为练习,你可以把它转化为不用with子句的等价查询。
3.8.7标量子查询
SQL允许子查询出现在返回单个值的表达式能够出现的任何地方,只要该子查询只返回包含单个属性的单个元组;这样的子查询称为标量子查询(scalar subquery)。例如,一个子查询可以用到下面例子的select子句中,这个例子列出所有的系以及它们拥有的教师数:
select dept_name,
(select count(*)
from instructor
where department.dept_name = instructor.dept_name)
as num_instructors
from department;
上面例子中的子查询保证只返回单个值,因为它使用了不带group by的count(*)聚集函数。此例也说明了对相关变量的使用,即使用在外层查询的from子句中关系的属性,例如上例中的department.dept_name。
标量子查询可以出现在select、where和having子句中。也可以不使用聚集函数来定义标量子查询。在编译时并非总能判断一个子查询返回的结果中是否有多个元组,如果在子查询被执行后其结果中有不止一个元组,则产生一个运行时错误。
注意从技术上讲标量子查询的结果类型仍然是关系,尽管其中只包含单个元组。然而,当在表达式中使用标量子查询时,它出现的位置是单个值出现的地方,SQL就从该关系中包含单属性的单元组中取出相应的值,并返回该值。
3.9数据库的修改
目前为止我们的注意力集中在对数据库的信息抽取上。现在我们将展示如何用SQL来增加、删除和修改信息。
3.9.1删除
删除请求的表达与查询非常类似。我们只能删除整个元组,而不能只删除某些属性上的值。SQL用如下语句表示删除:
delete from r
where P;
其中P代表一个谓词,r代表一个关系。delete语句首先从r中找出所有使P(t)为真的元组t,然后把它们从r中删除。如果省略where子句,则r中所有元组将被删除。
注意delete命令只能作用于一个关系。如果我们想从多个关系中删除元组,必须在每个关系上使用一条delete命令。where子句中的谓词可以和select命令的where子句中的谓词一样复杂。在另一种极端情况下,where子句可以为空,请求
delete from instructor;
将删除instructor关系中的所有元组。instructor关系本身仍然存在,但它变成空的了。
下面是SQL删除请求的一些例子:
从instructor关系中删除与Finance系教师相关的所有元组。
delete from instructor
where dept_name= ’Finance’;
删除所有工资在13 000美元到15 000美元之间的教师。
delete from instructor
where salary between 13000 and 15000;
从instructor关系中删除所有这样的教师元组,他们在位于Watson大楼的系工作。
delete from instructor
where dept_name in (select dept_name
from department
where building = ’Watson’);
此delete请求首先找出所有位于Watson大楼的系,然后将属于这些系的instructor元组全部删除。
注意,虽然我们一次只能从一个关系中删除元组,但是通过在delete的where子句中嵌套selectfromwhere,我们可以引用任意数目的关系。delete请求可以包含嵌套的select,该select引用待删除元组的关系。例如,假设我们想删除工资低于大学平均工资的教师记录,可以写出如下语句:
delete from instructor
where salary < (select avg (salary)
from instructor);
该delete语句首先测试instructor关系中的每一个元组,检查其工资是否小于大学教师的平均工资。然后删除所有符合条件的元组,即所有低于平均工资的教师。在执行任何删除之前先进行所有元组的测试是至关重要的,因为若有些元组在其余元组未被测试前先被删除,则平均工资将会改变,这样delete的最后结果将依赖于元组被处理的顺序!
3.9.2插入
要往关系中插入数据,我们可以指定待插入的元组,或者写一条查询语句来生成待插入的元组集合。显然,待插入元组的属性值必须在相应属性的域中。同样,待插入元组的分量数也必须是正确的。
最简单的insert语句是单个元组的插入请求。假设我们想要插入的信息是Computer Science系开设的名为“Database Systems”的课程CS437,它有4个学分。我们可写成:
insert into course
values (’CS437’, ’Database Systems’, ’Comp.Sci.’, 4);
在此例中,元组属性值的排列顺序和关系模式中属性排列的顺序一致。考虑到用户可能不记得关系属性的排列顺序,SQL允许在insert语句中指定属性。例如,以下SQL insert语句与前述语句的功能相同。
insert into course (course_id, title, dept_name, credits)
values (’CS-437’, ’Database Systems’, ’Comp.Sci.’, 4);
insert into course (title, course_id, credits, dept_name)
values (’Database Systems’, ’CS-437’, 4, ’Comp.Sci.’);
更通常的情况是,我们可能想在查询结果的基础上插入元组。假设我们想让Music系每个修满144学分的学生成为Music系的教师,其工资为18 000美元。我们可写作:
insert into instructor
select ID, name, dept_name, 18000
from student
where dept_name = ’Music’ and tot_cred> 144;
和本节前面的例子不同的是,我们没有指定一个元组,而是用select选出一个元组集合。SQL先执行这条select语句,求出将要插入到instructor关系中的元组集合。每个元组都有ID、name、dept_name (Music)和工资(18 000美元)。
在执行插入之前先执行完select语句是非常重要的。如果在执行select语句的同时执行插入动作,如果在student上没有主码约束的话,像
insert into student
select*
from student;
这样的请求就可能会插入无数元组。如果没有主码约束,上述请求会重新插入student中的第一个元组,产生该元组的第二份拷贝。由于这个副本现在是student中的一部分,select语句可能找到它,于是第三份拷贝被插入到student中。第三份拷贝又可能被select语句发现,于是又插入第四份拷贝,如此等等,无限循环。在执行插入之前先完成select语句的执行可以避免这样的问题。这样,如果在student关系上没有主码约束,那么上述insert语句就只是把student关系中的每个元组都复制一遍。
在讨论insert语句时我们只考虑了这样的例子:待插入元组的每个属性都被赋了值。但是有可能待插入元组中只给出了模式中部分属性的值,那么其余属性将被赋空值,用null表示。考虑请求:
insert into student
values (’3003’, ’Green’, ’Finance’, null);
此请求所插入的元组代表了一个在Finance系、ID为“3003”的学生,但其tot_cred值是未知的。考虑查询:
select ID
from student
where tot_cred> 45;
既然“3003”号学生的tot_cred值未知,我们不能确定它是否大于45。
大部分关系数据库产品有特殊的“bulk loader”工具,它可以向关系中插入一个非常大的元组集合。这些工具允许从格式化文本文件中读出数据,且执行速度比同等目的的插入语句序列要快得多。
3.9.3更新
有些情况下,我们可能希望在不改变整个元组的情况下改变其部分属性的值。为达到这一目的,可以使用update语句。与使用insert﹑delete类似,待更新的元组可以用查询语句找到。
假设要进行年度工资增长,所有教师的工资将增长5%。我们写出:
update instructor
set salary= salary * 1.05;
上面的更新语句将在instructor关系的每个元组上执行一次。
如果只给那些工资低于70 000美元的教师涨工资,我们可以这样写:
update instructor
set salary = salary * 1.05
where salary < 70 000;
总之,update语句的where子句可以包含select语句的where子句中的任何合法结构(包括嵌套的select)。和insert、delete类似,update语句中嵌套的select可以引用待更新的关系。同样,SQL首先检查关系中的所有元组,看它们是否应该被更新,然后才执行更新。例如,请求“对工资低于平均数的教师涨5%的工资”可以写为如下形式:
update instructor
set salary = salary * 1.05
where salary < (select avg (salary)
from instructor);
我们现在假设给工资超过100 000美元的教师涨3%的工资,其余教师涨5%。我们可以写两条update语句:
update instructor
set salary = salary * 1.03
where salary> 100000;
update instructor
set salary = salary * 1.05
where salary <= 100000;
注意这两条update语句的顺序十分重要。假如我们改变这两条语句的顺序,工资略少于100 000美元的教师将增长8%的工资。
SQL提供case结构,我们可以利用它在一条update语句中执行前面的两种更新,避免更新次序引发的问题:
update instructor
set salary = case
when salary <= 100000 then salary * 1.05
else salary * 1.03
end
case语句的一般格式如下:
case
when pred1 then result1
when pred2 then result2
…
when predn then resultn
else result0
end
当i是第一个满足的pred1,pred2…predn时,此操作就会返回resulti;如果没有一个谓词可以满足,则返回result0。case语句可以用在任何应该出现值的地方。
标量子查询在SQL更新语句中也非常有用,它们可以用在set子句中。考虑这样一种更新:我们把每个student元组的tot_cred属性值设为该生成功学完的课程学分的总和。我们假设如果一个学生在某门课程上的成绩既不是’F’,也不是空,那么他成功学完了这门课程。我们需要使用set子句中的子查询来写出这种更新,如下所示:
update student S
set tot_cred = (
select sum(credits)
from takes natural join course
where S.ID= takes.ID and
takes.grade <> ’F’ and
takes.grade is not null);
注意子查询使用了来自update语句中的相关变量S。如果一个学生没有成功学完任何课程,上述更新语句将把其tot_cred属性值设为空。如果想把这样的属性值设为0的话,我们可以使用另一条update语句来把空值替换为0。更好的方案是把上述子查询中的“select sum(credits)”子句替换为如下使用case表达式的select子句:
select case
when sum(credits) is not null then sum(credits)
else 0
end
3.10总结
SQL是最有影响力的商用市场化的关系查询语言。SQL语言包括几个部分:
数据定义语言(DDL),它提供了定义关系模式、删除关系以及修改关系模式的命令。
数据操纵语言(DML),它包括查询语言,以及往数据库中插入元组、从数据库中删除元组和修改数据库中元组的命令。
SQL的数据定义语言用于创建具有特定模式的关系。除了声明关系属性的名称和类型之外,SQL还允许声明完整性约束,例如主码约束和外码约束。
SQL提供多种用于查询数据库的语言结构,其中包括select、from和where子句。SQL支持自然连接操作。
SQL还提供了对属性和关系重命名,以及对查询结果按特定属性进行排序的机制。
SQL支持关系上的基本集合运算,包括并、交和差运算,它们分别对应于数学集合论中的∪、∩和-运算。
SQL通过在通用真值true和false外增加真值“unknown”,来处理对包含空值的关系的查询。
SQL支持聚集,可以把关系进行分组,在每个分组上单独运用聚集。SQL还支持在分组上的集合运算。
SQL支持在外层查询的where和from子句中嵌套子查询。它还在一个表达式返回的单个值所允许出现的任何地方支持标量子查询。
SQL提供了用于更新、插入、删除信息的结构。
术语回顾
数据定义语言
数据操纵语言
数据库模式
数据库实例
关系模式
关系实例
主码
外码
参照关系
被参照关系
空值
查询语言
SQL查询结构
select子句
from子句
where子句
自然连接运算
as子句
order by子句
相关名称(相关变量,元组变量)
集合运算
union
intersect
except
空值
真值“unknown”
聚集函数
avg, min, max, sum, count
group by
having
嵌套子查询
集合比较
{<,<=,>,>=} {some, all}
exists
unique
lateral子句
with子句
标量子查询
数据库修改
删除
插入
更新
实践习题
3.1使用大学模式,用SQL写出如下查询。(建议在一个数据库上实际运行这些查询,使用我们在本书的Web网站dbbook.com上提供的样本数据,上述网站还提供了如何建立一个数据库和加载样本数据的说明。)
a.找出Comp.Sci.系开设的具有3个学分的课程名称。
b.找出名叫Einstein的教师所教的所有学生的标识,保证结果中没有重复。
c.找出教师的最高工资。
d.找出工资最高的所有教师(可能有不止一位教师具有相同的工资)。
e.找出2009年秋季开设的每个课程段的选课人数。
f.从2009年秋季开设的所有课程段中,找出最多的选课人数。
g.找出在2009年秋季拥有最多选课人数的课程段。
3.2假设给你一个关系grade_points(grad_e, points),它提供从takes关系中用字母表示的成绩等级到数字表示的得分之间的转换。例如,“A”等级可指定为对应于4分,“A-”对应于3.7分,“B+”对应于3.3分,“B”对应于3分,等等。学生在某门课程(课程段)上所获得的等级分值被定义为该课程段的学分乘以该生得到的成绩等级所对应的数字表示的得分。
给定上述关系和我们的大学模式,用SQL写出下面的每个查询。为简单起见,可以假设没有任何takes元组在grade上取null值。
a.根据ID为12345的学生所选修的所有课程,找出该生所获得的等级分值的总和。
b.找出上述学生等级分值的平均值(GPA),即用等级分值的总和除以相关课程学分的总和。
c.找出每个学生的ID和等级分值的平均值。
3.3使用大学模式,用SQL写出如下插入、删除和更新语句。
a.给Comp.Sci.系的每位教师涨10%的工资。
b.删除所有未开设过(即没有出现在section关系中)的课程。
c.把每个在tot_cred属性上取值超过100的学生作为同系的教师插入,工资为10 000美元。
3.4考虑图318中的保险公司数据库,其中加下划线的是主码。为这个关系数据库构造出如下SQL查询:
a.找出2009年其车辆出过交通事故的人员总数。
b.向数据库中增加一个新的事故,对每个必需的属性可以设定任意值。
c.删除“John Smith”拥有的马自达车(Mazda)。
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)
图318习题3.4和习题3.14的保险公司数据库
3.5假设有关系marks(ID, score),我们希望基于如下标准为学生评定等级:如果score<40得F;如果40≤score<60得C;如果60≤score<80得B;如果80≤score得A。写出SQL查询完成下列操作:
a.基于marks关系显示每个学生的等级。
b.找出各等级的学生数。
3.6SQL的like运算符是大小写敏感的,但字符串上的lower()函数可用来实现大小写不敏感的匹配。为了说明是怎么用的,写出这样一个查询:找出名称中包含了“sci”子串的系,忽略大小写。
3.7考虑SQL查询
select distinct p.a1
from p, r1, r2
where p.a1=r1.a1 or p.a1=r2.a1
在什么条件下这个查询选择的p.a1值要么在r1中,要么在r2中?仔细考察r1或r2可能为空的情况。
3.8考虑图319中的银行数据库,其中加下划线的是主码。为这个关系数据库构造出如下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, loan_number)
account (account_number, branch_name, balance )
depositor (customer_name, account_number)
图319习题3.8和习题3.15的银行数据库
3.9考虑图320的雇员数据库,其中加下划线的是主码。为下面每个查询写出SQL表达式:
a.找出所有为“First Bank Corporation”工作的雇员名字及其居住城市。
b.找出所有为“First Bank Corporation”工作且薪金超过10 000美元的雇员名字、居住街道和城市。
c.找出数据库中所有不为“First Bank Corporation”工作的雇员。
d.找出数据库中工资高于“Small Bank Corporation”的每个雇员的所有雇员。
e.假设一个公司可以在好几个城市有分部。找出位于“Small Bank Corporation”所有所在城市的所有公司。
f.找出雇员最多的公司。
g.找出平均工资高于“First Bank Corporation”平均工资的那些公司。
employee(employee_name, street, city)
works(employee_name, company_name, salary)
company(company_name, city)
managers(employee_name, manager_name)
图320习题3.9、习题3.10、习题3.16、习题3.17和习题3.20的雇员数据库
3.10考虑图320的关系数据库,给出下面每个查询的SQL表达式:
a.修改数据库使“Jones”现在居住在“Newtown”市。
b.为“First Bank Corporation”所有工资不超过100 000美元的经理增长10%的工资,对工资超过100 000美元的只增长3%。
习题
3.11使用大学模式,用SQL写出如下查询。
a.找出所有至少选修了一门Comp.Sci.课程的学生姓名,保证结果中没有重复的姓名。
b.找出所有没有选修在2009年春季之前开设的任何课程的学生的ID和姓名。
c.找出每个系教师的最高工资值。可以假设每个系至少有一位教师。
d.从前述查询所计算出的每个系最高工资中选出最低值。
3.12使用大学模式,用SQL写出如下查询。
a.创建一门课程“CS-001”,其名称为“Weekly Seminar”,学分为0。
b.创建该课程在2009年秋季的一个课程段,sec_id为1。
c.让Comp.Sci.系的每个学生都选修上述课程段。
d.删除名为Chavez的学生选修上述课程段的信息。
e.删除课程CS001。如果在运行此删除语句之前,没有先删除这门课程的授课信息(课程段),会发生什么事情?
f.删除课程名称中包含“database”的任意课程的任意课程段所对应的所有takes元组,在课程名的匹配中忽略大小写。
3.13写出对应于图318中模式的SQL DDL。在数据类型上做合理的假设,确保声明主码和外码。
3.14考虑图318中的保险公司数据库,其中加下划线的是主码。对这个关系数据库构造如下的SQL查询:
a.找出和“John Smith”的车有关的交通事故数量。
b.对事故报告编号为“AR2197”中的车牌是“AABB2000”的车辆损坏保险费用更新到3000美元。
3.15考虑图319中的银行数据库,其中加下划线的是主码。为这个关系数据库构造出如下SQL查询:
a.找出在“Brooklyn”的所有支行都有账户的所有客户。
b.找出银行的所有贷款额的总和。
c.找出总资产至少比位于Brooklyn的某一家支行要多的所有支行名字。
3.16考虑图320中的雇员数据库,其中加下划线的是主码。给出下面每个查询对应的SQL表达式:
a.找出所有为“First Bank Corporation”工作的雇员名字。
b.找出数据库中所有居住城市和公司所在城市相同的雇员。
c.找出数据库中所有居住的街道和城市与其经理相同的雇员。
d.找出工资高于其所在公司雇员平均工资的所有雇员。
e.找出工资总和最小的公司。
3.17考虑图320中的关系数据库。给出下面每个查询对应的SQL表达式:
a.为“First Bank Corporation”的所有雇员增长10%的工资。
b.为“First Bank Corporation”的所有经理增长10%的工资。
c.删除“Small Bank Corporation”的雇员在works关系中的所有元组。
3.18列出两个原因,说明为什么空值可能被引入到数据库中。
3.19证明在SQL中,<>all等价于not in。
3.20给出图320中雇员数据库的SQL模式定义。为每个属性选择合适的域,并为每个关系模式选择合适的主码。
3.21考虑图321中的图书馆数据库。用SQL写出如下查询:
a.打印借阅了任意由“McGrawHill”出版的书的会员名字。
b.打印借阅了所有由“McGrawHill”出版的书的会员名字。
c.对于每个出版商,打印借阅了多于五本由该出版商出版的书的会员名字。
d.打印每位会员借阅书籍数量的平均值。考虑这样的情况:如果某会员没有借阅任何书籍,那么该会员根本不会出现在borrowed关系中。
member(memb_no, name, age)
book(isbn, title, authors, publisher)
borrowed(memb_no, isbn, date)
图321习题3.21的图书馆数据库
3.22不使用unique结构,重写下面的where子句:
where unique (select title from course)
3.23考虑查询
select course_id, semester, year, sec_id, avg (tot_cred)
from takes natural join student
where year = 2009
group by course_id, semester, year, sec_id
having count (ID) >= 2;
解释为什么在from子句中还加上与section的连接不会改变查询结果。
3.24考虑查询
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;
不使用with结构,重写此查询。
工具
很多关系数据库系统可以从市场上购得,包括IBM DB2、IBM Informix、Oracle、Sybase,以及微软的SQL Server。另外还有几个数据库系统可以从网上下载并免费使用,包括PostgreSQL、MySQL(除几种特定的商业化使用外是免费的)和Oracle Express edition。
大多数数据库系统提供了命令行界面,用于提交SQL命令。此外,大多数数据库还提供了图形化的用户界面(GUI),它们简化了浏览数据库、创建和提交查询,以及管理数据库的任务。还有商品化的IDE,用于在多个数据库平台上运行SQL,包括Embarcadero的RAD Studio与Aqua Data Studio。
pgAdmin工具为PostgreSQL提供了GUI功能;phpMyAdmin为MySQL提供了GUI功能。NetBeans IDE提供了一个GUI前端,可以与很多不同的数据库交互,但其功能有限;Eclipse IDE通过几种不同插件支持类似的功能,这些插件包括Data Tools Platform(DTP)和JBuilder。
本书的Web网站dbbook.com提供了SQL模式定义和大学模式的样本数据。该Web网站还提供了如何建立和访问一些流行的数据库系统的说明。本章讨论的SQL结构是SQL标准的一部分,但一些特征可能没被某些数据库所支持。Web网站上列出了这些不相容的特征,在这些数据库上执行查询时需要多加考虑。
文献注解
SQL的最早版本Sequel 2由Chamberlin等[1976]描述。Sequel 2是从Square语言(Boyce等[1975]以及Chamberlin 和 Boyce [1974])派生出来的。美国国家标准SQL86在ANSI [1986]中描述。IBM系统应用体系结构对SQL的定义由IBM[1987]给出。SQL89和SQL92官方标准可分别从ANSI[1989]和ANSI[1992]获得。
介绍SQL92语言的教材包括Date 和 Darwen[1997]、Melton 和 Simon[1993]以及Cannan 和 Otten[1993]。Date和Darwen[1997]以及Date[1993a]都包含了从编程语言角度对SQL92的评论。
介绍SQL:1999的教程包括Melton和Simon[2001]以及Melton[2002]。Eisenberg 和 Melton[1999]提供了对SQL:1999的概览。Donahoo 和 Speegle[2005]从开发者的角度介绍了SQL。Eisenberg等[2004]提供了对SQL:2003的概览。
SQL:1999、SQL:2003、SQL:2006和SQL:2008标准都是作为ISO/IEC标准文档集被发布的。标准文档中塞满了大量的信息,非常难以阅读,主要用于数据库系统的实现。标准文档可以从Web网站http://webstore.ansi.org上购买。
很多数据库产品支持标准以外的SQL特性,还可能不支持标准中的某些特性。关于这些特性的更多信息可在各产品的SQL用户手册中找到。
SQL查询的处理,包括算法和性能等问题,将在第11章讨论。关于这些问题的参考文献也在那里。
- 数据库系统概念(原书第6版.本科教学版)
- 数据库系统概念(原书第6版.本科教学版) 第1章
- 数据库系统概念(原书第6版.本科教学版) 第2章
- 数据库系统概念(原书第6版.本科教学版) 第3章
在线试读:
系列图书推荐
计算机图形学原理及实践(原书第3版)(进阶篇)
- ¥149.00
- ¥104.30
- 计算机图形学原理及实践(..
算法基础:Python和C#语言实现(原书第2版)
- ¥119.00
- ¥83.30
- 算法基础:Python和C#语言..
[套装书]数据挖掘:原理与实践(基础篇)+数据挖掘:原理与实践(进阶篇)(2册)
- ¥218.00
- ¥148.24
- [套装书]数据挖掘:原理与..
数据挖掘:原理与实践(基础篇)
- ¥139.00
- ¥87.57
- 数据挖掘:原理与实践(基础..
数据挖掘:原理与实践(进阶篇)
- ¥79.00
- ¥49.77
- 数据挖掘:原理与实践(进阶..
同类热销商品
Java编程思想(第4版)
- ¥108.00
- ¥75.60
- Java编程思想(第4版)
算法导论(原书第3版)
- ¥128.00
- ¥89.60
- 算法导论(原书第3版)
计算机网络:自顶向下方法(原书第7版)
- ¥89.00
- ¥62.30
- 计算机网络:自顶向下方法..
深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)
- ¥129.00
- ¥87.80
- 深入理解Java虚拟机:JVM高..
超简单:用Python让Excel飞起来
- ¥69.80
- ¥43.97
- 超简单:用Python让Excel飞..