数据查询
且不说你是否正在从事编程方面的工作或者不打算学习SQL,可事实上几乎每一位开发者最终都会遭遇它。你多半还用不着负责创建和维持某个数据库,但你怎么着也该知道以下的一些有关的SQL知识。我为那些感兴趣的开发者或者能从数据库操作中得益的读者撰写了这篇关于基本SQL语法的概述性文章。本文主要讨论基本的数据操作查询,后续的文章还会继续讨论如何修改数据库自身以及更高级的查询概念。SQL数据库是怎么回事?SQL(结构化查询语言)就是负责与ANSI维护的数据库交互的标准。最新的版本是SQL-99,还有一个新标准SQL-200n尚处于制定过程中。大多数的数据库都至少遵守ANSI-92标准的部分子集。不过,目前对最新标准的有效性还存在一些争论。专有数据库制造商根据这些标准开发自己的产品,同时制定出自己特有的数据库存储操作新概念。几乎各种不同的数据库都包含了自己特有的语法集合,只是通常很类似ANSI标准。在大多数情况下,尽管有一些数据库实例基于特定的扩展语法会因数据库的不同而产生不同的结果,但总的说来,这些新加的语法不过是对原有标准的扩充。如果数据库操作并没有得到你希望的结果,那么你不妨事先读一读数据库制造商提供的产品说明。假如到目前为止你头回遭遇SQL语言,那么你怎么也得先理解一些基本的SQL概念。我尽量把这些基本知识阐述得简明扼要,如果你对那些数据库术语还能忍受,你尽可跳到下一节,此外你还可以把自己的问题提交给以下的讨论区。笼统地说,“SQL数据库”其实就是关系型数据库管理系统(RDMS)通俗的叫法。对某些系统来说,“数据库”也指一组数据表、数据以及相互区分但结构类似的配置信息。在这种情况下,每一SQL数据库的安装都可能由若干数据库组成。在有些系统上,这种数据库则指的是表空间。数据表是一种包含多行数据的数据库构造,这种数据库构造由命名的列组成。通常数据表构造为包含关系信息,同一数据库或表空间以内可以创建若干数据表。表内的列保存某一种类型的数据而且应根据其保存数据的内容得以命名。例如,被称为“LastName”的列就应该在每一行包含姓氏条目。正是这一前提的存在才能让关系数据库查询返回一致的结果。字段(field)指的是某一行某一列对应的数据(或保存数据的地方)。另外,数据集合(dataset)则指的是多行多列的数据,而且数据集合通常说明你的数据库或数据表内的全部数据。结果集合(resultset)就是从数据库查询返回的数据;它能够描述从单一字段到
数据库内全部数据这一范围内的全部信息。数据库查询就是发送给数据库的SQL指令,这些指令向数据库请求某种施加在数据集合或数据库上的功能。现在我们就来看看基本的数据库查询,这些查询主要涉及到对数据库内数据的操作。在
本文中,所有的例子都采用了标准SQL语言,而且这些基本功能可以转换为应用在几乎各种环境下。数据查询类型SQL语言中的数据查询分为4种基本类型:SELECT:这条语句要求数据库返回指定结果的数据集合;你可以用这一语句检索数据库中保存的信息。INSERT:这条语句用来给数据表增加新一行数据。DELETE:该语句从你的数据库中删除若干行数据。UPDATE:该语句修改数据库内的现有数据。以上的这些语句都有各种各样的限定词和函数供你用来定义有关的数据集合,同时控制查询返回的结果集合。SELECT语句的选项最多。有许多种组合SELECT的查询选项,例如JOIN和UNION等。不过就我们目前来说,本文主要还是关注基本用途。用SELECT语句检索保存的信息为了获得数据库中保存的信息就必须采用SELECT语句。其基本功能限制在针对单一数据表操作,当然,其他范围的构造也是有的。为了返回特定列所对应的所有数据行,你可以使用以下语句:SELECTcolumn1,column2FROMtable_name;另外,使用通配符“*”可以从表中选出所有的列:SELECT*FROMtable_name;你要愿意自己编码分析以上返回的结果当然也没问题,不过你完全可以采用方便的WHERE子句限制返回的结果集合,该子句可以让你为选择数据定义某些条件。以下查询就会返回“column1”数值等于3的所以数据行:SELECT*FROMtable_nameWHEREcolumn1=3;除了“=”(等于)条件之外你还可以用到下列条件运算符:=等于<>不等于>大于<小于>=大于或等于
<=小于或等于
SQL条件语句另外,你还可以联合WHERE语句使用BETWEEN、LIKE等比较运算符以及AND和OR这类逻辑运算符。注意,OR语句是包含性的的。以下有一个例子组合了以上这些概念:SELECT*FROMtable_nameWHERE((Age<18)AND(LastNameBETWEEN‘Anderson’AND‘Miller’))ORCompanyLIKE‘%School%’;用自然语言来说,这条选择语句的含义是这样的:从数据表中选出年龄小于18岁而且姓氏在“Anderson”和“MIller”之间的或者其公司名称类中有“School”字样的数据行。用INSERT语句加入新数据使用INSERT语句可以创建新的数据行。如果你希望在某一行的某个字段中赋值则要用到UPDATE语句。插入语句的语法如下:INSERTINTOtable_name(column1,column2,column3)VALUES(‘data1’,‘data2’,‘data3’);如果你想按照表内现有列的同
一顺序插入所有的值,那么你不必指定列名,当然,从可读性考虑最好不要这样做。另外,如果你列出列名则不必要按照它们在数据库中出现的顺序包括它们,只要你列出的值与它们一一对应即可。有些列你并没有为其输入新的信息所以你自然没有必要列出它们来。一旦数据库中有了数据要修改起来也与此很相似。UPDATE语句和WHERE子句UPDATE用来修改现有的值或行里的空字段,因此它必须在匹配现有的数据集合同时提供可接受的值。除非你真地想要修改所有数据行上的值,否则你必须使用WHERE子句。UPDATEtable_nameSETcolumn1=‘data1’,column2=‘data2’WHEREcolumn3=‘data3’;你可以采用WHERE子句随意匹配任何一列,正在修改的一列都可以。这样会有助于你把某一特定的值修改为另一个值:UPDATEtable_nameSETFirstName=‘Shelley’WHEREFirstName=‘Shelly’ANDLastName=‘Doll’;
小心DELETE语句DELETE语句会从数据库的数据表中删除整行。如果你仅仅想删除单一的字段则应该使
用UPDATE语句把它修改为代表应用程序中的NULL的其他空值。一定要小心使用带WHERE子句的DELETE语句,否则你可能会遭遇清空全部数据表的风险。DELETEFROMtable_nameWHEREcolumn1=‘data1’;一旦你数据库中删除某一行数据就不可再后悔了,因此一般来说,最好在数据表中包括一名为“IsActive”的列或类似的指示信息,这样你就可以把该列数据设置为零表示数据禁用。只有在你确信不再需要受到影响的信息之后你才可以用DELETE语句。小结SQL就是数据库的语言,我们已经了解了数据查询中所采用的最基本命令语句。但还有很多基本概念尚未涉及,例如SUN和COUNT函数等,但以上列出的这些命令应该足够你开始着手数据库操作了。
SELECT语句选项
用ORDERBY对查询结果排序ORDERBY子句让数据库对查询结果排序,这样你就无须自己编写应用程序进行“手工”排序了。ORDERBY子句必须放在查询语句的结尾。其基本用法如下:SELECT*FROMContactsORDERBYfirst_name;你可以随意在任何选择语句中使用ORDERBY子句返回多列结果。你还可以用它连接其他子句:SELECTfirst_name,last_nameFROMContactsWHEREfirst_nameBETWEEN‘a’AND‘k’ORDERBYlast_name;你可以对多列数据排序。优先顺序按从左到右依次降低,所以查询语句中各列的排列顺序很重要。SELECT*FROMContactsORDERBYcompany,last_name,first_name;查询结果默认按数字或者字母的升序排序。你可以在ORDERBY子句后面加上DESC关键词改成降序排列。在下面的例子中,最高的net_amount排在最先(降序)。假如两行或者两行以上数据都包含了
同样的net_amount值,那么同行中last_name值在字母表中最先出现的排先,因为last_name一列还是按照升序排序的。SELECT*FROMSalesORDERBYnet_amountDESC,last_name,first_name;在按照定义的列名排序以后,大多数数据库随后将按照数据表内的第一列排序然后顺序向右再排序。具体的实现各有变化,因此,如果排序在应用中比较重要那么你应该明确地定义所要排序的列。另外一值得注意的问题是,采用ORDERBY子句(以及WHERE子句),你正在用来排序结果的数据列并不一定得是返回结果集合的一部分。只要所有引用的列都在数据表内存在则下例完全有效:SELECTcompany,first_name,net_amountFROMSalesORDERBYstart_date,last_name;
DISTINCT返回不重复结果DISTINCT关键词只返回结果集合内不重复的数据行。例如,有时你可能需要找出Sales表内的公司,但是你又不想看见每个条目。于是你可以用DISTINCT对应每一公司名返回一行数据:SELECTDISTINCTcompanyFROMSales;在使用DISTINCT时,它适用于所有的请求列。如果你打算列出表内的所有销售人员和他们所代表的公司而非每一销售记录,那么你可以使用下列语句。注意,这样操作还可能返回同一公司的若干条目等等。SELECTDISTINCTcompany,last_name,first_nameFROMSales;你还可以在对结果缩小范围和进行排序时结合SELECT语句使用DISTINCT。为了确定显示的内容,数据库首先会证实精练的请求是否匹配数据行,然后应用DISTINCT功能。在全部结果集合都得以确定之后即处理ORDERBY子句。如下例所示,只有net_amount大于100的数据行才被返回。由于DISTINCT保留遇见的第1个匹配查询条件的数据行而丢弃其他匹配行,所以ORDERBY语句所引用的net_amount看起来就好象产生了随机的结果。SELECTDISTINCTcompany,last_name,first_nameFROMSalesWHEREnet_amount>100ORDERBYcompany,net_amount;
函数应用逻辑返回单一值的函数称做聚集函数(aggregatefunction)通过应用程序访问下列聚集函数。的结果时,包含结果的“字段名”就是你所使用的实际函数。例如,在分析你的数据库结果时,结果数组的键值可能如下所示:$keyname=“COUNT(*)”;$resultkey=“AVG(net_amount)”;COUNTCOUNT函数计算出结果集合中的数据行数。和其他函数一样它接受一个参数。以下的基本示例能告诉你数据表内的行数:SELECTCOUNT(*)FROMSales;你也可以用它来计算任何结果集合中的行数。SELECTCOUNT(*)FROMSalesWHEREnet_amount>100;如果你想看看某特定列有多少行包含非空值,那你不妨对该列使用COUNT函数。注意,除非数据库设置为字段为空时缺省填充NULL否则将返回表内数据行
的总数。另外,列出的列在超出一个的情况下会引起错误。
SELECTCOUNT(company)FROMSales;COUNT还可以用来计算DISTINCT结果集合中的行数。SELECTCOUNT(DISTINCTcompany,last_name)FROMSales;COUNT语句通常用在程序中确定FOR循环的循环次数。
AVGAVG返回某列所有字段的平均值,该列必须是数字数据类型。该函数用列的名字作为其参数,如果列字段数据类型是非数字类型的则函数返回“0”。SELECTAVG(net_amount)FROMSales;你可以结合子句限制该函数的应用范围。SELECTAVG(net_amount)FROMSalesWHEREcompanyLIKE‘%ABCDCo%’;就象所有聚集函数一样,ORDERBY语句将被忽略。SUMSUM的工作方式和AVG差不多,只不过该函数返回结果集合中所有字段值的和。SELECTSUM(net_amount)FROMSalesWHEREnet_amount>100;AVG、SUM、MIN和MAX函数在没有指定列的情况下都会返回错误,所以你不能使用“*”通配符。MINMIN返回指定列中最小的非空值。如果指定列是数字数据类型则结果将是最小的数字。如果它是一种字符串数据类型则函数将返回按字母表顺序出现的第1个值。SELECTMIN(net_amount)FROMSalesWHERElast_name=“Smith”;SELECTMIN(last_name)FROMSales;
MAXMAX的工作方式和MIN函数一样,只不过该函数返回最大的非空值。该函数也可以用于字符串或者数字列SELECTMAX(net_amount)FROMSales;SELECTMAX(company)FROMSalesWHEREnet_amount>100;MAX函数有时还用在包含自动递增键字段的列上确定下一条目的键ID。除非你正在运行一个非公开的数据库,否则在使用这一信息插入下一条目时务必谨慎,以防其他用户先你
执行数据操作。GROUPBY令函数更有用虽然以上提到的所有这些函数都能提供相当有用的信息,但是,如果有GROUPBY子句帮忙的话更能让你在列的字段子集中应用这些函数。不要对你的Sales表中每一家公司一次又一次地执行MAX函数查询——你完全可以带GROUPBY子句获得同样的结果:SELECTcompany,MAX(net_amount)FROMSalesGROUPBYcompany;这样做可以获得每家公司net_amount的的最大值。在选择多列名的时候也可以采用该语句,你还可以用多列来对函数结果分组。下面的例子演示了以上各种方式。首先,包括GROUPBY子句可以令你指定要显示的Sum(net_amount)其他列。然而,你得知道这个例子将返回在组中遇到的第1个last_name值;将显示全部公司的结果而不仅仅针对匹配姓氏的数据行。这是因为,我们只使用了Company字段来定义我们的组。SELECTcompany,last_name,SUM(net_amount)FROMSalesGROUPBYcompany;在上面的例子中,last_name列实际上并没有提供什么有用的信息,但这样做是为了在下一个例子中要用到的功能
做准备。你可以创建多列定义的组。这样就可以在结果集合中产生针对特定行的函数结果,而结果集合则是由所有指定的GROUPBY列联合起来创建的:SELECTcompany,AVG(net_amount),last_nameFROMSalesGROUPBYcompany,last_name;
上面的例子给每家公司中每一姓氏给出了平均的net_amount。你列出GROUPBY列的顺序控制着结果的排序,但是实际的函数值结果是一样的。
下面的例子表明如何组织结果而不显示分组的列。在有些场合这样做是很有用的,例如,如果要显示个人的销售量但却不显示就能用上下面的例子了:SELECTcompany,COUNT(sale_id)FROMSalesGROUPBYcompany,last_name;
限制使用GROUPBY的查询如你在以上示例中所看到的那样,你可以结合WHERE字句利用以上的概念限制查询的范围。WHERE子句会首先被计算,然后执行函数。在使用组的时候就是这样的。SELECTcompany,AVG(net_amount),FROMSalesWHEREnet_amount>100GROUPBYcompany;上面的例子只对那些满足WHERE限制条件的数据行适用AVG函数。注意,WHERE
子句必须放在GROUPBY子句之前。你还可以用HAVING语句对分组计算之后限制返回的结果集合。SELECTcompany,AVG(net_amount),FROMSalesWHERElast_nameBETWEEN‘a’AND‘m’GROUPBYcompanyHAVINGAVG(net_amount)>500;
上面的语句计算每家公司net_amount的平均值,而且只计算那些姓氏满足限制条件的销售人员的销售量,同时只显示大于500的结果。
使用SQL子选择来合并查询
在一个结果组中搜索子选择的理念很简单:一个选择查询安置在另一个查询内部,创建一个在单一声明搜索中不可用的资源。子选择允许查询的合并,结果组比较的责任落到了数据库中而不是应用软件代码中。使用这个功能的一个途径是对两个表格中的可比数据专栏中的值进行定位。例如,我的一个数据库有两个表格,Album和Lyric。我可以很容易地通过下面的子查询声明来找到每一个Metallica的歌曲中包含“justice”的歌名:SELECTsong_nameFROMAlbumWHEREband_name=‘Metallica’ANDsong_nameIN(SELECTsong_nameFROMLyricWHEREsong_lyricLIKE‘%justice%’);这个例子是很简单的,我从Album表格中选择了所有Metallica的歌曲,接着,我在lyric表格中选择所有包含“justice”的歌曲,最后,我使用IN关键字来从Lyric表格结果组中显示的Album表格中返回歌曲名称。我使用Lyric表格结果组来给Album表格中的结果做限定。WHERE子句中的子选择部分是完全自包含的,因此我不需要使用例如Album.song_name和Lyric.song_name等完整的专栏名称。我没有从最终结果组的Lyric表格中返回任何值,如果我需要歌曲的Lyric,我会使用一个JO
IN声明。使用NOTIN排除结果你可以使用NOTIN关键字来获得明确地不被包含在另一个结果组中的结果。例如,我想要通过下面的代码来返回Metallica在“AndJusticeforAll”专辑中不包含单词“justice”的歌曲:SELECTsong_nameFROMAlbumWHEREalbum_name=‘AndJusticeforAll’ANDband_name=‘Metallica’
ANDsong_nameNOTIN(SELECTsong_nameFROMLyricWHEREsong_lyricLIKE‘%justice%’);在前面的SQL代码中,我选择了Metallica的“AndJusticeforAll,”专辑中的所有歌曲,接着是带有歌词中带有“justice”所有歌曲,最后从在Lyric结果组中没有出现的Album结果组返回了所有歌曲。较之于返回两个查询并使用代码来比较数组,你通过一个单独的声明就可以得到确切的结果。使用EXISTS来相关结果有时你可以通过多种途径来访问相同的数据,而且你需要对你的结果进行匹配(或相关)来得到值的交叉区。例如,我可以通过搜索Album表格来得到Metallica的歌曲列表,可是,我也可以从我的Cover表格中得到由Damage,Inc表演的Metallica的歌曲的列表,我可以在两个表格中直接比较查询结果来对值作相关。SELECTAlbum.song_nameFROMAlbumWHEREAlbum.band_name=‘Metallica’ANDEXISTS(SELECTCover.song_nameFROMCoverWHERECover.band_name=‘Damage,Inc.’ANDCover.song_name=Album.song_name);在SQL代码中,我使用完整的专栏名称,这是因为我直接对两个表格作比较,而不仅仅是将结果组作为一个被动资源来使用。我并不从Cover表格中返回结果。一些数据库支持NOTEXISTS关键字来确保你并没有匹配。使用合计函数来比较除了使用子选择在相关的表格中检查数据,你还可以在一个WHERE子选择中使用合计函数来确定主结果组。例如,我想要核实每一个Metallica歌曲在Album表格中的条目。而且,我还想返回缺少歌曲的专辑的名称。很方便地,AlbumInfo表格包含的一个专栏(album_tracks)给出了应该有多少首歌曲方面的信息。SELECTAlbumInfo.album_nameFROMAlbumInfoWHEREAlbumInfo.band_name=‘Metallica’ANDalbum_tracks<>(SELECTCOUNT(*)FROMAlbumWHEREAlbum.album_name=AlbumInfo.album_name);现在我已经成功地返回了所有Metallica的专辑中,应有的曲目数量与Album表格中实际的歌曲条目数量不符的专辑名称。返回子选择结果如果我还是关心每一张专辑的曲目数量并需要得到一个比较报告怎么办?你可以将一个子选择的结果作为最终结果组的一部分来返回。这个功能经常被合计函数所使用。通常地,对其他表格的访问可以作为你的查询的一部分。下一个例子将返回每一张Metallica的专辑,
应该包括的曲目数量和在Album表格中包括的条目数
量:SELECTAlbumInfo.album_name,album_tracks,(SELECTCOUNT(*)FROMAlbumWHEREAlbum.album_name=AlbumInfo.album_name)FROMAlbumInfoWHEREAlbumInfo.band_name=‘Metallica’;另一个强有力的例子涉及了在AlbumInfo表格中将album_tracks值改变为在Album表格中实际的条目数量:UPDATEAlbumInfoSETalbum_tracks=SELECTCOUNT(*)FROMAlbumWHEREAlbumInfo.album_name=Album.album_name)WHEREAlbumInfo.band_name=‘Metallica’;在上两个例子中的子选择声明被看作一个自包含单位来执行。子选择比较关键字(ALL,SOME,ANY)除了使用标准查询功能,还有三个关键字可以使你将一个表达式值和一个单栏子选择声明结果组作比较,这些关键字返回TRUE或FALSE的Boolean值。ALL关键字要求子选择中所有值都遵守比较运算符。SOME和ANY关键字则要求至少一对。这里是ALL关键字的一个简单实例。SELECT*FROMAlbumSalesWHEREalbum_gross>ALL(SELECTalbum_costsFROMAlbumProduction);上面的例子将从AlbumSales表格返回在AlbumProduction表格里面付出总额大于成本而生产最昂贵的专辑的所有记录。如果用ANY替代ALL,声明将返回所有付出总额大于最低专辑成本的专辑记录。声明=ANY与IN关键字意义是相同的。声明<>ALL与NOTIN关键字是对等的。关键字ANY和SOME也是等同的。数据库生产商中对这些关键字的支持情况是不同的,因此在出现问题时要相信查阅生产商方面的资料。再次欢迎您来到SQL(结构化查询语言)基础系列教程。本文将介绍数据库定义语言(DDL)用于创建数据库和表格以及修改表格结果的指令。
当你使用这些指令时一定要小心——它很容易删去你的数据库中的主要结构令您丢失数据。所以,在您开始修改数据库之前,您需要知道数据库是什么。
--------------------------------------------------------------------------------
数据库之间的差异本文中的样品查询系统遵循SQL92ISO标准。并不是所有的数据库都遵循该标准,有些数据库做了改进,这会产生不可预料的结果。如果你不能确定你的数据库是否支持该标准,请参考相应的文档。
-------------------------------------------------------------------------------创建数据库为了创建表格,你首先需要需要创建一个可以容纳表格的数据库。SQL用于创建数据库的基本语句是:CREATEDATABASEdbname;你的数据库用户必须有建立数据库的适当权限。如果与你有关的用户不能发出用于创建新数据库的命令,要求数据库管理员为你建立数据库,你也作为管理员登录然后建立数据库并设置权限。举个例子,用CREATE指令为一个应用程序建立一个数据库用于显示一个目录:CREATEDATABASECatalog;这给你一个用于在查
询时与其它表格区分的表格名字。下一步是创建用于输入它的表格。创建表格如你所知,表格是有若干个栏目所组成。当创建表格时,你可以定义栏目并分配字段属性。表格建立后,可以用ALTER表格指令来修改它,我们稍后将提到这一点。你可以用下面这条指令来创建数据库,命令行的参数为表格名字、栏目名字,还有每一栏的数据类型。CREATETABLEtable_name(column1data_type,column2data_type,column3data_type);不同的数据库提供商的标准差别很大。你的帮助文档中应该有一段详细说明如何使用每一种数据、接受何种参数。为了通用,我在表A中列出了一些常用的数据类型。表A数据类型用法详细说明CharChar(8)它包含了一个固定长度的字符串,其值常常是字符串长度。
VarcharVarchar(128)它包含了一个长度不大于指定值的长度可变的字符串。IntInt(32)这是一个不大于指定值得整数,也做Number或Integer。DecimalDecimal(12,2)这是一个总位数和小数点后位数不大于指定值得小数,也被称为Numeric或Number。BinaryBinary用于存储二进制对象,在数据库中它一般不可分解和显示,也称为Raw或Blob。BooleanBoolean用来只是真或假,也成为Bit或Byte。
通用数据类型
在本例中,我们建立了一个存放库存商品信息的表格。所用到的栏目和数据类型如表B所示:表B栏目名称:prod_idprod_colorprod_descrprod_size数据类型:Int(16)Varchar(20)Varchar(255)Decimal(8,2)
在本例中,我使用了三种基本数据类型;然而,在实际使用时,根据数据库支持的内容,我可能还用用上tinyint、文本和mediumtext数据类型。发出如下指令来建立表格:CREATETABLEProducts(prod_idINT(16),prod_colorVARCHAR(20),prod_descrVARCHAR(255),prod_sizeDECIMAL(8,2));如果这些指令顺利完成,你就可以在表格中正常地插入信息。你可以参到文章SQL基础一:数据查询"得到详细说明。
除了数据类型,你还可以在创建表格时定义自动增量字段(auto-incrementedfield)、关键字、索引和特殊数值限制。在表格定义时,这些参数与数据类型一同传递。如果在创建表格Product时定义具有特殊数值限制的自动增量prod_id,命令如下:CREATETABLEProducts(prod_idINT(16)AUTO_INCREMENT,prod_colorVARCHAR(20),prod_descrVARCHAR(255),prod_sizeDECIMAL(8,2),UNIQUE(`prod_id`));如果把prod_id做为索引字段定义,可以用CREATEINDEX:CREATEINDEXProdIndexONProduct(prod_id);这里有必要重申:数据库提供商在关键字的处理上有所不同。所以,具体情况请参考你的数据库提供商的文档。
-------------------------------------------------------------------------------关于索引的更多内容:索
引是一个比较深的课题。除了介绍有关关键字和索引的理论,Builder的供稿人EricRoland写了几篇很好的文章,你可以通过它们来学到更多的相关知识。修改表格当你开始对表格进行操作时,你也许觉得有必要修改表格的结构、字段类型等等。在前面,我强烈建议你避免在生产环境(productionenvironment)这么做。因为有些操作,如添加、删除和修改字段可能会删除或破坏相关字段中的数据。好,现在让我们看看如何修改表格。首先,在表格Product中加入一栏。你可以指定该栏插入的相对其它栏的位置,也可以让它插到表格末端(默认):ALTERTABLEProductADDprod_nameVARCHAR(20)AFTERprod_id;
用类似的语句删除一个栏目:ALTERTABLEProductDROPprod_size;最后,更改一个栏目的数据类型:ALTERTABLEProductCHANGEprod_colorprod_color_idINT(20);现在,你的表格如表C所示:表C栏目名称:prod_idprod_nameprod_color_idprod_descr数据类型:Int(16)Varchar(20)Int(20)Varchar(255))
注意,有些数据库不支持关键字DROP。另外,如果你改变现有的某一栏的数据类型,大多数数据库会试图转化该栏目现有数据的数据类型。然而,如果是转为一个不支持的数据类型,数据就有可能丢失。举例来说,如果把一个类型为Varchar的包含人名字的字段改为Int类型,转换的结果可能是整型的默认值。删除表格和数据库在删除表格和数据库之前,你需要确保丢失这些数据不会造成恶果。如果你删除数据库,库中的所有表格和内容都会被清除。如果你删除一个表格,表格中的所有内容都会丢失,但是库中的其它表格没有影响。在删除表格或整个栏目之前,你必须清楚数据库的结构。如果你进入一个已经存在的数据库并错误删除了某个元素,可能会影响到促发条件(?trigger)、存储过程和视图。有些RESTRICT数据库支持用关键字RESTRICT和CASCADE去预防由于删除表格带来的损失。一般按默认设置,预防丢失表格,而CASCADE用于删除与该表格有关的实体。现在上面建立的表格Product是可以被删除的,我们开始删除它:DROPTABLEProduct;现在删除数据库:DROPDATABASECatalog;
大多数数据库软件提供商支持DROPDATABASE命令,尽管它是在SQL99标准中被定义而不是SQL92。部分数据库提供了FLUSH命令,该命令可以让你删除表格中的内容但又可以保持表格的结果,:FLUSHTABLEProduct;如你所见,删除数据库中的主要结构并丢失所存的所有数据的容易程度令人难以想象,所以,一定要小心使用这些命令,而当你不清楚数据库中的内容时,就不要使用这些命令。数据库管理在前一篇文章中,你学
会了如何在一个或多个表格中查找数据。现在,你学会了如何把你操作数据库结构。你学会了创建、修改并销毁表格和数据。这些都是设计数据库驱动的应用程序的必须用的操作。
串行数据类型
SQL的数据类型决定了一个字段的内容在数据库中会被如何处理、存储和显示。SQL92定义了标准的数据类型,目的是给数据库制造商建立自己的数据类型提供蓝图。在前面的文章中,我们介绍了一些常用的数据类型,这些数据类型分为四大类:串行数值日期时间区间型本文将向你概述这些数据类型在数据库中是如何使用的,然后着重解释串行数据类型。这些信息可以作为有用的参考,或者作为关于某个数据库制造商具体产品中数据类型的背景知识。使用数据类型当你在数据库中创建了一个表格,你就定义了每列的名字以及要输入到这些列中的内容的数据类型。从先前的文章中借用一个例子:CREATETABLEProducts(prod_idINT(16)AUTO_INCREMENT,prod_colorVARCHAR(20),prod_descrVARCHAR(255),prod_sizeDECIMAL(8,2),UNIQUE(`prod_id`));在以上的查询中,定义行prod_colorVARCHAR(20)发出指令要创建一个列,名字是prod_color,数据类型是VARCHAR,长度为20。VARCHAR数据类型你的数据库使用和每个类型相关的描述符来区别数据类型。例如,的描述符所含的信息将它区别为串行数据型,它包含所有的串字符,其长度是可变的。数据库里列的定义还包含了其他信息,例如对应于数据类型的特定长度。
如前所述,每个数据库制造商都希望在SQL92定义的标准上建立自己的数据类型。这样每个数据库在定义数据类型时都能够设定自己所需要的最大容量限制和其他属性。许多数据库使用的数据类型名字和这里列出来的一样,尽管每种的实现方法都有微小的差别。要确定特定数据类型使用方法的细节最好的方法还是查阅数据库制造商的文档。已经说过了,希望对标准字符串数据类型有更多的了解就往下看。串有两种主要的串行数据类型:字符和位。串行使用数据库里由SQL_TEXT所定义的字符。SQL_92标准同时还提供了NATIONALCHARACTER(国家字符集)和NATIONALCHARACTERVARYING(国家字符集变体),这两者都能使用可定义字符集。后者的处理方法和CHARACTER以及CHARACTERVARYING类型一样。CHARACTER|CHAR使用方法:CHARACTER(clength)|CHAR(clength)CHARACTER和CHAR这两个关键字是相同的。CHARACTER类型一个突出的特点是它们能够包含这个字符。CHARACTER类型包含了固定长度的串字符(来自SQL_TEXT的语言集),clength。字符在值的长度小于clength时起填充作用。这表示CHARACTER字段的长度是固定的。
你可以把CHARACTER的数据类型字段和相同类型的其他允许不同长度的字段比较,或者和CHARACTERVARYING数据类型比较。有些数据库允许和数值数据类型比较。CHARACTERVARYING|CHARVARYING|VARCHAR使用方法:CHARACTERVARYING(maxlength)|CHARVARYING(maxlength)|VARCHAR(maxlength)CHARACTERVARYING,CHARVARYING,和VARCHAR这几个关键字是相同的。这些类型能容纳最大长度的字符串,maxlength。数据库把字段的长度作为值的实际长度。你可以把这些数据类型的字段和相同类型的其他允许不同最大长度的字段比较。BIT使用方法:BIT(blength)这种类型包含了带有长度的位字符(1和0),blength。例如,如果我们使用BIT(2),样本值将为“01”。有的数据库会在串的开头插入空位,其的则会填充它们以符合固定长度的要求。位字符是串,不是整数。你可以把BIT数据类型的字段与相同类型的允许不同长度的其它字段比较,或者和BITVARYING数据类型比较。
有些数据库允许BITS和CHARACTER或者INTEGER类型比较。BITVARYING使用方法:BITVARYING(maxlength)这种类型包含了最大长度的位字符,maxlength。所记录的长度被设为值的实际长度。数据库允许和其的BITVARYING数据字段比较,或者和BIT的数据字段比较。对我们的SQL系列有了一些了解了吗?请把你的评论、问题或者回应发到下面的讨论栏,或者如果你有关于SQL基础系列的论题,可以发到我们编辑的信箱。串理论数据库生产商通过建立这些基础的数据类型来创建你实际要实现的数据类型。对于字符串,这就可能包括相同名字的(不同)类型,例如CHAR或BIT,或者扩展到包括TEXT,SMALLTEXT,以及包含字符串的其他数据类型。数据从一个数据库迁移到另一个数据库时,这种设计上的弹性产生了一个必须克服的障碍。在一个数据库里,你可能会有一个叫做CHAR的类型,这个类型所允许的最大容量大于你要迁移到的数据库的最大容量。而且,(SQL92)标准中没有明确定义的类型可能会变化较大,这样的话只用遵从惯例来简化迁移。在ZDNetChina最近的文章《BLOB移植的替换方案》中讨论了存在数据类型移植问题时保护数据的一个可能的解决方案。SQL标准没要包括存储二进制数据的指标,这造成了不同数据库制造商产品间的不兼容。软件开发者必须找到提到方案列清除这些障碍。在这个系列的下一篇文章中,我们会看看SQL92的数字数据类型,每个有什么特点,以及对要实现这些类型的数据库的要求。
从子表里删除数据
在这篇文章里我要描述一下如何从表格里删除列,要删除的这些列同时还要依赖于其他表格的
标准。要解决这个问题就需要一个很聪明而且完全遵守SQL92子查询声明的应用程序。我必须提醒读者的是,尽管查询可能会遵守SQL的标准,但是众多的数据库生产商会以不同的句法支持实现SQL。以下这个解决方案应该适合于大多数数据库;但是,如果你的结果有出入,就还是应该查看一下文档。同时,由于这个查询要处理DELETE声明,所以你应该在将其应用于真实的生产环境以前在实验数据上进行测试。需要更多的背景信息?查看这些文章就能快速上路:《SQL基础I查数据查询》涉及到了DELETE查询的使用。《使用SQL子选项来合并查询》说明子选项查询能够减少对数据库请求的数量,并提供了例子。《SQL基础:查询多个表》提供了更多关于子选项的信息,还讲到了使用单个查询就
能访问多个表格的多种其他方法。宠物店的例子要解释如何进行这种类型的列删除,我会使用如下这个数据库的表格,该数据库叫做PetStore,并包含有清单(inventory)信息。在叫做“品种(breed)”的表A里,我存储有每种动物的信息和宠物店库存的信息。在叫做“清单”的表B里,包含有商店里特定动物的信息。在这个例子里,我们先假设商店把整窝Shitzu小狗都卖完了。我可以使用breed表格里的breed_id字段来删除Shitzu清单里的所有项目,就像这样:DELETEFROMinventoryWHEREbreed_idIN(SELECTbreed_idFROMbreedWHEREbreed_name=‘Shitzu’);首先,我要指定需要删除记录的表格,在这里是清单表格。然后再将识别字段breed_id同子选项子句的结果反复比对。我知道要找的是Shitzus,所以就能直接删掉他们,而不用再在单独的请求里查询breed_id。我必须要警告你的是,以这种方式使用DELETE声明是危险的,只有在你对数据库的结构很熟悉的情况下才能使用这些声明。DELETE查询会从受影响的表格里删除掉全部列,你应该知道这对你所管理着的数据意味着什么。有个好办法是使用SELETE*这个短语替代DELETE关键字来对DELETE声明的子查询结果进行测试,这样就能保证结果里含有你要删除的所有东西,就像这样:SELECT*FROMinventoryWHEREbreed_idIN(SELECTbreed_idFROMbreedWHEREbreed_name=‘Shitzu’);DELETE和JOIN联用有人问到了解决这个问题另一个可能的办法:把JOIN子句和DELETE声明联合使用。由于以前没有使用过这种方法,我就研究了一下,发现SQLServer的文档声明支持这个方法,尽管它不符合SQL92。在经过测试和询问各种数据库平台的老手之后,我发现把DELETE和JOIN声明联合使用在我测试过的任何平台上都行不通。从多个表格里一次删除以上的解决方案
还没有解释如何使用父表从多个子表里删除信息。但是SQL92规范里没有提供完成这项任务的标准解决方案。DELETE的声明不能把多个表格作为一个参数接受。作为一个具有破坏性的查询,这能保证在命令要被执行的地方不会出现歧义。此外,这个限制防止了在单个声明内将AND和多个子查询联用。如果测试SELECT声明的结果用以检查DELETE查询将要影响到的是哪些数据,你会发现SELECT会返回多个表格的清单,DELETE不会影响到的多个子查询不在其中。有很多可能的方法能够满足你的需求,例如在表格里创建一个字段,用以指明该项目是
否为活动的。或者,你可以使用一些数据库里的预存程序在每个所需的DELETE查询里迭代。
数值数据类型
SQL92标准定义了若干种基本数据类型,它们是SQL数据库中各种数据类型的基础。《字在符串数据类型》一文中,我们已经详细讨论了SQL92标准所定义的字符串数据类型。现在,我们来进一步讨论数值数据类型。你最好开始尝试使用不同数据库实现方法并在它们传递数据,这样可以加深你对数值数据类型的理解。本文将给你一个数值数据类型的概要,你可以结合你的数据库的文档资料来学习。在字符串、数值、datetime和interval这四种数据类型中,数值型的种类最多,约束也最多。在不同数据库实现方法之间交换数据时,数值型的精度也最容易降低。Oracle和SQL服务器之间的实现分歧(同样的数据类型长度不同)导致它们之间的数据传递过程会截短数字、改变它们的数值。因此,在移植程序前,你有必须很明确的了解两个平台间的数据定义差异,以及危及数据精度的风险。谨记上述警告后,让我们看看SQL92标准的数值类型基本数值类型与数值有关的类型统称为数值类型。所有的数值都有精度,精度指的是有效数字位数。有的数值还有标度值(scalevalue),它用来指示小数点右边的最小有效数字位数。例如,数字1234.56的精度为6,标度值为2,可以定义为NUMERIC(6,2)。每一个数据库实现方法都有关于如何近似数值或者截短数值的规则。除了提供获取数值长度和其它数值处理所需的属性外,SQL92提供了内建函数,如加、减、乘、除等。所有的数值类型之间都可以互相比较、互相赋值。尽管实现方法不同,但是它们有一个的共同点,即它们的结果一般都保留最大精度。NUMERIC用法:NUMERIC(精度,标度值)是一种精确数值类型,即它是数字的值的文字表示。(可以对该数字进行取舍或者截取以符合指定精度,标度值由预定义的规则确定。)为了符合标度值指定的小数数字位数,
舍去多余的小数部分,舍入过程采用十进制。数字的总长度等于精度,如果标度值大于0(有小数部分),则长度加1。小数部分的位数要符合标度值。DECIMAL|DEC用法:DECIMAL(精度,标度值)|DEC(精度,标度值)是一种精确数值类型。用十进制。数字的总长度等于精度,如果标度值大于0(有小数部分),则长度加1。
小数部分的位数不得小于标度值,小数位数的上限由数据库提供商设定。INTEGER|INT用法:INTEGER(精度)是一种精确数值类型。使用二进制或者十进制,这基于表示该数值的二进制位(bit)的个数(这是implementation-specific,与SMALLINT对应)。标度值恒为0。数据库供应商对其定义了最大精度和最小精度。供应商可能会提供的默认精度。SMALLINT用法:SMALLINT(精度)是一种精确数值类型。位数取舍方法与INTEGER(二进制或者十进制)相同。标度值恒为0。最大精度等于或者小于INTEGER的最大精度。FLOAT用法:FLOAT(精度)是一种近似数值类型,即对一个指定的数值用指数形式表示出来,如1.23e-45(等于),该数值类型的取舍和截短方法大多由数据库提供商定义。当取舍时,使用二进制精度。精度表示使用的最小位数,最大精度由数据库提供商设定。REAL用法:REAL是一种近似数值类型。使用二进制精度,最大精度由数据库提供商设定。其默认精度必须小于DOUBLEPRECISION的默认精度。DOUBLEPRECISION用法:DOUBLEPRECISION是一种近似数值类型。使用二进制精度,最大精度由数据库提供商设定。其默认精度必须大于PRECISION的默认精度。相关理论数据库提供商在基本数据类型的基础上创建了你实际需要的数据类型。对数值类型来说,它可以包括同名的数据类型,如INT、REAL,也包括为了满足特定场合或者用途而创建的新数据类型。在我们的下一篇文章,我们将讨论datetime和interval数据类型。
datetime和interval数据类型
datetime和interval是两种与时间有关的数据类型。它们的作用体现在以下几个方面:创建或者更改记录库中的某条记录、当某个时间发生时运行记录、或者计算某个datetime变量建立后所经历过的时间。本文将介绍SQL92标准对上述两种数据类型的描述。
SQL数据类型如果你想进一步了解SQL数据类型,请阅读SQL基础:字符串型数据类型,这篇文章的范围覆盖了SQL中的通用数据类型并说明了各种字符串数据类型的用法,SQL基础:数字数据类型详细介绍了数字数据类型。
Datetime用于表示时间或者日期的数据类型都属于datetime类型。每一种datetime数据类型都有
他自己的用于获取值的长度和它所保存信息的手段,如天、月、分钟、秒、秒的小数等等。实际上,datetime的实现形式随着定义它的标准不同而拥有不同的长度和格式;然而,各个公司定义的类型都内在地符合下述规则。举例来说,时标(timestamp)的某个实现可能没有分隔符,随着细节的规范不同,长度和格式也发生变化,在某些场合以空格做为间隔符。Datetime数据类型包括:DATA、TIME和TIMESTAMP。让我们仔细研究这些分类,首先我们看看DATA。DATA用法:DATADATA类型允许没有参数,如精度。DATA的字段包括年、月和日。DATA的长度为十个字符:YYYY-MM-DD。表示年、M表示月、D表示日。(Y)它只允许与其它DATA类型字段相比较。允许的数字必须符合公历的规范。TIME用法:TIME(精度)该类型包含了小时、分和秒,格式为hh:mm:ss(h表示小时、m表示分、s表示秒)精度可选择,(……)时间以世界标准时间(UniversalCoordinatedTime,UTC)为准,即00:00:00表示.格林威治的午夜,服务器的时区隐含的。如果不需要秒的小数部分,那么TIME的长度为八个字符。否则就是八位长度在加上精度:hh:mm:ss.p。它只能与其它TIME类型数据进行比较。如果没有指定精度,精度默认为0。
TIMEWITHTIMEZONE
用法:TIME(精度)WITHTIMEZONE这个值要符合TIME数据类型TIMEZONE部分表示相对UTC的时差:00:00:00+hh:mm。它的范围为-12:59到13:00。精度表示秒的小数部分。带有TIMEZONE的TIME长度为14个字符加上精度,在加上一个分隔符。只可以与带有TIMEZONE的TIME类型数据进行比较TIMESTAMP用法:TIMESTAMP(精度)该类型包含有年、月、日、时、分、秒,格式为:YYYY-MM-DDhh:mm:ss.。可以包括秒的小数部分,这由定义的精度决定。它的日期部分符合公历标准,时间部分为UTC格式。默认为当地时区。时标的长度为19个字符,加上精度,在加上精度分隔符。许多系统偏离上述定义的长度,UNIX风格时标格式为:YYYY-MM-DDhh:mm:ss.p。如,如果没有定义精度,默认值为6,但是许多数据库公司默认为0,所以请参考你的开发文档。时标只可以与其它TIMESTAMP类型的值相比较。TIMESTAMPWITHTIMEZONE用法:TIMESTAMP(精度)WITHTIMEZONE时标部分符合上述TIMESTAWP的规则。精度代表秒的小数部分。时区部分的要求和TIMEWITHTIMEZONE一样,即时区符合UTC规范,范围在-12:59到+13:00之间。总长度为25个字符,加上精度,加上一个精度分隔符:YYYY-MM-DDhh:mm:ss.p。它只能与其它TIMESTAMPWITHTIMEZONE类型的数据进行比较。IntervalInterval用于表示时间尺度。例如,你可以用操作
符(将在下面进行解释)去计算两个日期间天数并加以保存。各个公司在处理interval上有很大的不同——有些公司提供不同的度量单位,如年或者分钟,而有些公司在根本就不支持interval。SQL92标准的interval类型只提供一种子类型:INTERVAL。
INTERVAL
用法:INTERVAL(限定语)有两种类型的interval:一种为“年份-月份”,即保存年份和月份(YYYY-MM);一种为“天-时间”(DDHH:MM:SS),用来保存天数、小时、分钟和秒。限定语——在某些数据库中interval前导精度(leadprecision)——根据其值来指示interval采用“年份-月份”还是“天-时间”方式。interval可正可负。当与其它interval类型变量相比较时,结果保持最大精度,如有必要则补零。INTERVAL全部由整数组成,除了含有小数的秒之外。“年份-月份”类型的interval变量只能与其它的“年份-月份”的interval变量进行比较。“天-时间”类型也与此类似。操作符操作结果类型当处理日期时间时,时区保持不变——尽管有些数据库为了比较而将其中的一个时区转换为另一个。存在一些操作关键字,如OVERLAPS和EXTRACT,它们用于操作和比较datetime类型数据。然而,不同的数据库在这些操作关键字用法和支持方式上有着很大的不同。OVERLAPS用于计算时间交叠的跨度,其操作对象可以是两个datetime也可以是一个datetime和一个interval。EXTRACT用于提取datetime或者interval类型数据的某个部分,如在DATA类型数据中提取月份。数据类型由于已建立的标准是为了各公司在现有基础上实现实际上的数据类型的,因此在具体限制、参数和数据类型等方面,你还是需要经常参考你的数据库文档。
探索用户自定义数据类型
用户自定义数据类型是一个确保数据库中域与数据紧密结合的好办法。数据的类型可能在整个数据库中都是一致的,每个数据的适用范围和它的数据类型是相关联的。sp_bindruleCREATERULE命令也过程是一个向后兼容过程,该过程为数据类型确定了一个适用范围。是一个向后兼容命令,为域值的遵守产生了一个规则。该规则可以被限制为用户自定义的数CREATEDEFAULT也是一个向后兼容命令,而且也可以被限制为用户自定义的数据类型。据类型。这些向后兼容命令都是由SYBASE演化而来的。SQLServer的未来版本是否支持它们现在还不能确定。微软推荐用户使用CHECKCONSTRAINT命令。然而,CHECKCONSTRAINTS不支持模块化编码。你必须为所有需要CHECKCONSTRAINT的表格的每一列都创建一个CHECKCONSTRAINT。另一方面,创建规则和缺省值,并把它们限制在一个用户自定义数据
类型这个过程只需进行一次。用户自定义数据类型有很多种,比如性别和标签的布尔值。性别的域值可以是雄性,雌性,以及未知。布尔值的域值可以是数值,也可以是真假值。
下面的例子说明了向后兼容方法的模块性和关联域的未来方法。
用SQL实现树的查询
树形结构是一类重要的非线性结构,在关系型数据库中如何对具有树形结构的表进行查询,从而得到所需的数据是一个常见的问题。本文笔者以SQLServer2000为例,就一些常用的查询给出了相应的算法与代码,颇值得读者借鉴。树型结构关系型数据库将数据按表结构形式进行组织。它对表格的处理方便灵活,且易学易用,因而得到广泛的应用。关系型数据库所处理的表格是线性结构的,表的每一行对应着一个数据元素,称做一条记录。记录与记录之间呈线性排列,彼此间没有联系,然而,在解决实际问题时,常常会遇到非线性结构的数据。如下表所示,每一条纪录中的上级代码,就和其他纪录有着联系,这样就形成了一棵具有层次结构的树,它可以用下面的图来形象地表示:
树形结构是一种结点之间有分支,并具有层次关系的结构,它非常类似于自然界中的树。树结构在客观世界中大量存在,例如家谱、行政组织机构都可用树形象地表示。树在计算机领域中也有着广泛的应用,例如在编译程序中,用树来表示源程序的语法结构;在数据库系统中,用树来组织信息;在分析算法的行为时,用树来描述其执行过程。
在关系型数据库中如何对具有树形结构的表进行查询,从而得到所需的数据是一种常见的需求。下面以SQLServer2000为例,就三种常用的查询给出相应的算法与代码:1.节点A的位于第n层的父结点信息,如:员工黄菁菁的上两级上司的名称。2.某棵子树的统计信息,如:员工余顺景及其所有下属员工的工资总额。3.某棵子树的结点信息,如:员工郑可可及其所有下属员工的名称。
某节点的父节点信息要实现这样的查询,常使用递归的方法。我们可以用SQLServer2000增加的用户定义函数(UDF,UserDefinedFunction)这个新特性来实现递归函数调用。下面是函数的定义:CREATEFUNCTIONdbo.GetManager(@employee_idASchar(5),@levelASint=1--缺省值为1)RETURNSchar(5)其中,employee_id表示要查询的员工号码,level表示高于该员工的级别数,返回的结果是上司的员工号码。该函数的递归定义为:如果level=0,则返回当前的员工号码;如果level>0,则返回直接上司的level-1级的上司号码。根据这样的递归定义,我们可以写出完整的递归函数:CREATEFUNCTIONdbo.Ge
tManager(@employee_idASchar(5),@levelASint=1)
RETURNSchar(5)[email protected][email protected]_id——如果level为0,表示已经找到其上司号码RETURNdbo.GetManager((SELECT[上级号码]FROM[员工信息]WHERE[员工号码][email protected]_id),@level-1)--如果level大于0,则返回直接上司的level-1级的上司号码END执行下面的语句可以得到需要的结果:SELECT*FROM[员工信息]WHERE[员工号码]=dbo.GetManager(‘E9907’,2)当然,如果要让该递归函数更为健壮,我们还需要在函数中加入容错检查,这里不再赘述。某棵子树的统计信息这个查询同样使用递归的方法来实现。先看一下函数定义:CREATEFUNCTIONdbo.GetTotalSalary(@manager_idASchar(5))RETURNSint其中,@manager_id是要统计的某位上司的员工号码,返回其所有下属的工资总额。该函数的递归定义为:如果没有下属,则返回当前的工资额;如果有下属,则返回所有下属的工资总额。根据这样的递归定义,我们可以写出完整的递归函数:
CREATEFUNCTIONdbo.GetTotalSalary(@manager_idASchar(5))RETURNSintASBEGINRETURN(SELECT[工资]FROM[员工信息]WHERE[员工号码][email protected]_id)+CASEWHENEXISTS(SELECT*FROM[员工信息]WHERE[上级号码][email protected]_id)THEN(SELECTSUM(dbo.GetTotalSalary([员工号码]))FROM[员工信息]WHERE[上级号码][email protected]_id)ELSE0ENDEND上面的自定义用户函数中使用了CASE搜索函数,它按指定顺序为每个WHEN子句的Boolean_expression求值,返回第一个取值为TRUE的Boolean_expression的result_expression,如果没有取值为TRUE的Boolean_expression,则当有ELSE子句时SQLServer将返回else_result_expression;若没有ELSE子句,则返回NULL值。在自定义用户函数中,如果员工信息表中发现该员工有下属(EXISTS子查询),则为每个下属调用GetTotalSalary函数返回下属的工资总额,并用SUM函数求和;反之,则直接返回其工资额。执行下面的语句可以得到所需的结果:SELECTdbo.GetTotalSalary(‘E9902’)AS‘工资总额’
实际工作还可能有这样的查询要求,即某名员工一共有多少个下属级别(包括其自身),如张建平一共有四个下属级别。用树的术语来描述,即求出某棵子树的深度。可以通过这样的递归函数来实现:CREATEFUNCTIONdbo.GetUnderlyingLevel(@manager_idASchar(5))RETURNSintASBEGINRETURNCASEWHENEXISTS(SELECT*FROM[员工信息]WHERE[上级号码][email protected]_id)THEN1+(SELECTMAX(dbo.GetUnderlyingLevel([员工号码]))FROM[员工信息]WHERE[上级号码][email protected]_id)ELSE1ENDEND执行下面的语句可以得到所需的结果:SELECTdbo.GetUnderlyingLevel(‘E9901‘)AS‘下属级别’某棵子树所有子节点信息前面的两
种查询返回的都是标量值,这里的查询需返回某棵子树的所有子节点的信息,这是一个结果集,需要用table数据类型来存储。函数定义如下:CREATEFUNCTIONdbo.GetSubtreeInfo(@manager_idASint)
[email protected]([员工号码][char](5)NOTNULL,[][char](10)NOTNULL,[年龄][int]NOTNULL,[工资][money]NOTNULL,[上级号码][char](5)NULL,[级别][int]NOTNULL)其中,@manager_id代表要查询的上司的员工号码,返回的是其所有下属的信息,这些信息存放在table型变量@treeinfo中。由于该查询返回的是一个结果集,因此已经不能使用递归的方法来实现,我们使用循环的方法来实现,循环的过程为:将参数@manager_id所代表的上司的信息插入到表中,赋予级别0;级别增加为1,将所有上级号码为以上@manager_id的员工信息插入到表中;级别增加为2,将所有上级号码与第2步插入的记录中的员工号码一致的员工信息插入到表中;依次增加级别,直到找不到上级号码与前一步插入的纪录中的员工号码一致的员工信息为止。为了实现这个循环,我们要用系统函数@@ROWCOUNT来判断前一步中是否有新的记录被插入到表中。如果有,则循环继续;如果无,则循环结束。另外,我们在表中增加了一个名为“级别”的字段,既可以显示出所在的级别关系,还可以用来代表每一次新插入的记录,可谓一举两得。完整的函数定义如下:CREATEFUNCTIONdbo.GetSubtreeInfo(@manager_idASchar(5))[email protected]([员工号码][char](5)NOTNULL,[][char](10)NOTNULL,[年龄][int]NOTNULL,
[工资][money]NOTNULL,[上级号码][char](5)NULL,[级别][int]NOTNULL)[email protected]@[email protected][员工号码],[],[年龄],[工资],[上级号码],@levelFROM[员工信息]WHERE[员工号码][email protected][email protected]@ROWCOUNT>[email protected][email protected][email protected][员工号码],E.[],E.[年龄],E.[工资],E.[上级号码],@levelFROM[员工信息][email protected][上级号码]=T.[员工号码]ANDT.[级别][email protected]
SQL技巧