北京
图书在版编目(CIP)数据
锋利的SQL/张洪举,王晓文著.--2版.--北京:人民邮电出版社,2015.5
ISBN 978-7-115-38871-1
Ⅰ.①锋… Ⅱ.①张…②王… Ⅲ.①关系数据库系统 Ⅳ.①TP311.138
中国版本图书馆CIP数据核字(2015)第074985号
内容提要
本书从基础、开发、性能调整和实战4方面介绍了SQL技术及其应用,包括数据库与架构、表管理、索引管理、基本查询、子查询、联接和APPLY运算符、操作结果集、窗口计算和表旋转、数据修改、视图、游标、存储过程、触发器、用户自定义函数、事务处理、并发访问控制、查询的优化与执行等内容。
本书既覆盖了改善效率和性能的普通SQL技术,也深入探讨了SQL新技术,更包含一些实用的查询解决方案,希望本书能够成为引领读者进入SQL查询殿堂的捷径。此外,本书在介绍各种查询语法时,更注重对查询逻辑思维方式的引导和介绍,帮助读者举一反三,提升动手解决实际问题的能力。
本书可以作为SQL入门书,也可以作为SQL程序员、DBA的参考书。
◆著 张洪举 王晓文
责任编辑 杨海玲
责任印制 张佳莹 焦志炜
◆人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子函件 315@ptpress.com.cn
网址 http://www.ptpress.com.cn
北京艺辉印刷有限公司印刷
◆开本:800×1000 1/16
印张:29.5
字数:697千字 2015年5月第2版
印数:4001-6500册 2015年5月北京第1次印刷
定价:69.00元
读者服务热线:(010)81055410 印装质量热线:(010)81055316
反盗版热线:(010)81055315
本书第1版是在4年前出版的,至今仍在被众多的SQL爱好者追捧,甚至有人不辞辛劳地逐页扫描,上传至网络进行分享。本书第 1 版是基于SQL Server 2008 编写的,时光荏苒,目前SQL Server 2014 已经发布,窗口函数等新功能不断丰富和发展,先前需要编写很复杂的代码才能完成的工作,现在寥寥几行就能把问题解决。无论是从代码可读性还是执行效率方面看,SQL Server 2014 都值得我们去深入学习。因此,我们在第 1 版的基础上,对书的内容重新进行了补充和完善。此外,这次我们还补充了一些像“三值逻辑”这样的基础知识,因为在实际工作中,我经常发现,由于存在NULL 值比较问题,导致查询结果不正确,费尽千辛万苦地分析代码,最终却发现犯了低级错误。
当初之所以要写这本书,主要出于两方面的原因:一是伴随着各种数据库技术日新月异的发展,无论是哪种数据库产品,想用有限的篇幅去描述它的全貌,几乎都是不可能完成的任务,所以我就考虑能否抽取出各种数据库产品中一些大家共同关心的内容,进行深入细致的挖掘,而SQL无疑是这方面的首选;二是在与一些开发公司的合作中,发现公司间的SQL应用情况的差异也很大,一些公司出于产品的可移植性考虑,拒绝使用一些新的SQL技术,甚至尽量避免在服务器上部署存储过程。所以我希望在深入地讨论一些常用SQL技术的同时,也尽可能多介绍一些SQL的新技术,从而消除大家对新技术的恐惧感,对新技术的推广能够起到一定的作用。
本书特点
本书既覆盖了改善效率和性能的普通SQL技术,也深入探讨了SQL新技术,更包含一些实用的查询解决方案。希望本书能够成为引领读者进入SQL查询殿堂的捷径。
本书的内容是基于 SQL Server 数据库产品进行讨论的。不过,由于大家都在遵循 ANSI-SQL标准,所以无论是哪种数据库产品的 SQL,彼此之间的差别并不大。数据库开发人员在跨越不同的数据库产品时,一般不会遇到什么障碍。当然,本书不特定于某个 SQL Server 版本,在介绍一些新功能时,会介绍它是在哪个版本添加进来的。并且,本书在改写过程中,在添加新的查询解决方案的同时,保留了先前的解决方案,以便读者能够进行对比分析。
本书在介绍各种查询语法时,更注重对查询逻辑思维方式的引导和介绍,这样可以帮助读者在阅读之后举一反三,提升自己动手解决实际问题的能力。
本书适用读者
本书是按照由浅入深、循序渐进的方式对SQL进行介绍的,既包含了入门知识,也包含了深层次技术的讨论。即使是最基本的查询语句,我们也会尽力为读者提供解决深层次问题的能力。也就是说,同样一个问题,开发人员可以写几十甚至上百行的SQL语句来解决问题,也有可能仅通过一条SELECT语句就可以解决问题。对于SQL而言,虽然代码最简化并不一定是性能最优化,但至少是对思维能力的一种提升。
从这个角度而言,本书可以作为SQL入门书,也可以作为SQL程序员、DBA的参考书。
本书内容与结构
本书共19章,可大致分为基础篇、开发篇、性能调整篇和实战篇4部分。
基础篇包括第1章至第10章。其中,第1章是对查询工具、书写规范等基本内容的介绍,第2章至第5章是对数据库、表和索引的介绍,第5章至第10章则介绍了使用SELECT进行数据查询和使用INSERT、UPDATE、DELETE进行数据修改的各个方面。本次修订新增了SQL理论基础,新增函数介绍、架构概念、标识列和序列对象等一致性约束介绍,内存表、选择列表的同时操作概念,查询的逻辑处理步骤的修订,使用OFFSET-FETCH限制查询结果和限制查询修改,窗口分布函数和窗口偏移函数、MERGE与OUTPUT等,尤其是第9章的窗口计算部分,更是全部重新书写。
开发篇包括第11章至第15章。如果将SELECT、INSERT、UPDATE和DELETE作为基本查询语句,则IF...ELSE、WHILE和TRY...CATCH构造等则可以看作是SQL编程语句。在存储过程、触发器等对象中可以通过这些语句实现一些复杂的逻辑处理。如果你曾经是一位使用VB或VC的程序员,在学习C/S或B/S编程时,你应当掌握这种服务器端的编程工具,从而将业务逻辑计算合理地分布到服务器和客户端。本次修订对CLR 存储过程、触发器、函数等以Visual Studio 2013 为基础重新进行了编写。
性能调整篇包括第16章至第18章。第16章和第17章介绍的是事务处理机制和并发访问控制。其实,无论是微软还是 Oracle、IBM,其数据库产品的核心功能都是一样的,即在保证数据完整性的前提下提供最大的并发支持。数据库系统是通过“锁机制”来实现的,数据库引擎都提供有多种粒度的锁定模式,从而允许用户可以根据需要将资源锁定在适当的级别,尽量减少锁定开销。第18章则讨论了查询优化器的工作原理,重用查询计划,可以减少额外的编译开销,提高服务器性能。
实战篇仅包含第19章的内容,提供了同一时间范围内并发数统计、时间段天数统计、数字范围统计、地域范围内最大数统计等较为常见问题的解决方案。本次修订在原解决方案的基础上新增了窗口函数的解决方案。
系统要求
本书实例中使用的操作系统是Windows 7,使用的数据库是SQL Server 2014 开发者版本,开发工具是Visual Studio 2013。为使Visual Studio 2013 能够支持SQL Server 2014 的SQL开发,需要将其升级到最新版本。同时,由于SQL Server 示例数据库在2014版本中表结构变动较大,因此对书中的代码重新进行了调整和测试,该示例数据库可从 http://msftdbprodsamples.codeplex.com/releases/view/125550下载。
代码示例
本书中使用的所有代码,请从作者的博客(http://blog.csdn.net/zhanghongju)上获取。
致谢
在本书的完成过程中,得到了诸多 SQL Server 技术专家和爱好者的支持与帮助。他们无私和热情的参与,使本书的内容更加实用和更具指导性,在此一并表示感谢。他们是王向东、秦广、魏兰花、凌亚东、王亚羽、陈雨薇、王光辉、高存亭、桑晓红、王新河、张宪国、李联国、韩燕军。
由于时间仓促,加之作者水平有限,书中不足之处在所难免,敬请读者批评指正。
张洪举
2015年1月
SQL Server 2014 示例数据库安装步骤具体如下。
(1)登录http://msftdbprodsamples.codeplex.com/releases/view/125550网站,单击Adventure Works 2014 Full Database Backup.zip 下载数据库文件。
(2)下载完成后解压缩,释放后的文件名为AdventureWorks2014.bak。
(3)打开Microsoft SQL Server Management Studio,在“对象资源管理器”中右键单击“数据库”节点,在弹出的快捷菜单中选择“还原数据库”菜单项,如图1所示。
(4)在打开的“还原数据库”窗口中,单击“设备”后面的“…”按钮,在打开的对话框中单击“添加”按钮,选择释放后的数据库备份文件AdventureWorks2014.bak,如图2所示。依次单击“确定”按钮即可还原数据库。
(5)在“对象资源管理器”中右键单击“数据库”节点,在弹出的快捷菜单中选择“刷新”菜单项,即可看到数据库AdventureWorks2014。
第1章 SQL简介
第2章 数据库与架构
第3章 表管理
第4章 索引管理
第5章 基本查询
第6章 子查询
第7章 联接和APPLY运算符
第8章 操作结果集
第9章 窗口计算和表旋转
第10章 数据修改
SQL 的全称是结构化查询语言(Structured Query Language),是一种在关系数据库管理系统(Relational Database Management Systems,RDBMS)中查询和管理数据的标准语言。这是一种非常易读和易用的语言,只要稍微有一点英语基础,一些简单的数据查询、操作语句几乎都可以理解。但是,要想精通 SQL,并不是一件很容易的事情,一是因为在数据处理方面有许许多多的个案,要处理好这些个案,除了具有缜密的逻辑思维,还需要多练习和实践;二是与其他编程语言相比, SQL 是一种描述性语言,你只要说出想要什么结果,中间过程由数据库引擎去处理,其他编程语言则需要通过循环语句、判断语句等自己进行处理,如果出现什么问题,可以很快找到导致错误的语句,而对于SQL你就需要对寥寥几行的查询语句深入思考——数据库引擎为什么没有理解我的要求?所以说,数据库引擎的这种“封箱”操作给查找问题带来了麻烦,通常的解决方法就是将一个复杂语句拆解为几个简单语句,逐步验证中间结果的正确性,然后再重新组合为一条语句。从实际工作经验看,我不建议你写很复杂的语句来证明自己的高深,简单明了的语句对于后期的系统维护很有帮助,除非你的语句确实影响了处理效率。从本章开始,打好坚实的基础,并在此基础上不断提升自己的理论知识体系,当感受某个成功喜悦的时候,或许就会发现自己已经站在了SQL的巅峰。
本章我们将讲述一些最基本的SQL内容,如SQL的历史起源、ANSI是什么和一些基本的数据库理论,以及SQL的语法元素和执行SQL的工具等。基本知识学习起来可能比较枯燥。但是,如果你是初学 SQL,这些基本知识对于学习好本书的后续内容却起着至关重要的作用。我也曾见过许多资深的数据库管理员会犯一些低级错误,一上午在不停地分析语句,最后却发现仅仅是“三值”逻辑问题。
在20世纪60年代,网状数据库系统(如 CODASYL)和分层数据库系统(如IMS TM)是用于自动化银行业务、记账和订单处理系统的一流技术,这些系统是由于商业大型计算机的引入才启用的。而SQL是在20世纪70年代创建的一种基于关系数据库管理系统模型的数据查询、操作语言。
CODASYL是美国数据系统语言协会(Conference on Data System Language)的英文缩写,该协会成立于1957年,主要目的是开发一种用于创建商业应用的通用语言。1959年5月28日,该协会召开了首次会议,就语言开发进行讨论。这个语言实际上就是Cobol语言。
1963年6月10日,美国加利福尼亚州的系统开发公司(System Development Corporation)举办了一个题为“基于计算机的数据库开发和管理”(Development and Management of a Computer-centered Data Base)的研讨会,首次提出并定义了数据库(Database)术语,即一组文件(表)的集合,其中文件是数据项(行)的有序集合,而每个数据项由数据以及一个或多个键组成。
1965年,CODASYL 成立了“列表处理任务组”(List Processing Task Force),后更名为“数据库任务组”(Data Base Task Group)。1971年4月,任务组发布了一份重要的报告,报告概述了网状数据模型,被称为CODASYL 或DBTG(即Data Base Task Group的缩写)数据模型。这个模型定义了数据库的几个关键概念,包括定义模式的语法、定义子模式的语法和数据操作语言。
IMS 是信息管理系统(Information Management System)的英文缩写,是IBM公司的产品,这是一款分层数据库管理和事务处理系统。
IMS最初的开发目的是支持美国的阿波罗太空计划。1966年,IBM公司的12名成员、美国洛克维尔(American Rockwell)公司的10名成员和卡特彼勒公司(Caterpillar Tractor)的3名成员被集合起来,开始开发信息控制系统(Information Control System,ICS)和数据语言/接口(Data Language/Interface,DL/I),用于辅助跟踪建造太空船所需要的材料。其中,ICS部分是用于存储和获取数据的数据库部分,而DL/I部分则是用来与之交互的查询语言。
在开发过程中,IBM小组转移到加利福尼亚州南部的洛杉矶,并增加至21名开发人员。1967年,IBM团队完成了ICS的第一个版本。1968年4月开始进行安装测试。1968年8月14日,第一个安装成功,信息显示在美国国家航空航天局洛克维尔部门的IBM 2740打印机终端上。
1969年,ICS被更名为信息管理系统,即IMS。自第一个版本问世以来,IBM一直在不断开发和完善IMS 的功能。2007年,IBM推出了IMS 10 版本。IMS 10 具备了增强的XML 和网络服务功能,并且也是第一个将标准XML查询语言应用于层次结构化数据的数据库系统。
无论是CODASYL,还是IMS,虽然这些系统为早期系统提供了良好的基础,但它们的基本体系结构是将数据的物理操作与逻辑操作混合在一起。当数据的物理位置改变时,也必须更新应用程序来引用新的位置,给数据查询带来了不便。
SQL是一种关系型数据库查询语言,要介绍SQL的起源,就不得不介绍IBM公司的两个重量级人物——E.F.Codd 博士和Don Chamberlin 博士。E.F.Codd 博士最早提出了关系数据库管理系统(Relational Database Management System,RDBMS)模型,而Don Chamberlin 博士则是SQL和XQuery语言的主要创造者之一。他们对数据库的变革起到了革命性的作用。
Don Chamberlin 最初在IBM的T. J. Watson 研究中心(http://www.watson.ibm.com/)工作,当时该中心的主要研究方向是操作系统。Chamberlin 一开始从事的项目是SystemA,但项目很快便以失败而告终。当时担任项目经理的 Leonard Liu(现为 Augmentum 公司 CEO,http://www. augmentum.com)很有远见地预见到数据库的美好前景,他转变了整个小组的研究方向。Chamberlin因此在数据库软件和查询语言方面进行了大量研究,并成为了小组中最好的网状数据库CODASYL专家。
此时的 E.F.Codd 博士在IBM 的 San Jose 研究中心(即现在的 Almaden 研究中心, http://www.almaden.ibm.com/)工作。1969年,Codd在IBM的研究报告“Derivability, Redundancy, and Consistency of Relations Stored in Large Data Banks”(大型数据库中关系模型存储的可导、冗余和一致性)中第一次提出了关系模型,修订版本是Codd于1970年在一篇名为“A Relational Model of Data for Large Shared Data Banks”(大型共享数据库的数据关系模型)的文章中提出的,发表在《美国计算机学会通讯》杂志上。这是一篇里程碑性的论文,它确立了关系数据库的概念。但是,由于IBM正在从事IMS的开发,这种思想对IBM本身产品造成了威胁,所以公司内部最初持压制态度。当然这也与Codd采用了大量的数学方法、不容易理解有关。
1973年,IBM在外部竞争的压力下,开始加强在关系数据库方面的投入。Chamberlin被调到San Jose研究中心,加入新成立的项目System R。System R基于Codd提出的关系数据库管理系统模型。
System R项目包括研究高层的关系数据系统(Relational Data System,RDS)和研究底层的存储系统(Research Storage System,RSS)两个小组,Chamberlin 担任RDS 组的经理。RDS实际上就是一个数据库语言编译器,由于Codd提出的关系代数和关系演算过于数学化,影响了易用性。于是Chamberlin选择了自然语言作为研究方向,其结果就是诞生了结构化英语查询语言(Structured English Query Language,SEQUEL)。后来,由于商标之争,SEQUEL 更名为SQL。
System R是一个具有开创性意义的项目。它第一次实现了结构化查询语言,并已成为标准的关系数据查询语言。同时,它也是第一个证明了关系数据库管理系统可以提供良好事务处理性能的系统。System R系统中的设计决策,以及一些基本算法选择(如查询优化中的动态编程算法)对以后的关系系统都产生了积极影响。
System R本身作为原型虽然并未问世,但鉴于其影响,计算机协会(Association for Computing Machinery,ACM,http://www.acm.org/)还是把1988年的“软件系统奖”授予了System R开发小组。
ANSI是美国国家标准学会(American National Standards Institute)的英文缩写,该学会成立于1918年。当时,美国的许多企业和专业技术团体,已开始了标准化工作,但因彼此间没有协调,存在不少矛盾和问题。为了进一步提高效率,数百个科技学会、协会组织和团体,均认为有必要成立一个专门的标准化机构,并制定统一的通用标准。1918年,美国材料试验协会(ASTM)、美国机械工程师协会(ASME)、美国矿业与冶金工程师协会(ASMME)、美国土木工程师协会(ASCE)和美国电气工程师学会(AIEE)等组织,共同成立了美国工程标准委员会(AESC)。美国政府的商务部、陆军部和海军部也参与了该委员会的筹备工作。1928年,美国工程标准委员会改组为美国标准协会(ASA)。为致力于国际标准化事业和消费品方面的标准化,1966年8月,又改组为美利坚合众国标准学会(USASI)。1969年10月6日改成现名:美国国家标准学会(ANSI)。
虽然IBM首创了关系数据库理论,但Oracle却是第一家在市场上推出了这套技术的公司。随着时间的推移,SQL的简洁、直观,在市场上获得了不错的反响,从而引起了ANSI的关注,分别在1986年、1989年、1992年、1999年及2003年发布了SQL 标准。SQL Server 2000 遵循ANSI SQL:1992 标准,而SQL Server 2005 和2008还实现了ANSI SQL:1999 和ANSI SQL:2003中的一些重要特性。
数据库生产商在遵循ANSI标准的同时,也会根据自己产品的特点对SQL进行了一些改进和增强,于是也就有了SQL Server 的Transact-SQL、Oracle 的PL/SQL 等语言,我们称之为SQL 方言。在本书中,我们将以Transact-SQL为基础进行SQL语言的介绍。实际上,在学习过程中,大家也没有必要刻意关心哪些语句或关键字是SQL标准,哪些是Transact-SQL的扩展。其实常见的数据库操作,在绝大多数支持SQL语言的数据库中差别并不大,所以数据库开发人员在跨越不同的数据库产品时,一般不会遇到什么障碍。但是对于数据库管理员来说,则需要面对很多挑战,不同数据库产品在管理、维护和性能调整方面区别很大。
E.F.Codd博士提出的关系数据库管理系统模型基于两个数学分支:集合理论和谓词逻辑。理解集合理论,对于构建数据库结构,减少数据冗余,保证数据一致性方面具有重要意义。所以,即使你是一个资深的DBA,也建议你重温本节内容。
集合理论(集合论)是由数学家 Georg Cantor 创建的,这是一个基于关系模型的数学分支。Cantor的集合定义如下:
任意集合体M是我们感知或想到的,能够确定的互异对象m(称之为M的元素)的整体。
——Joseph W. Dauben 和Georg Cantor(普林斯顿大学出版社,1990)
看似简短的一句话,但是定义中的每个字都有着深刻和重要的意义。一个“集合”应将其视为单个实体,你的焦点应该放在对象的集合上,而不是组成集合的单个对象上。例如,当你对数据库中的雇员表编写查询时,应该将雇员的集合看作一个整体,而不是单个的雇员。这听起来可能并不重要并且很简单,但是很多程序员没有这种思维方式。
“互异”的含义是指集合中的每个元素必须是唯一的。对于数据库中的表,我们可以通过定义键约束来强制表中行的唯一性。没有键的话,就不能唯一地标识行,这样的表也就不能称之为“集合”,只能将其看作一个多重集合或一个无序的单位组。
“对象”可以是汽车或雇员这样的物理对象,也可以是抽象对象,如质数或线条。
“我们感知或想到的”这句话意味着集合的定义是主观的。例如,在教室中,“人”可以被认定为是“学生”的集合,也可以是“教师”的集合。因此,在定义集合方面你具有很大的自由度。在为数据库设计数据模型时,设计过程应仔细考虑应用程序的主观需求,从而为相关实体确定恰当的定义。
需要注意的是,Cantor的集合定义省略掉的内容同样重要。定义中没有提到集合元素间的任何顺序,集合元素的列出顺序并不重要。因为与顺序无关,{b, a, c}与{b, c, a}表示的是同一集合。对于属性(SQL中称之为列)集合,它们组成了关系(SQL中称之为表)的表头,元素应该是按名称标识,而不是按顺序位置标识。
同样,对于元组(SQL中称之为行)而言,它们构成了关系的主体,元素由其键值进行标识,而不是按位置标识。许多程序员很难适应这种观念,对于查询表而言,行之间没有顺序。换句话说,对表的查询可以按任意顺序返回表中的行,除非你基于特定展现目的,明确要求数据以特定的方式进行排序。
谓词逻辑的渊源可追溯到古希腊,是基于关系模型的另一个数学分支。谓词是一个属性或是一个表示“持有”或“不持有”的表达式,换句话说,也就是“真”或“假”。关系模型就是依靠谓词来维护数据的逻辑完整性并定义其结构的。例如,在一个名为Employees的表中定义了约束,仅允许工资(Salary)大于0的雇员存储在表中。谓词是“工资大于0”(T-SQL 表达式:salary > 0)。
当通过筛选数据来界定子集等操作时,也可以使用谓词。例如,如果需要查询Employees表并要求只返回销售部门的雇员行时,可以在查询筛选器中使用谓词“部门(Department)等于销售(Sales)”(SQL 表达式:department = 'sales')。
在集合理论中,可以使用谓词定义集合,这是非常有用的,因为不能总是通过列出所有元素来定义集合(例如,无限集合),通过特征定义集合往往更加方便简洁。例如,这是使用谓词定义无限集合的例子:所有质数集合的谓词定义是——x是一个仅能被 1 和自身整除的大于 1的正整数。对于任意指定值,谓词可能为“真”,也可能为“假”,而全部质数的集合是谓词为“真”的所有元素的集合。例如,这是一个使用谓词定义有限集合的例子,集合{0,1,2,3,4,5,6,7, 8,9}可以被定义为下面谓词为“真”的所有元素的集合——x是一个大于或等于0且小于或等于9的整数。
前面提过,关系模型是基于集合理论和谓词逻辑进行数据管理和操作的语义模型,它涉及命题、谓词、关系、元组和属性等概念。关系模型的目标是确保数据的一致性表示,最小化或是没有冗余且不牺牲完整性,并将定义数据的完整性(强制数据一致性)作为模型的一部分。RDBMS应实施关系模型并提供方法来存储、管理、实施完整性和查询数据。
1.命题、谓词和关系
很多人认为“关系”是指表之间的关系,这是不正确的。在数学的集合理论中,关系是集合的表现形式。在关系模型中,关系是相关信息的集合,与SQL中相对应的就是一个表——尽管不能完全对应。关系模型中的一个关键点是,单个的关系应代表一个单个集合(如客户)。需要注意的是,对多个关系操作(例如,两个关系之间的联接)的结果也是一个关系。
关系是由表头和主体组成的。表头包含一组属性(SQL中称之为列),其中每个元素由一个属性名称和类型名称标识。主体包含一个元组(SQL中称之为行)集合,其中每个元素都由键标识。为了简单起见,将表称之为行集。
当为数据库设计数据模型时,要使用关系(表)来表示所有数据。首先要确定需要在数据库中表示的命题。命题应当是一个断定或必须为“真”或“假”的语句。例如,语句“雇员 A 出生于1971年2月12日,在IT部门工作”是一个命题,如果这是一个真命题,它将表现为Employees (雇员)表中的一个行,如果是假命题,则根本不会出现。这个假设被称为“封闭世界假设”(close world assumption,CWA)。
下一步是将命题形式化,即取出实际数据(关系的主体)并定义结构(关系的表头)。可以以参数化命题方式思考谓词,关系的表头包含一个属性集合。属性由属性名称和类型名称进行标识,例如,一个Employees(雇员)关系的表头可能包含下列属性(属性名称和类型名称以成对方式出现):employeeid 整型、firstname 字符串型、lastname 字符串型、birthdate 日期型、departmentid整型。
类型是关系最基本的构造块,它约束了属性是一个可能或有效值的确定集合。例如,INT类型是范围自–2 147 483 648至2 147 483 647所有整数的集合。类型是数据库中谓词的最简单形式之一,因为它限制了该属性的允许值。例如,数据库不会接受一个雇员的出生日期为1971年2月31日的命题(更不用提像“abc”这样的生日)。注意,类型不受像整型或字符串型等基本类型的限制,它也可以是可能值的枚举,例如枚举可能的工作岗位。
2.缺失值
关系模型的一个方面是对于“是否谓词应限制于二值逻辑”的激情辩论。也就是说,在二值谓词逻辑中谓词是“真”或“假”。如果谓词不是“真”,那肯定是“假”。不过,也有人说具有三值(甚至四值)谓词逻辑空间,例如,像账号缺失这种情况。对于谓词,涉及缺失值就会出现既不是“真”也不是“假”的问题——因为它是未知的。例如,一个Employees(雇员)关系的手机属性,假设某些雇员的手机号码丢失了,怎么把这一事实表示在数据库中?这要使用三值逻辑实现,手机属性应当允许有一个代表缺失值的特定标记。然后,在这种缺失值情况下,将该手机属性与一些指定数字比较,谓词也将产生未知。三值谓词逻辑是指来自谓词结果的3 种可能逻辑值——“真”“假”和“未知”(true、false和unknown)。
SQL通过支持NULL标记实现了三值谓词逻辑,以表示缺失值的通用概念。SQL中对NULL和三值谓词逻辑的支持是造成极大混乱和复杂性的根源,一些资深的DBA在对故障查询语句进行分析时,费尽周折却发现是数据中存在NULL值导致的。当然,最好的方法是使用二值谓词逻辑,这是不会造成问题的,但是在实际工作中,你会发现这很难。尤其是对一些现有数据系统改造时,数据质量很差,你只能是尽可能地规范这些数据,而无法将其完全规范到二值逻辑中。
3.约束
关系模型的最大好处是能够把定义数据的完整性作为模型的一部分。数据完整性通过在数据模型中定义的约束规则实现,并由 RDBMS 实施。实施完整性的最简单方式是指定属性类型的nullability(是否支持或不支持 NULL 标记)标志。约束也可以通过模型自身实施,例如,限定关系Orders(orderid, orderdate, duedate, shipdate)中每笔订单有3 个互异日期,关系Employees(empid) 与EmployeeChildren(empid, childname)允许每个雇员有0 或多个子集。
约束的其他例子还包括提供实体完整性的候选键和提供引用完整性的外键。候选键是定义了一个或多个属性的键,防止关系中出现多个相同的元组(SQL中的行),基于候选键的谓词可以唯一地标识行(如雇员)。你可以在关系中定义多个候选键,例如,在Employees 关系中,可以在employeeid、SSN(社会安全号码)等属性上定义候选键。通常,可以任意选择一个候选键作为主键(例如,Employees 关系中的 employeeid),并作为标识行的首选方式。其他的所有候选键称为备用键。
外键用于强制引用完整性。外键定义了关系的一个或多个属性(称为引用关系)引用另一个关系(或同一关系)的候选键,此约束限定了引用关系的外键属性中的值,应是出现在被引用关系(父表)的候选键属性中的值。例如,假设Employees关系具有一个定义在departmentid属性上的外键,它引用Departments关系中的主键属性departmentid,这意味着Employees.departmentid中的值会出现在Departments.departmentid中的值。
4.规范化
关系模型还定义了规范化规则(也称为范式),用于确保每个实体都由单一关系表示。在规范化的数据库中,要在数据修改过程中避免异常,并在不牺牲完整性的情况下保持最低限度冗余。下面简要介绍一下由Codd提出的前3个范式(1NF、2NF和3NF)。
(1)1NF
第一范式是说关系(表)中的元组(行)必须是唯一的,并且属性是原子化的。也就是说,如果表正确地表示了关系,它已经符合了第一范式。
通过为表定义一个唯一键就可以实现唯一行。
你只能执行属性类型所定义的操作,我们知道,属性的原子性是主观的,这与集合的定义是主观的一样。例如,Employees关系中雇员姓名应当使用1个(fullname)、2个(firstname和lastname)还是3个(firstname、middlename和lastname)属性表示呢?答案取决于应用程序。如果应用程序需要分别处理雇员的姓名部分(如处于搜索目的),则有必要把它们分开,否则,则不需要。
同样,基于应用程序的需求,属性可能不被完全原子化,也有可能被亚原子化。例如,如果地址属性被作为一个特定应用程序的原子,不将“城市”作为地址的一部分会违反第一范式。
(2)2NF
第二范式涉及两个规则。一个规则是数据必须满足第一范式,另一个规则提及非键属性和候选键属性之间的关系。对于每个候选键,每个非键属性必须是对整个候选键的完全函数依赖。也就是说,如果要获取任何非键属性值,需要提供相同元组中候选键的所有属性的值;如果知道候选键的所有属性的值,就可以检索到任何元组的任何属性的任何值。
下面是一个违反第二范式的例子,假设定义了一个名为 Orders 的关系,表示订单和订单行的信息,如图1-1所示。Orders 关系包含下列属性:orderid、productid、orderdate、qty、customerid和companyname。主键定义为orderid和productid。
图1-1中违反了第二范式,因为有非键属性仅依赖于候选键(即该示例的主键)的一部分。例如,你可以仅通过orderid找到订单的 orderdate,以及 customerid 和 companyname。要符合第二范式,需要将原来的关系拆分为两个关系,即Orders和OrderDetails,如图1-2所示。Orders关系将包括orderid、orderdate、customerid和companyname属性,主键定义为orderid。OrderDetails关系将包括orderid、productid和qty,主键定义为orderid和productid。
(3)3NF
第三范式也有两个规则。数据必须满足第二范式,同时,所有非键属性必须依赖于非传递的候选键。通俗地讲,该规则的意思是所有非键属性必须相互独立。换句话说,一个非键属性不能依赖于另一个非键属性。
先前所述的Orders和OrderDetails关系现在已经符合第二范式。请记住,此时的Orders关系包含 orderid、orderdate、customerid 和 companyname 属性,主键定义为 orderid。customerid 和companyname均依赖于主键——orderid。例如,你需要通过orderid主键来查找代表订单中客户的customerid,同样,你需要通过 orderid 主键查找订单中客户的公司名称(companyname)。然而, customerid和companyname也是互相依靠的。为满足第三范式,需要添加包含customerid(作为主键)和 companyname 的 Customers 关系,如图1-3所示,然后就可以从 Orders 关系中删除companyname属性。
通俗地讲,2NF和3NF通常以这句话概括:“每个非键属性依赖于键,依赖于整个键,并且除了键别无他物”。
在介绍了SQL的起源后,来看一下Transact-SQL包括哪些语言类型。首先,为了遵循ANSI SQL标准,Transact-SQL 提供了数据定义语言(Data Definition Language,DDL)语句和数据操纵语言(Data Manipulation Language,DML)语句;其次,为了增强灵活性,Transact-SQL还提供了用于编程的流控制语句和其他语句。
对于语言类型,读者仅做大致了解就可以。在实际应用中,就像没必要区分哪些是SQL标准,哪些是SQL扩展一样,也没有必要区分DDL和DML,它们是一个协同工作的整体。
DDL语句用于创建数据库对象,如表、视图、索引等,表1-1中列出了一些常用的DDL语句。
DML语句用来检索和修改数据库的内容,表1-2中列出了一些常用的DML语句。
许多人在学习编程时,经常询问编程工具的好坏,哪种语言好学,或是在开发上更具备优势。实际上,编程工具可能还有优劣之分,但是编程语言之间差别并不大。尤其是像SQL这样的数据处理语言,只要具备了顺序语句、判断语句和循环语句,就可以处理所有业务逻辑问题。表1-3中列出了一些常用的编程和流控制语句。
下面将对一些常用的编程语句进行介绍。
1.IF...ELSE语句
当条件为“真”时,将执行IF关键字后面的语句,当条件为“假”时,将执行ELSE后面的语句。例如,下面示例的含义是:如果变量@i > 0,设置@MyVar1 = 100,否则设置@MyVar1 = 10,最后使用PRINT语句输出@MyVar1的值。其中的“--”是注释字符。
DECLARE @i AS int, @MyVar1 AS int; -- 声明变量,详细信息参考1.5节的介绍
SET @MyVar1 = 0; -- 设置变量值
SET @i = 1;
IF @i > 0
SET @MyVar1 = 100;
ELSE
SET @MyVar1 = 10;
PRINT CONVERT(char(12),@MyVar1); --输出@MyVar1变量的值
2.BEGIN...END
BEGIN...END提供了执行一组SQL语句的方法,此语句对编写IF...ELSE和WHILE循环非常有用。现在将上面示例中IF...ELSE语句改写为下列形式:
IF @i > 0
SET @MyVar1 = 100;
ELSE
BEGIN
SET @MyVar1 = 10;
PRINT CONVERT(char(12),@MyVar1); --输出@MyVar1变量的值
END
此时IF...ELSE 语句的含义是:如果@i > 0,设置@MyVar1 = 100,否则设置@MyVar1 = 10,并使用PRINT 语句输出@MyVar1 的值。也就是说,SET @MyVar1 = 10 和PRINT语句此时都属于ELSE部分。
3.WHILE循环
在WHILE关键字后面,可以编写一个控制循环执行的条件。在WHILE语句循环体的每一次执行前,都要测试条件。如果条件为真,则执行循环体;否则,将执行循环体后面的语句。
例如,下面的示例中使用了循环和判断语句。首先声明3个变量,@MyVar1用于累加1~100之间的奇数值,@MyVar2用于累加1~100之间的偶数值。
DECLARE @i AS int, @MyVar1 AS int, @MyVar2 AS int; -- 声明变量
SET @MyVar1 = 0; -- 设置变量值
SET @MyVar2 = 0;
SET @i = 1;
WHILE @i < 100 -- 开始循环
BEGIN --指定包含在循环中的语句
IF @i % 2 <> 0 --判断是否为奇数
SET @MyVar1 = @MyVar1 + @i; -- 累加奇数
ELSE
SET @MyVar2 = @MyVar2 + @i; -- 累加偶数
SET @i = @i + 1; --递增@i
END --结束循环
SELECT @MyVar1, @MyVar2, @i; -- 显示变量值
4.GOTO语句和标签
GOTO语句用于将执行流更改到标签处,虽然Transact-SQL和PL/SQL都提供了该语句,但是作为编程而言,我们不推荐使用此编程技术。要编写一个标签,应当在标识符后面加一个冒号。例如,下面的示例使用GOTO语句代替WHILE循环,与上面的WHILE循环示例功能相同。
DECLARE @i AS int, @MyVar1 AS int, @MyVar2 AS int; -- 声明变量
SET @MyVar1 = 0; -- 设置变量值
SET @MyVar2 = 0;
SET @i = 1;
table_loop: --指定标签
IF @i % 2 <> 0 --判断是否为奇数
SET @MyVar1 = @MyVar1 + @i; -- 累加奇数
ELSE
SET @MyVar2 = @MyVar2 + @i; -- 累加偶数
SET @i = @i + 1; --递增@i
IF (@i < 100) GOTO table_loop --跳转到标签处
SELECT @MyVar1, @MyVar2, @i; -- 显示变量值
5.使用WAITFOR语句
WAITFOR语句用于延迟后面语句的执行,可以指定延迟的时间长度或是具体的时间。参考下面的语句:
WAITFOR DELAY '00:1:15';
PRINT N'到时间了';
-- 另一种形式
WAITFOR TIME '10:02:10';
PRINT N'到时间了';
第一个WAITFOR语句使用DELAY关键字指定在1分15秒后执行后面的PRINT语句,最长延迟时间为24小时。第二个WAITFOR语句使用了TIME关键字,指定在10点2分10秒的时候执行后面的PRINT语句。
此语句通常用于并发测试,实际应用中并不多见。例如,可以指定在同一个时间执行多个相同的SQL语句。
应用程序可以将多个SQL语句作为一个批发送到服务器,然后服务器将该批中的语句编译成一个执行计划。在SQL Server 的Management Studio 工具,可以使用GO作为批发送的分隔符号。例如,在图1-4所示的语句中共包含3个批,选中后单击“执行”按钮一起执行。但是由于包含3个GO,所以会被分批发送,其中第1和第2个批都能正常执行,第3个批中由于最后一行INSERT语句的VALUSE关键字错了(正确的应当为VALUES),这个批在将这3个INSERT语句编译成一个执行计划时,将发生错误。因此,这 3 条 INSERT 语句都不会被执行,而不是仅仅发生错误的INSERT语句。从这个示例可以看出,理解批处理Transact-SQL语句的执行方式很重要。
Transact-SQL具有一些大多数语句都使用或受之影响的元素,包括标识符、数据类型、函数、表达式、运算符和保留关键字等。
数据库对象的名称即为其标识符,如服务器、数据库和数据库对象(例如表、视图、列、索引、触发器、过程、约束及规则等)都可以有标识符。对象标识符是在定义对象时创建的,创建完成后便可以使用标识符引用该对象。例如,下列语句创建一个名为 TableX 的表,其中包含 KeyCol 和Description 列,则TableX、KeyCol 和 Description 都是标识符。
CREATE TABLE TableX (KeyCol INT PRIMARY KEY, Description nvarchar(80));
无论是 TableX、还是 KeyCol 和 Description,这些中间无空格的字符,都称为常规标识符。常规标识符格式规则取决于数据库兼容级别(可以使用 sp_dbcmptlevel 存储过程设置该级别)。当兼容级别为90(SQL Server 2005)、100(SQL Server 2008)或110(SQL Server 2012)时,常规标示符使用下列规则。
第一个字符必须是下列字符之一。
Unicode标准3.2所定义的字母。Unicode中定义的字母包括拉丁字符a~z和A~Z,以及来自其他语言的字母字符。
下划线(_)、at符号(@)或者数字符号(#)。
在SQL Server中,某些位于标识符开头位置的符号具有特殊意义。以at符号开头的标识符表示局部变量或参数。以一个数字符号开头的标识符表示临时表或过程。以两个数字符号(##)开头的标识符表示全局临时对象。
某些Transact-SQL函数的名称以两个at符号(@@)开头。为了避免与这些函数混淆,不应使用以“@@”开头的名称。
后续字符可以包括以下几类。
如Unicode标准3.2中所定义的字母。
基本拉丁字符或其他国家/地区字符中的十进制数字。
at符号、美元符号($)、数字符号或下划线。
此外,常规标识符不能是 Transact-SQL 保留字,不允许嵌入空格或其他特殊字符,不允许使用Unicode标准之外的增补字符。
如果标示符中必须使用空格(如My Table)或其他不符合常规标示符规则的字符,则必须包含在双引号(")或者方括号([ ])内,否则SQL Server 无法正确识别它们。双引号和方括号被称为分隔标示符。例如,下面语句中的My Table 和order 必须包含在分隔标识符内,因为My Table中间有空格,order 是SQL Server 用于ORDER BY 子句的保留字。
SELECT * FROM [My Table] WHERE [order] = 10;
常规标识符和分隔标识符包含的字符数必须在1~128。对于本地临时表,标识符最多可以有116个字符。
在使用双引号作为分隔符时,SQL Server遵从的规则受SET QUOTED_IDENTIFIER设置影响。设置为ON(默认值)时,双引号只能分隔标识符,文字必须由单引号分隔;设置为OFF时,标识符不能加引号,且必须符合所有常规标识符规则。
如果字符串中已经包含有单引号,则应在该单引号前再添加一个单引号。例如,下面的语句用于从My Table 表中查找 Last Name 为O’Brien 的行。
SELECT * FROM "My Table"
WHERE "Last Name" = 'O''Brien';
大多数SQL语句并不显式引用数据类型,但是,由于语句中所引用对象的数据类型间的交互作用,语句的返回结果会受到影响。下列对象具有数据类型。
表和视图中的列。
存储过程中的参数。
变量。
返回一个或多个特定数据类型数据值的Transact-SQL函数。
具有返回代码(始终为integer数据类型)的存储过程。
为对象分配数据类型时可以为对象定义以下4个属性。
对象包含的数据种类。
所存储值的长度或大小。
数值的精度(仅适用于数字数据类型)。
数值的小数位数(仅适用于数字数据类型)。
1.基本类型
SQL Server 所支持的数据类型大体可分为精确数字、近似数字、日期和时间、字符串、Unicode字符串、二进制字符串和其他数据类型7种类别,详细信息如表1-4所示。
2.用户自定义数据类型
除了上面介绍的数据类型,还可以在SQL Server中创建三种用户自定义数据类型。
一种是从基本数据类型创建的别名数据类型,这样做的目的是为了更清楚地说明对象中值的类型。例如,下面的语句创建了一个基于 datetime 的 birthday 数据类型,用于在employee 的emp_birthday列中存储生日数据。
-- 创建一个允许null的birthday数据类型
CREATE TYPE birthday
FROM datetime NULL;
GO
-- 创建一个使用新数据类型的表
CREATE TABLE employee
(emp_id char(5), emp_first_name char(30), emp_last_name char(40), emp_birthday birthday);
另一种是CLR 用户定义数据类型,它是在Microsoft .NET Framework 公共语言运行时(CLR)使用编程方法创建的,这是从SQL Server 2005 开始提供的一种新功能。此外,包括触发器、存储过程、函数、聚合函数,都可以利用CLR 提供的丰富的编程模型来扩展SQL Server 的功能。
最后一种是用户定义表数据类型,也就是说用户可以定义一个表示表结构的数据类型。这是从SQL Server 2008 开始提供的一种新功能。下面的语句首先创建一个名为LocationTableType的表数据类型,然后创建一个基于该类型的变量,并向其中插入数据和查询数据。
-- 创建一个表数据类型LocationTableType
CREATE TYPE LocationTableType AS TABLE
(
FirstName VARCHAR(50),
LastName VARCHAR(50)
);
GO
-- 创建一个基于LocationTableType的变量
DECLARE @MyTable AS LocationTableType;
-- 向变量中插入数据行
INSERT INTO @MyTable VALUES('Ken','Levy');
INSERT INTO @MyTable VALUES('Sara','Ford');
-- 查询数据
SELECT * FROM @MyTable;
GO
3.数据类型的隐式转换
当两个不同数据类型的表达式用运算符组合后,数据类型优先级规则指定将优先级较低的数据类型转换为优先级较高的数据类型。如果此转换不是所支持的隐式转换,则返回错误。当两个操作数表达式具有相同的数据类型时,运算的结果便为该数据类型。优先级顺序是:用户定义数据类型(最高)、sql_variant、xml、datetime、smalldatetime、float、real、decimal、money、smallmoney、bigint、int、smallint、tinyint、bit、ntext、text、image、timestamp、uniqueidentifier、nvarchar、nchar、varchar、char、varbinary、binary(最低)。
与其他程序设计语言中的函数相似,SQL Server的函数可以有零个、一个或多个参数,并返回一个标量值或表格形式的值的集合。
表达式是标识符、值和运算符的组合,SQL Server 可以对其求值以获取结果。访问或更改数据时,可在多个不同的位置使用数据。例如,可以将表达式用作要在查询中检索的数据的一部分,也可以用作查找满足一组条件的数据时的搜索条件。
表达式可以是下列任何一种形式:
常量
函数
列名
变量
子查询
CASE、NULLIF或COALESCE
运算符是表达式的组成部分之一,可以使用运算符执行算术、比较、串联或赋值操作。例如,表达式PriceColumn * 1.1中的乘号(*)使价格提高百分之十。
注释是程序代码中不执行的文本字符串,也称为备注。注释可用于对代码进行说明或暂时禁用正在进行诊断的部分、SQL语句和批。使用注释对代码进行说明,便于将来对程序代码进行维护。
SQL Server 支持两种类型的注释字符。
--(双连字符)。该注释字符可与要执行的代码处在同一行,也可另起一行。从双连字符开始到行尾的内容均为注释。对于多行注释,必须在每个注释行的前面使用双连字符。
/* ... */(正斜杠-星号字符对)。这些注释字符可与要执行的代码处在同一行,也可另起一行,甚至可以在可执行代码内部。开始注释对(/*)与结束注释对(*/)之间的所有内容均视为注释。对于多行注释,必须使用开始注释字符对(/*)来开始注释,并使用结束注释字符对(*/)来结束注释。批中的注释没有最大长度限制。
下面是一些有效注释的示例。
USE AdventureWorks2014;
GO
-- 这是我的单行注释
SELECT EmployeeID, Title
FROM HumanResources.Employee;
GO
/* 这是多行注释的第1行,
这是多行注释的第2行。*/
SELECT Name, ProductNumber, Color
FROM Production.Product;
GO
-- 在一个SQL语句中添加注释
SELECT ContactID, /* FirstName, */ LastName
FROM Person.Contact;
-- 在代码行后使用注释
USE AdventureWorks2014;
GO
UPDATE Production.Product
SET ListPrice = ListPrice * .9; -- 我在代码行使用注释
GO
SQL Server 保留某些关键字供自己专用。例如,在osql 或SQL Server 代码编辑器会话中使用DUMP 关键字或BACKUP关键字,即表示让SQL Server 备份全部或部分数据库或者备份日志。
除SQL Server 定义的位置以外,在其他任何位置上,如果在SQL语句中使用保留关键字,均为非法。数据库中对象的名称不能与保留关键字相同。如果有这样的名称,则必须始终使用带分隔符的标识符来引用这个对象。尽管这个方法允许存在名称为保留关键字的对象,还是建议不要用与保留关键字相同的名称命名任何数据库对象。
常量和变量是程序设计过程中必不可少的元素,在前面的内容也涉及了对变量的简单介绍。
常量,也称为文字值或标量值,是表示一个特定数据值的符号。常量的格式取决于它所表示的值的数据类型。
1.字符串常量
字符串常量包含在单引号内,可以由字母数字字符(a-z、A-Z和0-9)以及特殊字符(如!、@和#)组成。例如:
'这是我的字符串常量'
如果单引号中的字符串包含一个嵌入的引号,可以使用两个单引号表示嵌入的单引号。对于嵌入在双引号中的字符串则没有必要这样做。例如,下面是“I’m Tom”常量的正确书写方式。
'I''m Tom'
空字符串用中间没有任何字符的两个单引号表示。
2.Unicode字符串常量
Unicode字符串的格式与普通字符串相似,但它前面有一个N标识符(N代表SQL-92标准中的区域语言)。N 前缀必须是大写字母。例如,'Michel'是字符串常量,而 N'Michel'则是 Unicode常量。
Unicode常量被解释为Unicode格式数据,并且不使用代码页进行计算。对于字符数据,存储Unicode数据时,每个字符使用2个字节,而不是每个字符1个字节。
3.二进制常量
二进制常量具有前辍 0x,并且是十六进制数字字符串。这些常量不使用引号括起。例如,下面是二进制字符串的示例:
0xAE
0x12Ef
0x69048AEFDD010E
0x (空二进制常量)
4.bit常量
bit常量使用数字0或1表示,并且不括在引号中。如果使用一个大于1的数字,则该数字将转换为1。
5.datetime常量
datetime 常量使用特定格式的字符日期值来表示,并被单引号括起来。SQL Server可识别下列格式中用单引号(')括起来的日期和时间。
字母日期,如'April 15, 1998'。
数值日期格式,如'4/15/1998'。
未分隔的字符串格式,如'19981207'指1998年12月7日。
下面分别是使用24和12小时制方式表示的时间常量:
'14:30:24'
'04:24 PM'
SQL Server 所支持的所有时间格式,请参考1.7.4节的表1-16。
6.integer常量
integer常量以没有用引号括起来并且不包含小数点的数字字符串来表示。integer常量必须全部为数字,并且不能包含小数。例如:
1894
2
7.decimal常量
decimal常量由没有用引号括起来并且包含小数点的数字字符串来表示。例如:
1894.1204
2.0
8.float和real常量
float和real常量使用科学记数法来表示。例如:
101.5E5
0.5E-2
9.money常量
money常量是以可选的货币符号作为前缀为的一串数字。money常量可以包含小数点,但是不能使用引号括起来。例如:
$12
$542023.14
10.uniqueidentifier常量
uniqueidentifier常量是表示GUID的字符串。可以使用字符或二进制字符串格式指定。例如,下面的示例指定的是相同的GUID:
'6F9619FF-8B86-D011-B42D-00C04FC964FF'
0xff19966f868b11d0b42d00c04fc964ff
11.指定负数和正数
可以在数字前面添加“+”和“-”一元运算符来表示正数和负数。如果没有添加一元运算符,则默认为正数。例如:
-- integer表达式
-2147483648
-- decimal表达式
+145345234.2234
-2147483648.10
-- float表达式
-12E5
-- money表达式
-$45.56
变量对应内存中的一个存储空间。与常量不同,变量的值在运行过程中可以随时改变。
1.局部变量和全局变量
根据变量的作用范围不同,可以分为局部变量和全局变量。
局部变量是用户在程序中定义的变量,它仅在定义的程序范围内有效。局部变量可以用来保存从表中读取的数据,也可以作为临时变量保存计算的中间结果。在批处理和脚本中的变量通常用于:
作为计数器计算循环执行的次数或控制循环执行的次数;
保存数据值以供控制流语句测试;
保存存储过程返回代码要返回的数据值或函数返回值。
局部变量名称的第一个字符必须为一个@。
在SQL Server 的早期版本中,如果变量以@@开头,则被称为全局变量。这些变量实际上是SQL Server 的系统函数,它们的语法遵循函数的规则。用户可以在程序中使用这些函数测试系统特性和SQL命令的执行情况。
2.声明变量
变量只有在声明后才能使用,可以使用 DECLARE 语句来声明变量。在声明变量时可以指定变量的数据类型和长度。例如,下面的DECLARE语句使用int数据类型创建名为@MyCounter的局部变量。
DECLARE @MyCounter int;
如果要声明多个局部变量,需要在定义的局部变量后使用一个逗号,然后指定下一个局部变量名称和数据类型。例如,下面的 DECLARE 语句创建三个局部变量,名称分别为@LastName、@FirstName和@Salary。
DECLARE @LastName nvarchar(30), @FirstName nvarchar(20), @Salary decimal(7,2);
在使用变量时,需要注意变量的作用域。变量具有局部作用域,只在定义它们的批处理或过程中可见。作用域范围从声明变量的地方开始到声明变量的批处理或存储过程的结尾。例如,下面的脚本存在语法错误,因为在一个批处理中引用了在另一个批处理中声明的变量。
USE AdventureWorks2014; -- 指定使用的数据库
GO
DECLARE @MyVariable int;
SET @MyVariable = 1;
GO -- 该语句将结束批
-- @MyVariable已经超出了范围并不再存在
-- 下面的SELECT语句会发生语法错误,因为它引用了一个不存在的变量
SELECT *
FROM HumanResources.Employee
WHERE EmployeeID = @MyVariable;
3.为变量设置值
在声明变量后,变量值被默认设置为NULL。要为变量赋值,可以使用SET或SELECT语句。其中,SET是为变量赋值的首选方法。
仍旧使用上面的示例,将声明的MyVariable变量赋值为1,并在SELECT语句的WHERE子句中使用该变量。语句如下:
USE AdventureWorks2014; -- 指定使用的数据库
GO
DECLARE @MyVariable int;
SET @MyVariable = 1; -- 设置变量值为1
SELECT * FROM HumanResources.Employee
WHERE EmployeeID = @MyVariable;
GO
变量也可以通过选择列表中当前所引用的值赋值。如果在选择列表中引用变量,则它应当被赋以标量值或者SELECT语句应仅返回一行。例如:
USE AdventureWorks2014; -- 指定使用的数据库
GO
DECLARE @EmpIDVariable int;
SELECT @EmpIDVariable = MAX(EmployeeID)
FROM HumanResources.Employee;
SELECT @EmpIDVariable; --显示@EmpIDVariable的值
GO
注意 如果在单个 SELECT 语句中有多个赋值子句,SQL Server 并不保证表达式求值的顺序。只有当赋值之间有引用时才能看到影响。
如果 SELECT 语句返回多行而且变量引用一个非标量表达式,则变量被设置为结果集最后一行中表达式的返回值。例如,下面的语句将@EmpIDVariable设置为所返回的最后一行的EmployeeID字段的值。
USE AdventureWorks2014;
GO
DECLARE @EmpIDVariable int;
SELECT @EmpIDVariable = EmployeeID
FROM HumanResources.Employee
ORDER BY EmployeeID DESC;
SELECT @EmpIDVariable; --显示@EmpIDVariable的值
GO
运算符是一种符号,用来指定要在一个或多个表达式中执行的操作。SQL Server 所使用的运算符可以分为算术运算符、赋值运算符、位运算符、比较运算符、逻辑运算符、字符串串联运算符和一元运算符,共7个类别。
算术运算符可以对两个表达式执行数学运算,这两个表达式可以是数值数据类型类别的任何数据类型。可用算术运算符如表1-5所示。
加(+)和减(-)运算符也可用于对datetime和smalldatetime型值执行算术运算。
在进行算术运算时,需要注意计算结果的精度、小数位数和数据类型长度的变化。所谓精度,是指数字中的数字个数。小数位数是数中小数点右边的数字个数。例如,数123.45的精度是5,小数位数是2。
numeric 和decimal数据类型的默认最大精度为38,而早期的SQL Server 版本中,默认的最大精度为28。
数字数据类型的长度是指存储此数所占用的字节数。字符串或Unicode数据类型的长度是字符个数。binary、varbinary和image数据类型的长度是字节数。例如,int数据类型可以有10位数,用4个字节存储,不接受小数点。int数据类型的精度是10,长度是4,小数位数是0。
除了decimal类型之外,数字数据类型的精度和小数位数是固定的。如果算术运算符有两个相同类型的表达式,结果就为该数据类型,并且具有对此类型定义的精度和小数位数。如果运算符有两个不同数字数据类型的表达式,则由数据类型优先级决定结果的数据类型。
表1-6列出了当运算结果是decimal类型时,如何计算结果的精度和小数位数。只有当下列任一条件成立时,结果才为decimal数据类型。
两个表达式都是decimal类型。
一个表达式是decimal类型,而另一个是比decimal优先级低的数据类型。
操作数表达式由表达式e1(精度为p1,小数位数为s1)和表达式e2(精度为p2,小数位数为s2)来表示。非decimal类型的任何表达式的精度和小数位数,采用该表达式的当前设置。
注意 结果精度和小数位数的绝对最大值为38。当结果精度大于38时,相应的小数位数会减少,以避免结果的整数部分被截断。
等号(=)是唯一的赋值运算符。它通常与SET语句一起使用,为变量赋值。例如,下面将创建一个@MyCounter变量,然后使用赋值运算符为其赋值。
DECLARE @MyCounter INT
SET @MyCounter = 1
也可以使用赋值运算符在列标题和定义列值的表达式之间建立关系。下面的语句将显示列标题FirstColumnHeading和SecondColumnHeading。在所有行的FirstColumnHeading列中均显示字符串“xyz”。然后,在SecondColumnHeading列中列出来自Product表的每个产品ID。
USE AdventureWorks
GO
SELECT FirstColumnHeading = 'xyz',
SecondColumnHeading = ProductID
FROM Production.Product;
GO
位运算符在两个表达式之间执行按位运算,这两个表达式可以是整数数据类型类别中的任何数据类型。可用的位运算符如表1-7所示。
位运算符的操作数可以是整数或二进制字符串数据类型类别中的任何数据类型(image数据类型除外),但两个操作数不能同时是二进制字符串数据类型类别中的某种数据类型。表 1-8 列出了所支持的操作数数据类型。
对于按位进行与运算、或运算和异或运算的计算规则如表1-9所示。
比较运算符测试两个表达式是否相同。除了text、ntext或image数据类型的表达式外,比较运算符可以用于所有的表达式。表1-10列出了可用的比较运算符。
使用比较运算符的表达式的计算结果为布尔数据类型,它有 3 种值:TRUE、FALSE 和UNKNOWN。与其他SQL Server 数据类型不同,布尔数据类型不能被指定为表列或变量的数据类型,也不能在结果集中返回。
逻辑运算符对某些条件进行测试,以获得其真实情况。逻辑运算符和比较运算符一样,返回带有TRUE或FALSE值的布尔数据类型。表1-11列出了可用的逻辑运算符。
加号(+)是字符串串联运算符,可以用它将字符串串联起来。其他所有字符串操作都使用字符串函数(如SUBSTRING)进行处理。
例如,下面使用加号将“abc”和“efg”串联起来,得到的结果是“abcefg”。
'abc'+'efg'
在进行字符传串联时,需要注意字符串长度的变化。
在将两个char、varchar、binary或varbinary表达式串联时,所生成表达式的长度是两个源表达式长度之和,或是8000字符,以二者中少者计算。
在将两个nchar或nvarchar表达式串联时,所生成表达式的长度是两个源表达式长度之和,或是4000字符,以二者中少者计算。
一元运算符只对一个表达式执行操作,该表达式可以是数字数据类型类别中的任何一种数据类型。表1-12列出了可用的一元运算符。
+和-运算符可以用于数字数据类型类别中任意一数据类型的任意表达式。~运算符只能用于整数数据类型类别中任意一数据类型的表达式。
为满足通常的程序设计需要,Transact-SQL语言提供了非常丰富的函数,包括聚合函数、配置函数、游标函数、日期和时间函数、数学函数等。本节将介绍一些常用的函数类型。
聚合函数对一组值执行计算并返回单个值。除了COUNT以外,聚合函数都会忽略空值。聚合函数经常与SELECT 语句的GROUP BY 子句一起使用。
例如,下面的语句使用COUNT函数来获取Employee表中的员工数量。
USE AdventureWorks2014; -- 指定使用的数据库
SELECT COUNT(EmployeeID)
FROM HumanResources.Employee;
GO
又如,下面的语句使用SUM函数计算SalesOrderDetail表中的销售总额。
USE AdventureWorks2014;
SELECT SUM(LineTotal)
FROM Sales.SalesOrderDetail;
GO
配置函数用于返回当前配置选项的设置信息。
例如,下面的语句使用@@LANGUAGE函数返回当前会话的语言名称。
SELECT @@LANGUAGE AS 语言名称;
游标函数用于返回有关游标的信息。
下面的示例使用@@FETCH_STATUS函数和WHILE循环,逐步检索游标中的行,直至检索完毕。
DECLARE Employee_Cursor CURSOR FOR
SELECT EmployeeID, Title FROM AdventureWorks.HumanResources.Employee; -- 建立游标
OPEN Employee_Cursor; --打开游标
FETCH NEXT FROM Employee_Cursor; -- 开始检索
WHILE @@FETCH_STATUS = 0
BEGIN
FETCH NEXT FROM Employee_Cursor; -- 检索下一行
END
CLOSE Employee_Cursor; -- 关闭游标
DEALLOCATE Employee_Cursor; -- 删除游标引用
GO
日期和时间函数用于对日期和时间输入值执行操作,并返回一个字符串、数字值或日期和时间值。
数学函数根据提供的输入值执行计算后返回一个数值。其中,算术函数(如ABS、CEILING、DEGREES、FLOOR、POWER、RADIANS 和 SIGN)返回与输入值相同数据类型的值。三角函数和其他函数(包括EXP、LOG、LOG10、SQUARE和SQRT)将输入值转换为float并返回float值。
数据类型转换函数仅包括CAST和CONVERT函数,二者的功能类似。使用这两个函数可以显式地将一种数据类型的表达式转换为另一种数据类型的表达式。
下面分别是CAST和CONVERT函数的语法格式:
CAST ( expression AS data_type [ (length ) ])
CONVERT ( data_type [ ( length ) ] , expression [ , style ] )
expression是任何有效的表达式。data_type是目标数据类型(仅限于系统数据类型),包括xml、bigint和sql_variant。length是nchar、nvarchar、char、varchar、binary或varbinary数据类型的可选参数,对于CONVERT函数,如果未指定length,则默认为30个字符。
style用于以下三个方面。
指定在将datetime或smalldatetime数据转换为字符数据(nchar、nvarchar、char、varchar、nchar或nvarchar数据类型)时的日期格式的样式。
用于将float、real、money或smallmoney数据转换为字符数据时的字符串格式的样式。
用于指定二进制和字符型十六进制值之间数据转换时的格式(仅限于SQL Server 2008)。
表1-18列出了将datetime或smalldatetime数据转换为字符数据时的可用值。左侧的两列是style的值,将style值加100,将返回包括世纪数的四位年份格式。
表1-19列出了在将float或real转换为字符数据时的style可用值。
表1-20列出了在将money或smallmoney转换为字符数据时的style可用值。
在将numeric或decimal数据转换为字符数据时,如果要删除结果集尾随的零,可以使用128作为style的值。
表1-21列出了在将字符串转换为xml数据时的style可用值。
下面的语句使用CAST函数将Production.Product表中的ListPrice列由money转换为varchar数据类型。
USE AdventureWorks2014;
SELECT 'The list price is ' + CAST(ListPrice AS varchar(12)) AS ListPrice
FROM Production.Product;
GO
返回的结果类似下列形式:
ListPrice
-------------------------
The list price is 8.09
The list price is 108.00
The list price is 7.16
使用CONVERT函数同样可以实现上面的功能,参考下面的语句。
USE AdventureWorks2014;
SELECT 'The list price is ' + CONVERT(varchar(12), ListPrice) AS ListPrice
FROM Production.Product;
GO
表1-22列出了二进制和字符型十六进制值转换时的style可用值。
参考下面的示例代码:
-- 转换二进制值0x4E616d65到一个字符值
SELECT CONVERT(char(8), 0x4E616d65, 0) AS 'Style 0, 二进制到字符';
-- 下面的示例演示了Style为1的情况下,如何强行截断结果值
-- 产生的结果值由于包含字符0x ,所以被截断
SELECT CONVERT(char(8), 0x4E616d65, 1) AS 'Style 1, 二进制到字符';
-- 下面的示例演示了Style为2的情况下,没有截断结果值
-- 这是因为0x字符未包含在结果中
SELECT CONVERT(char(8), 0x4E616d65, 2) AS 'Style 2, 二进制到字符';
-- 转换字符值Name到一个二进制值
SELECT CONVERT(binary(8), 'Name', 0) AS 'Style 0, 字符到二进制';
SELECT CONVERT(binary(4), '0x4E616D65', 1) AS 'Style 1, 字符到二进制';
SELECT CONVERT(binary(4), '4E616D65', 2) AS 'Style 2, 字符到二进制';
结果如图1-5所示。
使用字符串函数可以对字符串输入值进行剪裁、替换等操作,并返回字符串或数值。字符串函数及其功能如表1-23所示。
使用文本和图像函数可以对文本或图像输入值或列执行操作。文本和图像函数及其功能如表1-24所示。
从SQL server 2012 开始,增加了IIF和CHOOSE 两个逻辑函数。这两个函数都是非标准的,目的是为了支持更容易地从Microsoft Access 数据库进行迁移。函数功能如表1-25所示。
在本章的前面介绍了SQL的语法规则、常量和变量、运算符、函数等基本知识,到底通过什么工具来执行SQL语句呢?对于程序开发人员来而言,可以从客户端应用程序发送SQL语句到服务器端执行。此外,出于服务器管理和SQL测试等需要,SQL Server 也提供了Management Studio、sqlcmd、bcp和sqlps工具,可以通过它们执行SQL语句。
其中,bcp 用于大量行插入SQL Server 表,但该工具不需要具有Transact-SQL知识。sqlps 是一个Microsoft C#命令提示实用工具,用于以交互方式即席运行PowerShell命令或是运行PowerShell脚本文件。由于这两个工具的功能超出了本书的范围,我们仅对SQL Server Management Studio和sqlcmd做一下介绍。
在Windows中依次选择“开始”→“程序”→“Microsoft SQL Server 2014”,单击SQL Server 2014 Management Studio(在Vista中要以管理员身份运行),将打开登录窗口,选择身份验证方式后,将打开Management Studio。
单击工具栏中的“新建查询”按钮可以打开一个查询窗口,如图1-6所示。可以在查询窗口中输入SQL语句后,单击“执行”按钮执行查询。如果希望仅执行其中的部分语句,可以选定要执行的语句,然后再单击“执行”按钮。
在Windows中依次选择“开始”→“程序”→“附件”→“命令提示符”(在Vista中要以管理员身份运行),将打开“命令提示符”窗口。
要连接到 SQL Server 服务器,必须指定服务器名称。安装在命名实例中的,还必须指定实例名。默认情况下,sqlcmd使用Windows身份验证。如果要使用SQL Server身份验证连接到SQL Server的,则还必须提供连接用户名和密码。例如,如果要连接到名为server1的服务器,则需要使用下列参数:
sqlcmd -S server1 -U SqlUserAccount -P SqlPassword
如果是受信任的Windows用户,则可以省略-U和-P参数。例如,图1-7所示的sqlcmd窗口中使用sqlcmd –S (local)命令连接到服务器,并从HumanResources.Employee 表中查找EmployeeID 为1的雇员。注意其中的GO命令,该命令用于执行所输入的SQL语句。
要退出sqlcmd,可以执行exit或quit命令。
书写规范与语法规范是两个完全不同的概念,违反语法规范会导致程序执行错误,而违反书写规范虽然不会导致错误,但是会导致阅读困难和代码的通用性。这些书写规范是根据大多数人阅读代码时的习惯而提出的,并不是必须完全遵守的。
1.在名称中仅使用字母、数字和下划线
之所以要在名称中仅使用字母、数字和下划线,因为这些字符可以被移植到任何其他编程语言中。在应用程序的数据库和宿主语言中能够使用相同的名称,会非常方便。
但是,也存在一些特殊情况。例如,在SQL Server中临时表名称需要以“#”开头,而它在其他编程语言中具有特殊含义。如果必须使用临时表,则只能使用“#”。此外,参数名称也存在这种情况,它需要以“@”开头。但是,无论怎样,在名称中尽量避免使用特殊符号是一个非常正确的选择。
不要将下划线作为名称的第一个或最后一个字母,因为这看上去像少了一部分一样。
2.列名、参数和变量等标量小写
通常情况下,小写单词比大写容易阅读。曾经做过测试,阅读小写文本的速度比大写的速度快5%~10%。当名称由两个单词组合而成时,为便于阅读,应当采用大小写混合的写法。例如,下面按由易至难的方式列出了存放修改日期列的三种书写方法:
ModifiedDate --比较容易阅读
modifieddate --阅读难度增加
MODIFIEDDATE --阅读最困难
但是,也有一种观点认为大小写混合的写法阅读起来比全部小写要难一些,原因是在全部小写的情况下,会把modifieddate看作一个单词,而ModifiedDate这种形式会被看作两个单词,分散注意力。总之,在列名、参数和变量中全部使用大写字母是一个非常糟糕的选择。
3.模式对象名首字母大写
模式对象包括表、视图和存储过程等,在创建这些名称时,应当将首字母大写,表示为专有名词。
4.保留关键字大写
保留关键字是 Transact-SQL 语言语法的一部分,用于定义、操作和访问数据库。将保留关键字大写后,会起到一种突出效果,使整个语句重点突出、结构清晰。看一下下面的语句:
select a, b, c from MyTable where id = 1;
对比一下:
SELECT a, b, c FROM MyTable WHERE id = 1;
阅读上面的两个语句,看一下能否快速找出每个子句,而下面的书写格式则阅读起来会更清晰。
SELECT a, b, c
FROM MyTable
WHERE id = 1;
下面列出了SQL Server 的保留关键字:
ADD | ALL | ALTER | AND | ANY | AS | ASC | AUTHORIZATION
BACKUP | BEGIN | BETWEEN | BREAK | BROWSE | BULK | BY
CASCADE | CASE | CHECK | CHECKPOINT | CLOSE | CLUSTERED | COALESCE
COLLATE | COLUMN | COMMIT | COMPUTE | CONSTRAINT | CONTAINS
CONTAINSTABLE | CONTINUE | CONVERT | CREATE | CROSS | CURRENT
CURRENT_DATE | CURRENT_TIME | CURRENT_TIMESTAMP | CURRENT_USER
CURSOR
DATABASE | DBCC | DEALLOCATE | DECLARE | DEFAULT | DELETE | DENY
DESC | DISK | DISTINCT | DISTRIBUTED | DOUBLE | DROP | DUMP
ELSE | END | ERRLVL | ESCAPE | EXCEPT | EXEC | EXECUTE | EXISTS
EXIT | EXTERNAL
FETCH | FILE | FILLFACTOR | FOR | FOREIGN | FREETEXT | FREETEXTTABLE
FROM | FULL | FUNCTION
GOTO | GRANT | GROUP
HAVING | HOLDLOCK
IDENTITY | IDENTITY_INSERT | IDENTITYCOL | IF | IN | INDEX | INNER | INSERT
INTERSECT | INTO | IS
JOIN
KEY | KILL
LEFT | LIKE | LINENO | LOAD
MERGE |
NATIONAL | NOCHECK | NONCLUSTERED | NOT | NULL | NULLIF
OF | OFF | OFFSETS | ON | OPEN | OPENDATASOURCE | OPENQUERY | OPENROWSET
OPENXML | OPTION | OR | ORDER | OUTER | OVER
PERCENT | PIVOT | PLAN | PRECISION | PRIMARY | PRINT | PROC
PROCEDURE | PUBLIC
RAISERROR | READ | READTEXT | RECONFIGURE | REFERENCES | REPLICATION
RESTORE | RESTRICT | RETURN | REVERT | REVOKE | RIGHT | ROLLBACK
ROWCOUNT | ROWGUIDCOL | RULE
SAVE | SCHEMA | SECURITYAUDIT | SELECT | SEMANTICKEYPHRASETABLE
SEMANTICSIMILARITYDETAILSTABLE | SEMANTICSIMILARITYTABLE
SESSION_USER | SET | SETUSER | SHUTDOWN | SOME | STATISTICS | SYSTEM_USER
TABLE | TABLESAMPLE | TEXTSIZE | THEN | TO | TOP | TRAN | TRANSACTION
TRIGGER | TRUNCATE | TRY_CONVERT | TSEQUAL
UNION | UNIQUE | UNPIVOT | UPDATE | UPDATETEXT | USE | USER
VALUES | VARYING | VIEW
WAITFOR | WHEN | WHERE | WHILE | WITH | WITHIN GROUP | WRITETEXT
在语言标记之间放置一个空格,尽量地符合英语书写习惯,可以增强语句的可阅读性。
1.等号两边使用空格
在书写赋值语句时,应当在等号两边使用空格分隔,如SET @i = 1比SET @i=1 更容易阅读。
2.逗号后面使用空格
应当遵循在逗号后面使用空格的原则,因为英语中逗号和句号很容易混淆。例如:
SELECT MyTable.a,MyTable1.b,MyTable2.c
FROM MyTable,MyTable1,MyTable2;
下面的形式会更容易阅读一些:
SELECT MyTable.a, MyTable1.b, MyTable2.c
FROM MyTable, MyTable1, MyTable2;
当表或列名称比较长时,下面的形式则更好一些。
SELECT EmployeeID,
Title,
BirthDate,
MaritalStatus
FROM HumanResources.Employee;
必要的缩进会使语句的层次和逻辑关系更加清晰,通常是缩进2个空格。例如,在下面的语句中,AND关键词连接了两个筛选条件,缩进后会更加突出WHERE子句。
SELECT *
FROM HumanResources.Employee
WHERE ManagerID = 16
AND EmployeeID > 100;
下面是一个左外连接的语句,首先将HumanResources.Employee和Person.Contact表中列分别放在了单独的行中,以便进行区分;然后LEFT缩进后表示与FROM后面的表进行连接,ON再次缩进表示是LEFT的连接条件。
SELECT E.EmployeeID, E.Title,
P.FirstName, P.LastName, P.EmailAddress
FROM HumanResources.Employee AS E
LEFT OUTER JOIN Person.Contact AS P
ON E.EmployeeID = P.ContactID
WHERE E.ManagerID = 16
AND E.EmployeeID > 100;
还有一种观点认为,在关键词与参数之间应当使用垂直空白道的方式进行分隔,会增强可阅读性。例如:
SELECT E.EmployeeID, E.Title,
P.FirstName, P.LastName, P.EmailAddress
FROM HumanResources.Employee AS E
LEFT OUTER JOIN Person.Contact AS P
ON E.EmployeeID = P.ContactID
WHERE E.ManagerID = 16
AND E.EmployeeID > 100;
又如,下面的语句使用了垂直空白道分隔,并对子查询使用了缩进。
SELECT DISTINCT CustName
FROM Customers AS C
WHERE NOT EXISTS
(SELECT *
FROM OrderHeader
WHERE CustID = Customers.CustID);
存在多行 Transact-SQL 的情况下,相关语句之间可以直接换行书写,而对于两个步骤之间的语句应当间隔一个空行。如果需要的话,也可以加入一些适当的注释语句。例如:
USE AdventureWorks2014;
GO
-- 读取 Employee 表的数据
SELECT *
FROM HumanResources.Employee;
GO
从本章开始,以及后面的几章,我们将简要讲述一下在SQL Server中创建数据库、表和索引等方面的知识。实际上,如果仅仅是单纯的 SELECT 语句查询,你可能感觉不到数据库存在的价值和意义。在一些小型数据库系统中,对于自由表同样支持符合ANSI标准的SELECT查询,并不需要创建数据库。但是,当需要保持表之间数据的一致性时,你可能需要使用到触发器这样的工具,例如,当删除一个表中的某行时,触发器会自动删除另一个表中的相关行,这时候就需要使用到数据库。或是当你在开发客户/服务器程序时,如果将所有数据处理都下载到客户端去执行,那将是非常耗时的工作,如果把在服务器端可以完成的逻辑计算分离出来,单独交给服务器来执行,就可以显著提高执行的效率,这时候你可能需要使用到存储过程或函数,这些对象也是存储在数据库中的。从这方面讲,数据库是一个存储表、索引,以及表之间逻辑运算关系的容器。
在SQL Server中,可以通过SQL语句或SQL Server Management Studio 来进行数据库管理,包括创建、修改和删除数据库操作。
本节将介绍 SQL Server 数据库的实现方式,包括实例、数据库、架构、数据库文件存储等内容。
传统的 SQL Server 数据库服务方式是安装在客户场所内,客户负责所有的事情——硬件、安装软件、处理更新、高可用性和灾难恢复(HADR)、安全,以及其他事项。客户可以在同一个服务器上安装产品的多个实例,可以编写查询与多个数据库交互,也可以在数据库之间切换连接。
当然,目前已经进入了云时代,数据库作为IT基础架构中最重要的部分,与云的结合将变得非常重要。微软支持两种 SQL Server 云:私有云和公共云。云这个术语用于私有情况应当是有点混乱,主要因为它是在本地托管的,但私有云使用了虚拟化技术。公共云称为Windows Azure SQL Database(以前名为SQL Azure),它是在微软数据中心托管, 硬件、维护、HADR和更新全部由微软负责,但是客户仍旧负责索引和查询优化。
使用公共云,客户可以在云服务器(当然是一个概念性的服务器)上有多个数据库,但一次只能连接到一个数据库,客户不能在数据库之间切换,也不能编写多数据库查询。
此外,SQL Server 公共云服务与企业内部部署的SQL Server在功能上会有所差异,某些运行在内部部署上的SQL语句,可能不能运行在SQL Server 公共云上。但是,公共云的版本更新和部署进度比企业内部部署的SQL Server 快很多,因此,一些SQL 功能有可能在它们出现在企业内部部署的SQL Server 版本之前已经在SQL Database中实现了。
在企业内部部署的SQL Server中可以安装多个实例,如图2-1所示。在安全、所管理数据和所有其他资源方面,每个实例完全独立于其他实例。
可以将计算机中多个实例中的一个实例作为默认实例,其他实例则必须是命名实例。在安装时要确定是默认实例还是命名实例,确定以后便不能更改。若要连接到默认实例,客户端应用程序可以指定计算机的名称或IP地址。若要连接到命名实例,客户端需要指定计算机的名称或IP地址,后跟一个反斜杠(\),再跟上实例名称(这是在安装时指定的)。例如,假设你在一台名为Server1的计算机上安装有两个 SQL Server 实例,其中一个实例是以默认实例安装,另一个作为命名实例安装,称为Inst1。要连接到默认实例,只需要指定Server1作为服务器名称。但是,若要连接到命名实例,需要指定服务器和实例名称:Server1\Inst1。
允许在同一台计算机上安装 SQL Server 的多个实例会有多种好处。例如,一个数据库服务支持公司为了能够重现客户遇到的问题,就可以安装一个新实例来模拟客户的生产环境,而对其他实例不会产生影响。再如,数据库服务供应商有时候需要保证其客户数据与其他客户数据完全安全分离,就可以采取这种方式解决。
对于云方式的数据库连接,在你购买服务时,服务商会告诉你连接方法。
架构是从SQL Server 2005 开始引入的,之前没有架构的概念,只有用户的概念。架构独立于创建它们的数据厍用户而存在,每个对象都属于一个数据库架构(对象包括表、视图、存储过程等),如图2-2所示。你可以把架构看作表、视图、存储过程等对象的容器。
数据库架构是一个独立于数据库用户的非重复命名空间,用户与架构相互独立,删除用户不会删除架构中的对象。将架构与数据库用户分离对管理员和开发人员而言有下列好处。
极大地简化了删除数据库用户的操作。删除数据库用户不需要重命名该用户架构所包含的对象。因而,在删除创建架构所含对象的用户后,不再需要修改和测试显式引用这些对象的应用程序。
多个用户可以共享一个默认架构以进行统一名称解析。
开发人员通过共享默认架构可以将共享对象存储在为特定应用程序专门创建的架构中,而不是DBO架构中。
可以用更大的粒度管理对象的权限。
如果数据库用户具有创建架构的权限,就可以在数据库中创建和更改架构,该用户就是架构的所有者,并且可以将架构的所有者转让给其他用户,或者授予其他用户访问该架构的权限,只有架构的所有者才能在架构中创建对象。也就是说,你可以在架构级别控制权限。例如,可以授予用户对架构的 SELECT 权限,允许用户从该架构的所有对象中查询数据。引入架构后,就可以实现对架构内的对象统一设置访问权限。所以说,对于确定如何安排架构中的对象,安全是一个很重要的考虑因素。
架构作为一个命名空间——它被用作对象名称的前缀。例如,假设在一个名为Sales的架构中有一个名为Orders的表。架构限定式对象名称(也称为“两部分式对象名称”)是Sales.Orders。引用对象时如果省略了架构名称,SQL Server 将通过一个过程来解决架构名称,如检查对象是否在用户的默认架构中,如果不是,则检查是否存在于dbo架构中。微软建议在代码中引用对象时始终使用“两部分式”对象名称。在没有显式指定架构名称的情况下,在判断对象时会有一些不必要的额外支出,既然是不必要的,为什么要支出呢? 此外,如果多个具有相同名称的对象存在于不同架构中,结果往往是得到一个不同的对象,而不是想要的对象。
SQL Server 将数据库映射为一组操作系统文件。数据和日志信息绝不混合在同一个文件中,而且一个文件只能由一个数据库使用。文件组是文件的命名集合,用于简化数据存放和管理任务(如备份和还原操作)。
1.数据库文件
数据库是由数据文件和事务日志文件构成的。创建数据库时,你可以为每个文件定义各种属性,包括文件名称、位置、初始大小、最大大小和一个自动增长的增量。每个数据库必须至少有一个数据文件和一个日志文件(SQL Server默认的)。数据文件存储对象数据,日志文件存储SQL Server需要维护事务的信息。
数据文件包括主文件、次要文件。主文件中包含着数据库的启动信息。此外,主文件还用于存储数据。每个数据库都有一个主文件。主文件的建议文件扩展名为.mdf。
次要文件包含不能放置在主数据文件中的所有数据。如果主文件足够大,能够包含数据库中的所有数据,则该数据库不需要次要数据文件。有些数据库可能非常大,因此需要多个次要数据文件,也可能在独立的磁盘驱动器上使用次要文件以将数据分散到多个磁盘上。次要文件的建议文件扩展名为.ndf。
在创建数据库时,应当根据数据库中预期的最大数据量,创建尽可能大的数据文件。
事务日志文件包含用于恢复数据库的日志信息,每个数据库必须至少有一个事务日志文件。日志文件最小为512 KB。事务日志的建议文件扩展名为.ldf。
虽然 SQL Server 可以并行写入多个数据文件,但它只能以连续方式一次写入一个日志文件。因此,与数据文件不同,有多个日志文件不会带来性能提升。如果日志所在的磁盘驱动器空间不足,你可能需要添加日志文件。
2.文件和文件组的填充策略
数据文件被组织在称为“文件组”的逻辑组中。文件组是所创建对象(如表或索引)的目标,对象数据将分散在其所隶属目标文件组的文件中,文件组可以按你要求的方式来控制对象的物理位置。
每个数据库至少有一个PRIMARY文件组,此文件组包含主文件和未放入其他文件组的所有次要文件。可以创建用户定义的文件组,用于将数据文件集合起来,以便于管理、数据分配和放置。你可以决定哪个文件组被标记为默认文件组。如果创建对象时的语句没有显式指定一个不同的目标文件组,则对象将被建立在默认文件组中。
文件组对组内的所有文件都使用按比例填充策略。将数据写入文件组时,数据库引擎会根据文件中的可用空间量将一定比例的数据写入文件组中的每个文件,而不是将所有数据先写满第一个文件,然后再写入下一个文件。例如,如果文件f1有100 MB可用空间,文件f2有200 MB可用空间,则从文件f1中分配一个区,从文件f2中分配两个区,依次类推。这样,两个文件几乎同时填满。
文件组中的所有文件一满,数据库引擎就自动按照循环方式一次扩展一个文件,以容纳更多数据(假定数据库设置为自动增长)。例如,某个文件组由三个文件组成,它们都设置为自动增长。当文件组中所有文件的空间都已用完时,只扩展第一个文件。当第一个文件已满,无法再向文件组中写入更多数据时,将扩展第二个文件。当第二个文件已满,无法再向文件组中写入更多数据时,将扩展第三个文件。当第三个文件已满,无法再向文件组中写入更多数据时,将再次扩展第一个文件,依次类推。
使用文件和文件组可以改善数据库的性能,因为这样允许跨多个磁盘、多个磁盘控制器或RAID (独立磁盘冗余阵列)系统创建数据库。例如,如果计算机上有 4 个磁盘,那么可以创建一个由 3个数据文件和1个日志文件组成的数据库,每个磁盘上放置1个文件。在对数据进行访问时,4个读/写磁头可以同时并行地访问数据。这样可以加快数据库操作的速度。
另外,文件和文件组还允许数据布局,可以在特定的文件组中创建表。这样可以改善性能,因为可以将特定表的所有 I/O 都定向到一个特定的磁盘。例如,可以将最常用的表放在一个文件组的一个文件中,该文件组位于一个磁盘上;而将数据库中其他不常访问的表放在另一个文件组的其他文件中,该文件组位于第二个磁盘上。
3.文件和文件组的设计规则
下列规则适用于文件和文件组。
一个文件或文件组不能由多个数据库使用。
一个文件只能是一个文件组的成员。
数据和事务日志信息不能属于同一个文件或文件组。
事务日志文件不能属于任何文件组。
下面是使用文件和文件组时的一些建议。
大多数数据库在只有单个数据文件和单个事务日志文件的情况下性能良好。
如果使用多个文件,应当为附加文件创建第二个文件组,并将其设置为默认文件组。这样,主文件将只包含系统表和对象。
要使性能最大化,应当在尽可能多的不同的可用本地物理磁盘上创建文件或文件组。应当将争夺空间最激烈的对象置于不同的文件组中。
使用文件组将对象放置在特定的物理磁盘上。
将在同一联接查询中使用的不同表置于不同的文件组中。由于采用并行磁盘I/O对联接数据进行搜索,所以性能将得以改善。
将最常访问的表和属于这些表的非聚集索引置于不同的文件组中。如果文件位于不同的物理磁盘上,由于采用并行I/O,所以性能将得以改善。
不要将事务日志文件置于其中已有其他文件和文件组的物理磁盘上。
在创建数据库之前,必须先确定数据库的名称、所有者、大小以及存储该数据库的文件和文件组。所谓所有者,即是创建数据库的用户。一般情况下,大多数产品对象由数据库所有者拥有。
在创建数据库之前,应注意下列事项。
要创建数据库,必须至少拥有CREATE DATABASE、CREATE ANY DATABASE或ALTER ANY DATABASE权限。
创建数据库的用户将成为该数据库的所有者。
对于一个SQL Server实例,最多可以创建32767个数据库。
数据库名称必须遵循为标识符指定的规则。
在创建新数据库时,model数据库中的所有用户定义对象都将复制到所有新创建的数据库中。因此,可以向model数据库中添加任何对象(如表、视图、存储过程和数据类型),以便将这些对象包含到所有新创建的数据库中。
可以使用CREATE DATABASE语句创建数据库,其语法格式如下:
CREATE DATABASE database_name
[ ON
[ PRIMARY ] [ <filespec> [ ,...n ]
[ , <filegroup> [ ,...n ] ]
[ LOG ON { <filespec> [ ,...n ] } ]
]
[ COLLATE collation_name ]
][;]
database_name
要创建的新数据库的名称。
ON
指定以显式定义方式指定存储数据库数据部分的磁盘文件(数据文件)。
PRIMARY
指定<filespec>列表中的主文件。在<filespec>项中的第一个文件将成为主文件。如果没有指定PRIMARY,则CREATE DATABASE 语句中列出的第一个文件将成为主文件。
LOG ON
指定存储数据库日志的磁盘文件(日志文件)。LOG ON 后跟着以逗号分隔的用于定义日志文件的<filespec>项列表。如果没有指定 LOG ON,将自动创建一个日志文件,其大小为该数据库的所有数据文件大小总和的25%或512 KB,取两者之中的较大者。
COLLATE collation_name
指定数据库的默认排序规则。排序规则名称既可以是Windows排序规则名称,也可以是SQL排序规则名称。如果没有指定排序规则,则将 SQL Server 实例的默认排序规则分配为数据库的排序规则。排序规则一般用于SELECT查询的ORDER BY 子句,详细信息参考5.6节的介绍。
CREATE DATABASE 语句中的<filespec>部分用于控制文件属性,其语法格式如下:
(
NAME = logical_file_name ,
FILENAME = 'os_file_name'
[ , SIZE = size [ KB | MB | GB | TB ] ]
[ , MAXSIZE = { max_size [ KB | MB | GB | TB ] | UNLIMITED } ]
[ , FILEGROWTH = growth_increment [ KB | MB | GB | TB | % ] ]
) [ ,...n ]
NAME logical_file_name
指定文件的逻辑名称。logical_file_name必须在数据库中唯一,必须符合标识符规则。
FILENAME ' os_file_name '
指定操作系统(物理)文件名称。执行CREATE DATABASE语句前,指定路径必须存在。如果指定了UNC(通用命名约定)路径,则无法设置SIZE、MAXSIZE和FILEGROWTH参数。
SIZE size
指定文件的初始大小。如果没有为主文件指定size,则数据库引擎将使用model数据库中的主文件的大小。如果指定了辅助数据文件或日志文件,但未指定该文件的size,则数据库引擎将以1 MB 作为该文件的大小。
可以使用千字节(KB)、兆字节(MB)、千兆字节(GB)或兆兆字节(TB)后缀,默认为 MB。
MAXSIZE max_size
指定文件可增大到的最大大小,可以使用KB、MB、GB和TB后缀,默认为 MB。
UNLIMITED
指定文件将增长到磁盘已满。在SQL Server中,指定为不限制增长的日志文件的最大大小为2 TB,而数据文件的最大大小为16 TB。
FILEGROWTH growth_increment
指定每次需要新空间时为文件添加的空间量。growth_increment值不能超过MAXSIZE设置值。该值可以使用 MB、KB、GB、TB 或百分比(%)为单位指定。默认值为 MB。growth_increment值为0时表明自动增长被关闭,不允许增加空间。
如果未指定FILEGROWTH,则数据文件的默认值为1 MB,日志文件的默认增长比例为10%,并且最小值为64 KB。
CREATE DATABASE 语句中的<filegroup>部分用于控制文件组属性,其语法格式如下:
FILEGROUP filegroup_name [ DEFAULT ]
<filespec> [ ,...n ]
FILEGROUP filegroup_name
文件组的逻辑名称。filegroup_name 必须在数据库中唯一,不能是系统提供的名称 PRIMARY和PRIMARY_LOG。
DEFAULT
指定文件组为数据库中的默认文件组。
1.创建未指定文件的数据库
下面的语句将创建名为mydata的数据库,并创建相应的主文件和事务日志文件。因为语句没有<filespec>项,所以主数据库文件的大小为 model 数据库主文件的大小。事务日志将设置为下列值中的较大者:512 KB 或主数据文件大小的25%。因为没有指定MAXSIZE,文件可以增大到填满所有可用的磁盘空间为止。
CREATE DATABASE mydata
2.创建指定数据和事务日志文件的数据库
下面的语句将创建数据库Sales。因为没有使用关键字PRIMARY,第一个文件(Sales_dat)将成为主文件。因为在Sales_dat文件的SIZE参数中没有指定MB或KB,将默认按MB分配。Sales_log文件以MB为单位进行分配,因为SIZE参数中显式声明了MB后缀。
CREATE DATABASE Sales
ON
( NAME = Sales_dat,
FILENAME = 'c:\saledat.mdf',
SIZE = 10,
MAXSIZE = 50,
FILEGROWTH = 5 )
LOG ON
( NAME = Sales_log,
FILENAME = 'c:\salelog.ldf',
SIZE = 5MB,
MAXSIZE = 25MB,
FILEGROWTH = 5MB ) ;
3.通过指定多个数据和事务日志文件创建数据库
下面的语句将创建数据库Archive,该数据库具有3个100 MB 的数据文件和两个100 MB 事务日志文件。主文件是列表中的第一个文件,并使用 PRIMARY 关键字显式指定。事务日志文件在LOG ON关键字后指定。
CREATE DATABASE Archive
ON
PRIMARY
(NAME = Arch1,
FILENAME = 'c:\archdat1.mdf',
SIZE = 100MB,
MAXSIZE = 200,
FILEGROWTH = 20),
( NAME = Arch2,
FILENAME = 'c:\archdat2.ndf',
SIZE = 100MB,
MAXSIZE = 200,
FILEGROWTH = 20),
( NAME = Arch3,
FILENAME = 'c:\archdat3.ndf',
SIZE = 100MB,
MAXSIZE = 200,
FILEGROWTH = 20)
LOG ON
(NAME = Archlog1,
FILENAME = 'c:\archlog1.ldf',
SIZE = 100MB,
MAXSIZE = 200,
FILEGROWTH = 20),
(NAME = Archlog2,
FILENAME = 'c:\archlog2.ldf',
SIZE = 100MB,
MAXSIZE = 200,
FILEGROWTH = 20) ;
4.创建具有文件组的数据库
下面的语句将创建数据库Sales,该数据库具有以下文件组。
包含文件Spri1_dat和Spri2_dat的PRIMARY文件组。将这些文件的FILEGROWTH增量指定为15%。
名为SalesGroup1的文件组,其中包含文件SGrp1Fi1和SGrp1Fi2。
名为SalesGroup2的文件组,其中包含文件SGrp2Fi1和SGrp2Fi2。
CREATE DATABASE Sales
ON PRIMARY
( NAME = SPri1_dat,
FILENAME = 'c:\SPri1dat.mdf',
SIZE = 10,
MAXSIZE = 50,
FILEGROWTH = 15% ),
( NAME = SPri2_dat,
FILENAME = 'c:\SPri2dt.ndf',
SIZE = 10,
MAXSIZE = 50,
FILEGROWTH = 15% ),
FILEGROUP SalesGroup1
( NAME = SGrp1Fi1_dat,
FILENAME = 'c:\SG1Fi1dt.ndf',
SIZE = 10,
MAXSIZE = 50,
FILEGROWTH = 5 ),
( NAME = SGrp1Fi2_dat,
FILENAME = 'c:\SG1Fi2dt.ndf',
SIZE = 10,
MAXSIZE = 50,
FILEGROWTH = 5 ),
FILEGROUP SalesGroup2
( NAME = SGrp2Fi1_dat,
FILENAME = 'c:\SG2Fi1dt.ndf',
SIZE = 10,
MAXSIZE = 50,
FILEGROWTH = 5 ),
( NAME = SGrp2Fi2_dat,
FILENAME = 'c:\SG2Fi2dt.ndf',
SIZE = 10,
MAXSIZE = 50,
FILEGROWTH = 5 )
LOG ON
( NAME = Sales_log,
FILENAME = 'c:\salelog.ldf',
SIZE = 5MB,
MAXSIZE = 25MB,
FILEGROWTH = 5MB ) ;
5.创建数据库并指定排序规则名称
下面的示例将创建数据库MyOptionsTest,并将排序规则指定为French_CI_AI。
CREATE DATABASE MyOptionsTest
COLLATE French_CI_AI;
可以使用下面的语句验证数据库的选项设置:
SELECT name, collation_name
FROM sys.databases
WHERE name = N'MyOptionsTest';
在创建数据库后,应当备份master数据库。因为创建数据库将更新master中的系统表。如果master需要还原,则从上次备份master之后新建的所有数据库都将仍然在系统表中有引用,因而可能导致出现错误信息。
GO
可以使用DB_ID函数判断数据库是否已经存在,该函数用于返回数据库的标识号,如果标识号不为空,则表示数据库已经存在。例如,下面的语句返回AdventureWorks数据库的标识号。
SELECT DB_ID(N'AdventureWorks') AS [Database ID];
GO
如果未指定数据库名称参数,则返回当前数据库的标识号。例如,下面的语句返回当前数据库master的标识号。
USE master; -- 切换到master数据库
GO
SELECT DB_ID() AS [Database ID]; -- 返回当前数据库的标识号
GO
实际上,在创建示例数据库时经常用到该函数。例如,下面的语句首先判断要创建的 mytest数据库是否已经存在,如果存在则先删除掉,然后再新建数据库。
USE master;
IF DB_ID (N'mytest') IS NOT NULL
DROP DATABASE mytest;
GO
CREATE DATABASE mytest;
GO
创建数据库后,可以对其原始定义进行更改,如扩展或收缩数据库、设置数据库选项等。要修改数据库,可以使用ALTER DATABASE 等语句。
默认情况下,SQLServer可根据创建数据库时定义的增长参数自动扩展数据库。也可以通过为现有数据库文件分配更多空间,或者创建新文件来手动扩展数据库。如果未将数据库设置为自动增长或硬盘上没有足够的磁盘空间,数据库已经用完分配给它的空间且不能自动增长,会出现1105错误。
扩展数据库时,必须使数据库的大小至少增加1 MB。如果扩展了数据库,则根据被扩展的文件,数据文件或事务日志文件将可以立即使用新空间。扩展数据库时,应指定允许文件增长到的最大大小。这样可防止文件无限制地增大,以致于用尽整个磁盘空间。
可以使用ALTER DATABASE 语句设置数据库大小或向数据库添加文件,其语法格式如下:
ALTER DATABASE database_name
ADD FILE <filespec> [ ,...n ]
[ TO FILEGROUP { filegroup_name | DEFAULT } ]
| ADD LOG FILE <filespec> [ ,...n ]
| REMOVE FILE logical_file_name
| MODIFY FILE <filespec>
其中<filespec>部分用于设置文件组的属性,语法格式如下:
(
NAME = logical_file_name
[ , NEWNAME = new_logical_name ]
[ , FILENAME = 'os_file_name' ]
[ , SIZE = size [ KB | MB | GB | TB ] ]
[ , MAXSIZE = { max_size [ KB | MB | GB | TB ] | UNLIMITED } ]
[ , FILEGROWTH = growth_increment [ KB | MB | GB | TB| % ] ]
[ , OFFLINE ]
)
其中的OFFLINE选项用于将文件设置为脱机并使文件组中的所有对象都不可访问,仅在文件已损坏但可以还原时,才能使用该选项。其他参数选项请参考前面CREATE DATABASE语句中的说明。
例如,下面的语句用于将Sales中的SPri1_dat 文件扩展到15 MB,并将最大值设置为25 MB。
ALTER DATABASE Sales
MODIFY FILE
(
NAME = 'SPri1_dat',
SIZE = 15MB,
MAXSIZE = 25MB
)
下面的语句向SalesGroup1文件组中添加一个SGrp1Fi3_dat文件。
ALTER DATABASE Sales
ADD FILE
(
NAME = SGrp1Fi3_dat,
FILENAME = 'c:\SG1Fi3dt.ndf',
SIZE = 5MB,
MAXSIZE = 10MB,
FILEGROWTH = 5MB
)
TO FILEGROUP SalesGroup1 ;
执行下面的语句则可以删除上面添加的SGrp1Fi3_dat文件。
ALTER DATABASE Sales
REMOVE FILE SGrp1Fi3_dat ;
下面是使用ALTER DATABASE语句向数据库中添加、删除和修改文件组时的语法格式:
ALTER DATABASE database_name
ADD FILEGROUP filegroup_name
| REMOVE FILEGROUP filegroup_name
| MODIFY FILEGROUP filegroup_name
{ <filegroup_updatability_option>
| DEFAULT
| NAME = new_filegroup_name
}
<filegroup_updatability_option>部分的语法格式如下:
{ READONLY | READWRITE } | { READ_ONLY | READ_WRITE }
例如,下面的语句用于向Sales数据库中添加一个名为SalesGroup3的文件组。
ALTER DATABASE Sales
ADD FILEGROUP SalesGroup3 ;
下面的语句重命名文件组SalesGroup3为SalesGroup4。
ALTER DATABASE Sales
MODIFY FILEGROUP SalesGroup3
NAME = SalesGroup4 ;
可以使用DBCC SHRINKDATABASE语句或DBCC SHRINKFILE语句来手动收缩数据库或数据库中的文件。数据库中的每个文件都可以通过删除未使用的页的方法来减小。尽管数据库引擎会有效地重新使用空间,但某个文件多次出现无须原来大小的情况后,收缩文件就变得很有必要了。可以成组或单独地手动收缩数据库文件,也可以设置数据库的AUTO_SHRINK选项为ON来指定按间隔自动收缩。
文件始终从末尾开始收缩。例如,如果有个5 GB的文件,并且在DBCC SHRINKFILE 语句中指定为4 GB,则数据库引擎将从文件的最后一个1 GB开始释放尽可能多的空间。如果文件中被释放的部分包含使用过的页,则数据库引擎先将这些页重新放置到文件的保留部分。只能将数据库收缩到没有剩余的可用空间为止。例如,如果某个 5 GB 的数据库有 4 GB 的数据,并且在DBCC SHRINKFILE语句中指定为 3 GB,则只能释放1 GB。
在使用DBCC SHRINKDATABASE 语句时,无法将整个数据库收缩得比其初始大小更小。例如,如果数据库创建时的大小为10 MB,后来增长到100 MB,则该数据库最小只能收缩到10 MB,即使已经删除数据库的所有数据也是如此。
但是,使用DBCC SHRINKFILE 语句时,可以将各个数据库文件收缩得比其初始大小更小。必须对每个文件分别进行收缩,而不能尝试收缩整个数据库。
1.手动收缩数据库
下面是DBCC SHRINKDATABASE 语句的语法格式:
DBCC SHRINKDATABASE
( 'database_name' | database_id | 0
[ ,target_percent ]
[ , { NOTRUNCATE | TRUNCATEONLY } ]
)
[ WITH NO_INFOMSGS ]
'database_name' | database_id | 0
要收缩的数据库的名称或ID。如果指定0,则使用当前数据库。
target_percent
数据库收缩后的数据库文件中所需的剩余可用空间百分比。
NOTRUNCATE
指定在数据库文件中保留所释放的文件空间。如果未指定,将所释放的文件空间释放给操作系统。
TRUNCATEONLY
将数据文件中任何未使用空间释放给操作系统,并将文件收缩到最后分配的区,从而无须移动任何数据即可减小文件大小。使用TRUNCATEONLY时,将忽略target_percent设置。
WITH NO_INFOMSGS
取消严重级别从0到10的所有信息性消息。
下面的语句使Sales数据库中文件有10%的可用空间。
DBCC SHRINKDATABASE ('Sales', 10)
2.使用ALTER DATABASE设置自动收缩数据库
将数据库的AUTO_SHRINK选项设置为ON后,数据库引擎将自动收缩有可用空间的数据库。下面是使用ALTER DATABASE语句将Sales 数据库的AUTO_SHRINK 选项设置为ON的方法。
ALTER DATABASE Sales
SET AUTO_SHRINK ON ;
3.收缩文件
下面是DBCC SHRINKFILE 语句的语法格式:
DBCC SHRINKFILE
(
{ 'file_name' | file_id }
{ [ , EMPTYFILE ]
| [ [ , target_size ] [ , { NOTRUNCATE | TRUNCATEONLY } ] ]
}
)
[ WITH NO_INFOMSGS ]
'file_name'
要收缩的文件的逻辑名称。
file_id
要收缩的文件的标识(ID)号。可以使用FILE_ID函数获取文件的ID。
target_size
用兆字节表示的文件大小。如果未指定,则DBCC SHRINKFILE将文件大小减少到默认文件大小。
EMPTYFILE
将指定文件中的所有数据迁移到同一文件组中的其他文件。
NOTRUNCATE
将释放的文件空间保留在文件中。当与target_size一起指定NOTRUNCATE时,释放的空间不会释放给操作系统。唯一影响是将已使用的页从target_size行前面重新定位到文件的前面。
TRUNCATEONLY
将文件中的任何未使用空间释放给操作系统,并将文件收缩到最后一次分配的区,从而减小了文件大小,但是没有移动任何数据。不会尝试将行重新定位到未分配的页。使用TRUNCATEONLY时,将忽略target_size。
WITH NO_INFOMSGS
禁止显示所有信息性消息。
例如,下面的语句将Sales数据库中的SPri1_dat文件的大小收缩到8 MB。
USE Sales ;
GO
DBCC SHRINKFILE (SPri1_dat, 8) ;
以下示例演示了清空文件以便从数据库中将其删除的步骤。针对此示例,首先创建一个数据文件,并假设该文件包含数据。
USE AdventureWorks;
GO
-- 创建一个数据文件并假设其包含数据
ALTER DATABASE AdventureWorks
ADD FILE (
NAME = Test1data,
FILENAME = 'C:\t1data.ndf',
SIZE = 5MB
);
GO
-- 清空数据文件
DBCC SHRINKFILE (Test1data, EMPTYFILE);
GO
-- 从数据库中移除数据文件
ALTER DATABASE AdventureWorks
REMOVE FILE Test1data;
GO
可以在新建数据库时或对现有数据库通过“数据库属性”窗口进行部分数据库选项设置,而使用ALTER DATABASE的SET子句则可以进行更加全面的选项设置。可用的设置选项如表2-1所示。
例如,下面的语句设置Sales数据库的ANSI_NULLS和ANSI_NULL_DEFAULT选项为ON。
ALTER DATABASE Sales
SET ANSI_NULLS ON,ANSI_NULL_DEFAULT ON ;
在SQL Server中,可以更改数据库的名称。在重命名数据库之前,应该确保没有人使用该数据库,而且该数据库设置为单用户模式。
下面是使用ALTER DATABASE语句重命名数据库时的语法格式:
ALTER DATABASE database_name
MODIFY NAME = new_database_name ;
例如,下面语句将Sales数据库重命名为Sales1。
ALTER DATABASE Sales
SET SINGLE_USER; --设置为单用户
GO
ALTER DATABASE Sales
MODIFY NAME = Sales1; --重命名为Sales1
GO
ALTER DATABASE Sales1 --重新设置为多用户
SET MULTI_USER;
在数据库删除之后,文件及其数据都将从服务器磁盘中删除。在删除数据库时,可以不用管数据库所处的状态(包括脱机、只读和可疑),但是应当满足下列前提条件。
如果数据库涉及日志传送操作,在删除数据库之前应当取消日志传送操作。
要删除为事务复制发布的数据库,或删除为合并复制发布或订阅的数据库,应当首先从数据库中删除复制。如果数据库已损坏,不能首先删除复制,则通常仍然可以通过首先使用ALTER DATABASE将数据库设置为脱机然后再删除的方法来删除数据库。
必须首先删除数据库上存在的数据库快照。
在删除数据库后,应备份master数据库,因为删除数据库将更新master数据库中的信息。
可以使用DROP DATABASE 语句删除数据库,其语法格式如下:
DROP DATABASE { database_name | database_snapshot_name } [ ,...n ]
database_name
指定要删除的数据库的名称。
database_snapshot_name
指定要删除的数据库快照的名称。
例如,下面的语句将删除Sales数据库。
DROP DATABASE Sales ;
前面提过,架构是数据库中对象的容器,对于被集体管理的对象子集,通过架构可以简化操作。用户拥有架构和默认架构 DBO,默认架构用于解析未使用其完全限定名称引用的对象的名称。这里的意思是,在访问默认架构中的对象时,不需要指定架构的名称。而对于非默认架构,则需要使用下面的格式进行访问,不过,微软建议你一直使用下面的两部分式对象名称。
架构名称.对象名称
可以使用CREATE SCHEMA 语句创建架构,语法格式如下所示。其中的schema_element部分允许使用CREATE TABLE、CREATE VIEW、GRANT、REVOKE和DENY 语句来定义此架构包含的表和视图,并可对单个语句中的任何安全对象授予、撤销或拒绝授予权限。
CREATE SCHEMA schema_name_clause [ <schema_element> [ , ...n ] ]
<schema_name_clause> ::=
{
schema_name
| AUTHORIZATION owner_name
| schema_name AUTHORIZATION owner_name
}
<schema_element> ::=
{
table_definition | view_definition | grant_statement
revoke_statement | deny_statement
}
schema_name
在数据库内标识架构的名称,最大长度是128个字符。
AUTHORIZATION owner_name
指定将拥有架构的数据库级主体的名称。此主体还可以拥有其他架构,并且可以不使用当前架构作为其默认架构。
table_definition
指定在架构内创建表的CREATE TABLE语句。
view_definition
指定在架构内创建视图的CREATE VIEW 语句。
grant_statement
指定可对除新架构外的任何安全对象授予权限的GRANT语句。
revoke_statement
指定可对除新架构外的任何安全对象撤销权限的REVOKE语句。
deny_statement
指定可对除新架构外的任何安全对象拒绝授予权限的DENY语句。
例如,下面的语句将建立一个名为Employees的架构,所有者为dbo。
CREATE SCHEMA Employees AUTHORIZATION dbo
又如,下面的语句创建由Annik拥有的、包含表NineProngs的Sprockets架构。此语句向Mandar授予SELECT权限,而对Prasanna拒绝授予SELECT权限。
USE AdventureWorks2014;
GO
CREATE SCHEMA Sprockets AUTHORIZATION Annik
CREATE TABLE NineProngs (source int, cost int, partnumber int)
GRANT SELECT TO Mandar
DENY SELECT TO Prasanna;
GO
在创建一个架构后,可以根据需要使用GRANT、REVOKE、DENY语句对数据库用户进行授予、撤销授予、拒绝权限设置。虽然我们这本书是讲解SQL的,但是对于此操作,使用SQL Server Management Studio 会更方便一些,步骤如下。
在对象资源管理器中,展开数据库引擎实例。
展开“数据库”节点,从中选择一个数据库并展开。
展开数据库的“安全性”节点后,继续展开“架构”节点,右键单击要修改的架构并选择“属性”,打开架构的属性窗口。
在架构的属性窗口的“常规”页面中,可以指定架构的名称和所有者。要查找一个有效的数据库级主体,可以单击“搜索”按钮。
可以在架构的属性窗口的“权限”页面中设置架构的权限,步骤如下。
在“权限”页面中单击“添加”按钮打开“选择用户或角色”对话框。
在“选择用户或角色”对话框的编辑框中输入要选择的用户或角色名称,并使用分号将名称分割开。如图2-3所示。
在添加完用户或角色后,可以为其指定详细的权限设置,如图2-4所示。
下面的语句把对架构HumanResources的INSERT权限授予Guest用户。
GRANT INSERT ON SCHEMA :: HumanResources TO guest;
下面的语句则是撤销Guest用户的INSERT权限。
REVOKE INSERT ON SCHEMA :: HumanResources TO guest;
在某些时候,可能需要移动对象到另一个容器中。但是,只能将对象在同一数据库之内的架构之间移动,在移动时,将会改变与对象相关联的命名空间。因此,也将改变对象的查询和访问方法。
将对象移入新架构时,将删除与该对象关联的全部权限。如果已显式设置对象所有者,则该所有者保持不变。移动对象应当使用ALTER SCHEMA语句,语法格式如下:
ALTER SCHEMA schema_name TRANSFER object_name;
schema_name
当前数据库中的架构名称,对象将移入其中。
object_name
要移入架构的对象名称。
下面的语句将表Address从架构Person移动到HumanResources架构中。
ALTER SCHEMA HumanResources TRANSFER Person.Address ;
可以使用DROP SCHEMA语句删除架构。要删除的架构不能包含任何对象。例如,下面的语句将删除Employees架构:
DROP SCHEMA Employees ;