跟我学SQL:(一)数据查询 且不说你是否正在从事编程方面的工作或者不打算学习SQL,可事实上几乎每一位开发者最终都会遭遇它。你多半还用不着负责创建和维持某个,但你怎么着也该知道以下的一些有关的SQL知识。 我为那些感兴趣的开发者或者能从操作中得益的读者撰写了这篇关于基本SQL语法的概述性文章。本文主要讨论基本的数据操作查询,后续的文章还会继续讨论如何修改自身以及更高级的查询概念。 SQL是怎么回事? SQL(结构化查询语言)就是负责与ANSI维护的交互的标准。最新的版本是SQL-99,还有一个新标准SQL-200n尚处于制定过程中。大多数的都至少遵守ANSI-92标准的部分子集。不过,目前对最新标准的有效性还存在一些争论。专有制造商根据这些标准开发自己的产品,同时制定出自己特有的存储操作新概念。几乎各种不同的都包含了自己特有的语法集合,只是通常很类似ANSI标准。在大多数情况下,尽管有一些实例基于特定的扩展语法会因的不同而产生不同的结果,但总的说来,这些新加的语法不过是对原有标准的扩充。如果操作并没有得到你希望的结果,那么你不妨事先读一读制造商提供的产品说明。 假如到目前为止你头回遭遇SQL语言,那么你怎么也得先理解一些基本的SQL概念。我尽量把这些基本知识阐述得简明扼要,如果你对那些术语还能忍受,你尽可跳到下一节,此外你还可以把自己的问题提交给以下的讨论区。 笼统地说,“SQL”其实就是关系型管理系统(RDMS)通俗的叫法。对某些系统来说,“”也指一组数据表、数据以及相互区分但结构类似的配置信息。在这种情况下,每一SQL的安装都可能由若干组成。在有些系统上,这种则指的是表空间。 数据表是一种包含多行数据的构造,这种构造由命名的列组成。通常数据表构造为包含关系信息,同一或表空间以内可以创建若干数据表。 表内的列保存某一种类型的数据而且应根据其保存数据的内容得以命名。例如,被称为“LastName”的列就应该在每一行包含姓氏条目。正是这一前提的存在才能让关系查询返回一致的结果。 字段(field)指的是某一行某一列对应的数据(或保存数据的地方)。另外,数据集合(data set)则指的是多行多列的数据,而且数据集合通常说明你的或数据表内的全部数据。结果集合(result set)就是从查询返回的数据;它能够描述从单一字段到内全部数据这一范围内的全部信息。 查询就是发送给的SQL指令,这些指令向请求某种施加在数据集合或上的功能。 现在我们就来看看基本的查询,这些查询主要涉及到对内数据的操作。在本文中,所有的例子都采用了标准SQL语言,而且这些基本功能可以转换为应用在几乎各种环境下。 数据查询类型 SQL语言中的数据查询分为4种基本类型: SELECT:这条语句要求返回指定结果的数据集合;你可以用这一语句检索中保存的信息。 INSERT:这条语句用来给数据表增加新一行数据。 DELETE:该语句从你的中删除若干行数据。 UPDATE:该语句修改内的现有数据。 以上的这些语句都有各种各样的限定词和函数供你用来定义有关的数据集合,同时控制查询返回的结果集合。SELECT语句的选项最多。有许多种组合SELECT的查询选项,例如JOIN和UNION等。不过就我们目前来说,本文主要还是关注基本用途。 用SELECT语句检索保存的信息 为了获得中保存的信息就必须采用SELECT语句。其基本功能限制在针对单一数据表操作,当然,其他范围的构造也是有的。为了返回特定列所对应的所有数据行,你可以使用以下语句: SELECT column1, column2 FROM table_name; 另外,使用通配符“*”可以从表中选出所有的列: SELECT * FROM table_name; 你要愿意自己编码分析以上返回的结果当然也没问题,不过你完全可以采用方便的WHERE子句限制返回的结果集合,该子句可以让你为选择数据定义某些条件。以下查询就会返回“column1”数值等于3的所以数据行: SELECT * FROM table_name WHERE column1 = 3; 除了“=”(等于)条件之外你还可以用到下列条件运算符: 表A = 等于 <> 不等于 > 大于 < 小于 >= 大于或等于 <= 小于或等于 SQL 条件语句 另外,你还可以联合WHERE语句使用BETWEEN、LIKE等比较运算符以及AND和OR这类逻辑运算符。注意,OR语句是包含性的的。以下有一个例子组合了以上这些概念: SELECT * FROM table_name WHERE ((Age < 18) AND (LastName BETWEEN ‘Anderson’ AND ‘Miller’)) OR Company LIKE ‘%School%’; 用自然语言来说,这条选择语句的含义是这样的:从数据表中选出年龄小于18岁而且姓氏在“Anderson”和“MIller”之间的或者其公司名称类中有“School”字样的数据行。 用INSERT语句加入新数据 使用INSERT语句可以创建新的数据行。如果你希望在某一行的某个字段中赋值则要用到UPDATE语句。 插入语句的语法如下: INSERT INTO table_name (column1, column2, column3) VALUES (‘data1’, ‘data2’, ‘data3’); 如果你想按照表内现有列的同一顺序插入所有的值,那么你不必指定列名,当然,从可读性考虑最好不要这样做。另外,如果你列出列名则不必要按照它们在中出现的顺序包括它们,只要你列出的值与它们一一对应即可。有些列你并没有为其输入新的信息所以你自然没有必要列出它们来。 一旦中有了数据要修改起来也与此很相似。 UPDATE语句和WHERE子句 UPDATE用来修改现有的值或行里的空字段,因此它必须在匹配现有的数据集合同时提供可接受的值。除非你真地想要修改所有数据行上的值,否则你必须使用WHERE子句。 UPDATE table_name SET column1 = ‘data1’, column2 = ‘data2’ WHERE column3 = ‘data3’; 你可以采用WHERE子句随意匹配任何一列,正在修改的一列都可以。这样会有助于你把某一特定的值修改为另一个值: UPDATE table_name SET FirstName = ‘Shelley’ WHERE FirstName = ‘Shelly’ AND LastName = ‘Doll’; 小心DELETE语句 DELETE语句会从的数据表中删除整行。如果你仅仅想删除单一的字段则应该使用UPDATE语句把它修改为代表应用程序中的NULL的其他空值。一定要小心使用带WHERE子句的DELETE语句,否则你可能会遭遇清空全部数据表的风险。 DELETE FROM table_name WHERE column1 = ‘data1’; 一旦你中删除某一行数据就不可再后悔了,因此一般来说,最好在数据表中包括一名为“IsActive”的列或类似的指示信息,这样你就可以把该列数据设置为零表示数据禁用。只有在你确信不再需要受到影响的信息之后你才可以用DELETE语句。 小结 SQL就是的语言,我们已经了解了数据查询中所采用的最基本命令语句。但还有很多基本概念尚未涉及,例如SUN和COUNT函数等,但以上列出的这些命令应该足够你开始着手操作了。 跟我学SQL:(二) SELECT语句选项 跟我学SQL分为三部分,其中涵盖了有关SQL标准的基本知识。在上一篇文章里我们讨论了一些数据库术语和4种最基本的数据查询类型。此外,我们还解释了WHERE子句和条件语句的用法,同时我们提供了各类查询的具体示例。 在这篇文章里,我们将就其他一些SQL函数和子句进行阐述,供你用于基本的SELECT数据查询中。 SELECT选项精制结果 正如我们从上一篇文章中所读到的那样,SELECT语句具有种类繁多的各类选项,这些选项可以用来控制数据返回的方式。这些选项以子句、关键词和函数的形式存在。 子句是一种修改结果的语句。子句不是必要的语句但它对数据的内容及其显示进行了提炼。WHERE子句就是这样的子句。 关键词触发数据库的内在功能。这些关键词在有时甚至是查询所必需的。例如“INSERT INTO table_name (column1) VALUES (‘data1’);”语句中的INTO和VALUE就是如此。我们将了解DISTINCT关键词,它能触发一些非常有用的可选功能。 下面总结了一些最常用的子句、关键词和函数。然后我会对每一部分举例说明。 ORDER BY – 按照指定列排序返回结果的子句 DISTINCT – 只返回结果集合内唯一行的关键词 COUNT -- 返回匹配查询的数据行总数数值的函数 AVG – 该函数返回指定列的平均值 SUM –该函数把指定的列中的数字加起来 MIN – 该函数返回列中最小的非NULL值 MAX –该函数返回列中的最大值 GROUP BY – 按列汇集查询函数结果的子句 用ORDER BY对查询结果排序 ORDER BY子句让数据库对查询结果排序,这样你就无须自己编写应用程序进行“手工”排序了。ORDER BY子句必须放在查询语句的结尾。其基本用法如下: SELECT * FROM Contacts ORDER BY first_name; 你可以随意在任何选择语句中使用ORDER BY 子句返回多列结果。你还可以用它连接其他子句: SELECT first_name, last_name FROM Contacts WHERE first_name BETWEEN ‘a’ AND ‘k’ ORDER BY last_name; 你可以对多列数据排序。优先顺序按从左到右依次降低,所以查询语句中各列的排列顺序很重要。 SELECT * FROM Contacts ORDER BY company, last_name, first_name; 查询结果默认按数字或者字母的升序排序。你可以在ORDER BY 子句后面加上DESC关键词改成降序排列。在下面的例子中,最高的net_amount排在最先(降序)。假如两行或者两行以上数据都包含了同样的net_amount值,那么同行中last_name值在字母表中最先出现的排先,因为last_name一列还是按照升序排序的。 SELECT * FROM Sales ORDER BY net_amount DESC, last_name, first_name; 在按照定义的列名排序以后,大多数数据库随后将按照数据表内的第一列排序然后顺序向右再排序。具体的实现各有变化,因此,如果排序在应用中比较重要那么你应该明确地定义所要排序的列。 另外一值得注意的问题是,采用ORDER BY子句(以及WHERE子句),你正在用来排序结果的数据列并不一定得是返回结果集合的一部分。只要所有引用的列都在数据表内存在则下例完全有效: SELECT company, first_name, net_amount FROM Sales ORDER BY start_date, last_name; DISTINCT返回不重复结果 DISTINCT关键词只返回结果集合内不重复的数据行。例如,有时你可能需要找出Sales表内的公司,但是你又不想看见每个条目。于是你可以用DISTINCT对应每一公司名返回一行数据: SELECT DISTINCT company FROM Sales; 在使用DISTINCT时,它适用于所有的请求列。如果你打算列出表内的所有销售人员和他们所代表的公司而非每一销售记录,那么你可以使用下列语句。注意,这样操作还可能返回同一公司的若干条目等等。 SELECT DISTINCT company, last_name, first_name FROM Sales; 你还可以在对结果缩小范围和进行排序时结合SELECT语句使用DISTINCT。为了确定显示的内容,数据库首先会证实精练的请求是否匹配数据行,然后应用DISTINCT功能。在全部结果集合都得以确定之后即处理ORDER BY子句。如下例所示,只有net_amount大于100的数据行才被返回。由于DISTINCT保留遇见的第1个匹配查询条件的数据行而丢弃其他匹配行,所以ORDER BY语句所引用的net_amount看起来就好象产生了随机的结果。 SELECT DISTINCT company, last_name, first_name FROM Sales WHERE net_amount > 100 ORDER BY company, net_amount; 函数应用逻辑 返回单一值的函数称做聚集函数(aggregate function)。通过应用程序访问下列聚集函数的结果时,包含结果的“字段名”就是你所使用的实际函数。例如,在分析你的数据库结果时,结果数组的键值可能如下所示: $keyname = “COUNT(*)”; $resultkey = “AVG(net_amount)”; COUNT COUNT函数计算出结果集合中的数据行数。和其他函数一样它接受一个参数。以下的基本示例能告诉你数据表内的行数:SELECT COUNT(*) FROM Sales; 你也可以用它来计算任何结果集合中的行数。 SELECT COUNT(*) FROM Sales WHERE net_amount > 100; 如果你想看看某特定列有多少行包含非空值,那你不妨对该列使用COUNT函数。注意,除非数据库设置为字段为空时缺省填充NULL否则将返回表内数据行的总数。另外,列出的列在超出一个的情况下会引起错误。 SELECT COUNT(company) FROM Sales; COUNT还可以用来计算DISTINCT结果集合中的行数。 SELECT COUNT(DISTINCT company, last_name) FROM Sales; COUNT语句通常用在程序中确定FOR循环的循环次数。 AVG AVG返回某列所有字段的平均值,该列必须是数字数据类型。该函数用列的名字作为其参数,如果列字段数据类型是非数字类型的则函数返回“0”。SELECT AVG(net_amount) FROM Sales; 你可以结合子句限制该函数的应用范围。 SELECT AVG(net_amount) FROM Sales WHERE company LIKE ‘%ABCD Co%’; 就象所有聚集函数一样,ORDER BY语句将被忽略。 SUM SUM的工作方式和AVG差不多,只不过该函数返回结果集合中所有字段值的和。 SELECT SUM(net_amount) FROM Sales WHERE net_amount > 100; AVG、SUM、MIN和MAX函数在没有指定列的情况下都会返回错误,所以你不能使用“*”通配符。 MIN MIN返回指定列中最小的非空值。如果指定列是数字数据类型则结果将是最小的数字。如果它是一种字符串数据类型则函数将返回按字母表顺序出现的第1个值。SELECT MIN(net_amount) FROM Sales WHERE last_name = “Smith”; SELECT MIN(last_name) FROM Sales; MAX MAX的工作方式和MIN函数一样,只不过该函数返回最大的非空值。该函数也可以用于字符串或者数字列 SELECT MAX(net_amount) FROM Sales; SELECT MAX(company) FROM Sales WHERE net_amount > 100; MAX函数有时还用在包含自动递增键字段的列上确定下一条目的键ID。除非你正在运行一个非公开的数据库,否则在使用这一信息插入下一条目时务必谨慎,以防其他用户先你执行数据操作。 GROUP BY 令函数更有用 虽然以上提到的所有这些函数都能提供相当有用的信息,但是,如果有GROUP BY子句帮忙的话更能让你在列的字段子集中应用这些函数。不要对你的Sales表中每一家公司一次又一次地执行MAX函数查询——你完全可以带GROUP BY子句获得同样的结果: SELECT company, MAX(net_amount) FROM Sales GROUP BY company; 这样做可以获得每家公司net_amount的的最大值。在选择多列名的时候也可以采用该语句,你还可以用多列来对函数结果分组。 下面的例子演示了以上各种方式。首先,包括GROUP BY子句可以令你指定要显示的其他列。然而,你得知道这个例子将返回在组中遇到的第1个last_name值;Sum( net_amount )将显示全部公司的结果而不仅仅针对匹配姓氏的数据行。这是因为,我们只使用了Company字段来定义我们的组。 SELECT company, last_name, SUM(net_amount) FROM Sales GROUP BY company; 在上面的例子中,last_name列实际上并没有提供什么有用的信息,但这样做是为了在下一个例子中要用到的功能做准备。你可以创建多列定义的组。这样就可以在结果集合中产生针对特定行的函数结果,而结果集合则是由所有指定的GROUP BY列联合起来创建的: SELECT company, AVG(net_amount), last_name FROM Sales GROUP BY company, last_name; 上面的例子给每家公司中每一姓氏给出了平均的net_amount。你列出GROUP BY列的顺序控制着结果的排序,但是实际的函数值结果是一样的。 下面的例子表明如何组织结果而不显示分组的列。在有些场合这样做是很有用的,例如,如果要显示个人的销售量但却不显示姓名就能用上下面的例子了: SELECT company, COUNT(sale_id) FROM Sales GROUP BY company, last_name; 限制使用GROUP BY的查询 如你在以上示例中所看到的那样,你可以结合WHERE字句利用以上的概念限制查询的范围。WHERE子句会首先被计算,然后执行函数。在使用组的时候就是这样的。 SELECT company, AVG(net_amount), FROM Sales WHERE net_amount > 100 GROUP BY company; 上面的例子只对那些满足WHERE限制条件的数据行适用AVG函数。注意,WHERE子句必须放在GROUP BY子句之前。你还可以用HAVING语句对分组计算之后限制返回的结果集合。 SELECT company, AVG(net_amount), FROM Sales WHERE last_name BETWEEN ‘a’ AND ‘m’ GROUP BY company HAVING AVG(net_amount) > 500; 上面的语句计算每家公司net_amount的平均值,而且只计算那些姓氏满足限制条件的销售人员的销售量,同时只显示大于500的结果。 跟我学SQL:(三)使用SQL子选择来合并查询 你是否曾经为了得到所需要的信息而反复查询?子选择,也被称为子查询,也许正是你在寻找的。SQL的这项功能使你可以在一组结果中查询,创造性地给结果组加以限定,或是在向数据库的单一调用中将结果与一个无关系的查询做相关。这篇文章中我将给出几个子选择的例子并就何时使用他们进行讨论。 在一个结果组中搜索 子选择的理念很简单:一个选择查询安置在另一个查询内部,创建一个在单一声明搜索中不可用的资源。子选择允许查询的合并,结果组比较的责任落到了数据库中而不是应用软件代码中。 使用这个功能的一个途径是对两个表格中的可比数据专栏中的值进行定位。例如,我的一个数据库有两个表格,Album和Lyric。我可以很容易地通过下面的子查询声明来找到每一个Metallica的歌曲中包含“justice”的歌名: SELECT song_name FROM Album WHERE band_name = ‘Metallica’ AND song_name IN (SELECT song_name FROM Lyric WHERE song_lyric LIKE ‘%justice%’); 这个例子是很简单的,我从Album表格中选择了所有Metallica的歌曲,接着,我在lyric表格中选择所有包含“justice”的歌曲,最后,我使用IN关键字来从Lyric表格结果组中显示的Album表格中返回歌曲名称。 我使用Lyric表格结果组来给Album表格中的结果做限定。WHERE子句中的子选择部分是完全自包含的,因此我不需要使用例如Album.song_name和Lyric.song_name等完整的专栏名称。我没有从最终结果组的Lyric表格中返回任何值,如果我需要歌曲的Lyric,我会使用一个JOIN声明。 使用NOT IN排除结果 你可以使用NOT IN关键字来获得明确地不被包含在另一个结果组中的结果。例如,我想要通过下面的代码来返回Metallica在“And Justice for All”专辑中不包含单词“justice”的歌曲: SELECT song_name FROM Album WHERE album_name = ‘And Justice for All’ AND band_name = ‘Metallica’ AND song_name NOT IN (SELECT song_name FROM Lyric WHERE song_lyric LIKE ‘%justice%’); 在前面的SQL代码中,我选择了Metallica的“And Justice for All,”专辑中的所有歌曲,接着是带有歌词中带有“justice”所有歌曲,最后从在Lyric结果组中没有出现的Album结果组返回了所有歌曲。较之于返回两个查询并使用代码来比较数组,你通过一个单独的声明就可以得到确切的结果。 使用EXISTS来相关结果 有时你可以通过多种途径来访问相同的数据,而且你需要对你的结果进行匹配(或相关)来得到值的交叉区。例如,我可以通过搜索Album表格来得到Metallica的歌曲列表,可是,我也可以从我的Cover表格中得到由Damage, Inc表演的Metallica的歌曲的列表,我可以在两个表格中直接比较查询结果来对值作相关。 SELECT Album.song_name FROM Album WHERE Album.band_name = ‘Metallica’ AND EXISTS (SELECT Cover.song_name FROM Cover WHERE Cover.band_name = ‘Damage, Inc.’ AND Cover.song_name = Album.song_name); 在SQL代码中,我使用完整的专栏名称,这是因为我直接对两个表格作比较,而不仅仅是将结果组作为一个被动资源来使用。我并不从Cover表格中返回结果。一些数据库支持NOT EXISTS关键字来确保你并没有匹配。 使用合计函数来比较 除了使用子选择在相关的表格中检查数据,你还可以在一个WHERE子选择中使用合计函数来确定主结果组。例如,我想要核实每一个Metallica歌曲在Album表格中的条目。而且,我还想返回缺少歌曲的专辑的名称。很方便地,AlbumInfo表格包含的一个专栏(album_tracks)给出了应该有多少首歌曲方面的信息。 SELECT AlbumInfo.album_name FROM AlbumInfo WHERE AlbumInfo.band_name = ‘Metallica’ AND album_tracks <> (SELECT COUNT(*) FROM Album WHERE Album.album_name = AlbumInfo.album_name); 现在我已经成功地返回了所有Metallica的专辑中,应有的曲目数量与Album表格中实际的歌曲条目数量不符的专辑名称。 返回子选择结果 如果我还是关心每一张专辑的曲目数量并需要得到一个比较报告怎么办?你可以将一个子选择的结果作为最终结果组的一部分来返回。这个功能经常被合计函数所使用。通常地,对其他表格的访问可以作为你的查询的一部分。下一个例子将返回每一张Metallica的专辑,应该包括的曲目数量和在Album表格中包括的条目数量: SELECT AlbumInfo.album_name, album_tracks, (SELECT COUNT(*) FROM Album WHERE Album.album_name = AlbumInfo.album_name) FROM AlbumInfo WHERE AlbumInfo.band_name = ‘Metallica’; 另一个强有力的例子涉及了在AlbumInfo表格中将album_tracks值改变为在Album表格中实际的条目数量: UPDATE AlbumInfo SET album_tracks = SELECT COUNT(*) FROM Album WHERE AlbumInfo.album_name = Album.album_name) WHERE AlbumInfo.band_name = ‘Metallica’; 在上两个例子中的子选择声明被看作一个自包含单位来执行。 子选择比较关键字(ALL, SOME, ANY) 除了使用标准查询功能,还有三个关键字可以使你将一个表达式值和一个单栏子选择声明结果组作比较,这些关键字返回TRUE或FALSE的Boolean值。ALL关键字要求子选择中所有值都遵守比较运算符。SOME和ANY关键字则要求至少一对。这里是ALL关键字的一个简单实例。 SELECT * FROM AlbumSales WHERE album_gross > ALL (SELECT album_costs FROM AlbumProduction); 上面的例子将从AlbumSales表格返回在AlbumProduction表格里面付出总额大于成本而生产最昂贵的专辑的所有记录。如果用ANY替代ALL,声明将返回所有付出总额大于最低专辑成本的专辑记录。声明= ANY与IN关键字意义是相同的。声明<> ALL与NOT IN关键字是对等的。关键字ANY和SOME也是等同的。数据库生产商中对这些关键字的支持情况是不同的,因此在出现问题时要相信查阅生产商方面的资料。 谁对标准化数据结构有疑问? 子选择查询句法是简单易懂的,而懂得何时使用它却是一个难点。如果你曾经在标准化数据结构方面出现问题,子选择声明将帮助你获得更深入的了解 跟我学SQL:(四)查询多个表格 在对跨多个表格的数据进行组合时,有时很难搞清楚要使用哪一个SQL句法。我将在这里对将多个表格中的查询合并至单一声明中的常用方式进行阐述。 在这篇文章中的样本查询符合SQL92 ISO标准。不是所有的数据库生产商都遵循这项标准,而且很多厂商采取的提升措施会带来一些意料不到的后果。如果你不确定你的数据库是不是支持这些标准,你可以参看生产厂商的有关资料。 SELECT 一个简单的SELECT声明就是查询多个表格的最基本的方式。你可以在FROM子句中调用多个表格来组合来自多个表格的结果。这里是一个它如何工作的实例: SELECT table1.column1, table2.column2 FROM table1, table2 WHERE table1.column1 = table2.column1; 这个实例中,我使用点号(table1.column1)来指定专栏来自哪一个表格。如果所涉及的专栏只在一个参考的表格中出现,你就不需要加入完整的名称,但是加入完整名称会对可读性起到帮助。 在FROM子句中表格之间由逗号来分隔,你可以加入所需的任意多的表格,尽管一些数据库有一个在引入正式的JOIN声明之前他们可以有效地处理的内容这方面的限制,这个将在下面谈到。 这个句法是一个简单的INNER JOIN。一些数据库将它看成与一个外部的JOIN是等同的。WHERE子句告知数据库哪一个区域要做关联,而且它返回结果时,就像列出的表格在给定的条件下组合成一个单独的表格一样。值得注意的是,你的比较条件并不需要与你作为结果组返回的专栏相同。在上面的例子中,table1.column1和table2.column1用来组合表格,但是返回的却是table2.column2。 你可以在WHERE子句中使用AND关键字来将这个功能扩展至多于两个的表格。你还可以使用这样的表格组合来限制你的结果而不用实际地从每个表格返回专栏。在下面的例子中,table3与table1匹配,但是我没有从table3返回任何东西来显示。我只是确保来自table1的有关专栏存在于table3之中。注意此例中table3需要在FROM子句中被引用。 SELECT table1.column1, table2.column2 FROM table1, table2, table3 WHERE table1.column1 = table2.column1 AND table1.column1 = table3.column1; 然而,要注意的是,这个查询多个表格的方式是一个暗指的JOIN。你的数据库可能对事物进行不同的处理,这取决于它所使用的优化引擎。而且,忽略对与WHERE子句的相关特性的定义将会给你带来不愿看到的结果,例如从余下的查询中返回与每一个可能的结果相关的专栏的rogue域,就像在CROSS JOIN之中一样。 如果你习惯于你的数据库处理这种类型的声明的方式,且你只对两个或是少数几个表格进行组合,一个简单的SELECT声明就可以达到目的。 JOIN JOIN的工作方式与SELECT声明是相同的,它从不同的表格中返回一个带有专栏的结果组。在暗含的JOIN之上使用外部JOIN的优势是对你的结果组的更好的控制,而且还可能在涉及很多个表格的情况下提升性能表现。 JOIN的类型有几种:LEFT,RIGHT,FULL OUTER,INNER和CROSS。你所使用的类型是由你想要看到的结果所决定的。例如,使用LEFT OUTER JOIN将会从列出的第一个表格中返回所有有关的行,而同时如果没有信息与第一个表格相关的话将潜在地从所列出的第二个表格中加入行。 在这里INNER JOIN和暗含的JOIN是不同的,INNER JOIN将只返回那些在两个表格中都有数据的行。 对第一个SELECT查询使用如下JOIN声明: SELECT table1.column1, table2.column2 FROM table1 INNER JOIN table2 ON table1.column1 = table2.column1; 子查询 子查询,或叫子选择声明,是在一个查询中将结果组作为资源使用的一个途径。他经常被用来对结果进行限制或定义,而不是运行多个查询或操纵应用软件之中的数据。有了子查询,你可以参考表格来决定数据的内含,或是在一些情况下,返回一个专栏,而这个专栏是一个子选择的结果。 下面的例子中使用了两个表格。一个表格中包含了我想要返回的数据,而另一个表格则给出一个比较点来确定什么数据是我确实感兴趣的。 SELECT column1 FROM table1 WHERE EXISTS ( SELECT column1 FROM table2 WHERE table1.column1 = table2.column1 ); 子查询很重要的一个方面就是性能表现。便利性是有代价的,它取决于你所使用的表格和声明的大小,数量和复杂性,还有你可能会允许你的应用软件做处理工作。每一个查询在被主查询作为资源使用之前,都将被完整地单独处理。如果可能的话,创造性地使用JOIN声明可以以较少的滞后时间提供出相同的信息。 JOIN声明和子查询 对于JOINS的更详细的阐述和有关的理念,请参阅 掌握连接的一些概念。关于子查询的更多内容,请参阅使用SQL子选择来合并查询。 跟我学SQL:(五)创建和修改表格 再次欢迎您来到SQL(结构化查询语言)基础系列教程。本文将介绍数据库定义语言(DDL)用于创建数据库和表格以及修改表格结果的指令。 当你使用这些指令时一定要小心——它很容易删去你的数据库中的主要结构令您丢失数据。所以,在您开始修改数据库之前,您需要知道数据库是什么。 -------------------------------------------------------------------------------- 数据库之间的差异 本文中的样品查询系统遵循SQL92 ISO标准。并不是所有的数据库都遵循该标准,有些数据库做了改进,这会产生不可预料的结果。如果你不能确定你的数据库是否支持该标准,请参考相应的文档。 -------------------------------------------------------------------------------- 创建数据库 为了创建表格,你首先需要需要创建一个可以容纳表格的数据库。SQL用于创建数据库的基本语句是: CREATE DATABASE dbname; 你的数据库用户必须有建立数据库的适当权限。如果与你有关的用户不能发出用于创建新数据库的命令,要求数据库管理员为你建立数据库,你也作为管理员登录然后建立数据库并设置权限。 举个例子,用CREATE指令为一个应用程序建立一个数据库用于显示一个目录: CREATE DATABASE Catalog; 这给你一个用于在查询时与其它表格区分的表格名字。下一步是创建用于输入它的表格。 创建表格 如你所知,表格是有若干个栏目所组成。当创建表格时,你可以定义栏目并分配字段属性。表格建立后,可以用ALTER表格指令来修改它,我们稍后将提到这一点。 你可以用下面这条指令来创建数据库,命令行的参数为表格名字、栏目名字,还有每一栏的数据类型。 CREATE TABLE table_name (column1 data_type, column2 data_type, column3 data_type); 不同的数据库提供商的标准差别很大。你的帮助文档中应该有一段详细说明如何使用每一种数据、接受何种参数。为了通用,我在表A中列出了一些常用的数据类型。 表A 数据类型 用法 详细说明 Char Char(8) 它包含了一个固定长度的字符串,其值常常是字符串长度。 Varchar Varchar(128) 它包含了一个长度不大于指定值的长度可变的字符串。 Int Int(32) 这是一个不大于指定值得整数,也做Number或Integer。 Decimal Decimal(12,2) 这是一个总位数和小数点后位数不大于指定值得小数,也被称为Numeric或Number。 Binary Binary 用于存储二进制对象,在数据库中它一般不可分解和显示,也称为Raw或Blob。 Boolean Boolean 用来只是真或假,也成为Bit或Byte。 通用数据类型 在本例中,我们建立了一个存放库存商品信息的表格。所用到的栏目和数据类型如表B所示: 表B 栏目名称: prod_id prod_color prod_descr prod_size 数据类型: Int(16) Varchar(20) Varchar(255) Decimal(8,2) 在本例中,我使用了三种基本数据类型;然而,在实际使用时,根据数据库支持的内容,我可能还用用上tinyint、文本和mediumtext数据类型。 发出如下指令来建立表格: CREATE TABLE Products (prod_id INT(16), prod_color VARCHAR(20), prod_descr VARCHAR(255), prod_size DECIMAL(8,2)); 如果这些指令顺利完成,你就可以在表格中正常地插入信息。你可以参到文章SQL基础一:数据查询"得到详细说明。 除了数据类型,你还可以在创建表格时定义自动增量字段(auto-incremented field)、关键字、索引和特殊数值限制。在表格定义时,这些参数与数据类型一同传递。如果在创建表格Product时定义具有特殊数值限制的自动增量prod_id,命令如下: CREATE TABLE Products (prod_id INT(16)AUTO_INCREMENT, prod_color VARCHAR(20), prod_descr VARCHAR(255), prod_size DECIMAL(8,2), UNIQUE (`prod_id`)); 如果把prod_id做为索引字段定义,可以用CREATE INDEX: CREATE INDEX ProdIndex ON Product (prod_id); 这里有必要重申:数据库提供商在关键字的处理上有所不同。所以,具体情况请参考你的数据库提供商的文档。 -------------------------------------------------------------------------------- 关于索引的更多内容: 索引是一个比较深的课题。除了介绍有关关键字和索引的理论,Builder.com的供稿人Eric Roland写了几篇很好的文章,你可以通过它们来学到更多的相关知识。 修改表格 当你开始对表格进行操作时,你也许觉得有必要修改表格的结构、字段类型等等。在前面,我强烈建议你避免在生产环境(production environment)这么做。因为有些操作,如添加、删除和修改字段可能会删除或破坏相关字段中的数据。 好,现在让我们看看如何修改表格。首先,在表格Product中加入一栏。你可以指定该栏插入的相对其它栏的位置,也可以让它插到表格末端(默认): ALTER TABLE Product ADD prod_name VARCHAR(20) AFTER prod_id; 用类似的语句删除一个栏目: ALTER TABLE Product DROP prod_size; 最后,更改一个栏目的数据类型: ALTER TABLE Product CHANGE prod_color prod_color_id INT(20); 现在,你的表格如表C所示: 表C 栏目名称: prod_id prod_name prod_color_id prod_descr 数据类型: Int(16) Varchar(20) Int(20) Varchar(255)) 注意,有些数据库不支持关键字DROP。另外,如果你改变现有的某一栏的数据类型,大多数数据库会试图转化该栏目现有数据的数据类型。然而,如果是转为一个不支持的数据类型,数据就有可能丢失。举例来说,如果把一个类型为Varchar的包含人名字的字段改为Int类型,转换的结果可能是整型的默认值。 删除表格和数据库 在删除表格和数据库之前,你需要确保丢失这些数据不会造成恶果。如果你删除数据库,库中的所有表格和内容都会被清除。如果你删除一个表格,表格中的所有内容都会丢失,但是库中的其它表格没有影响。 在删除表格或整个栏目之前,你必须清楚数据库的结构。如果你进入一个已经存在的数据库并错误删除了某个元素,可能会影响到促发条件(?trigger)、存储过程和视图。有些数据库支持用关键字RESTRICT和CASCADE去预防由于删除表格带来的损失。RESTRICT一般按默认设置,预防丢失表格,而CASCADE用于删除与该表格有关的实体。 现在上面建立的表格Product是可以被删除的,我们开始删除它: DROP TABLE Product; 现在删除数据库: DROP DATABASE Catalog; 大多数数据库软件提供商支持DROP DATABASE命令,尽管它是在SQL99标准中被定义而不是SQL92。 部分数据库提供了FLUSH命令,该命令可以让你删除表格中的内容但又可以保持表格的结果,: FLUSH TABLE Product; 如你所见,删除数据库中的主要结构并丢失所存的所有数据的容易程度令人难以想象,所以,一定要小心使用这些命令,而当你不清楚数据库中的内容时,就不要使用这些命令。 数据库管理 在前一篇文章中,你学会了如何在一个或多个表格中查找数据。现在,你学会了如何把你操作数据库结构。你学会了创建、修改并销毁表格和数据。这些都是设计数据库驱动的应用程序的必须用的操作。 跟我学SQL:(六)串行数据类型 SQL的数据类型决定了一个字段的内容在数据库中会被如何处理、存储和显示。SQL92定义了标准的数据类型,目的是给数据库制造商建立自己的数据类型提供蓝图。在前面的文章中,我们介绍了一些常用的数据类型,这些数据类型分为四大类: 串行 数值 日期时间 区间型 本文将向你概述这些数据类型在数据库中是如何使用的,然后着重解释串行数据类型。这些信息可以作为有用的参考,或者作为关于某个数据库制造商具体产品中数据类型的背景知识。 使用数据类型 当你在数据库中创建了一个表格,你就定义了每列的名字以及要输入到这些列中的内容的数据类型。从先前的文章中借用一个例子: CREATE TABLE Products (prod_id INT(16)AUTO_INCREMENT, prod_color VARCHAR(20), prod_descr VARCHAR(255), prod_size DECIMAL(8,2), UNIQUE (`prod_id`)); 在以上的查询中,定义行prod_color VARCHAR(20)发出指令要创建一个列,名字是prod_color,数据类型是VARCHAR,长度为20。 你的数据库使用和每个类型相关的描述符来区别数据类型。例如,VARCHAR数据类型的描述符所含的信息将它区别为串行数据型,它包含所有的串字符,其长度是可变的。数据库里列的定义还包含了其他信息,例如对应于数据类型的特定长度。 如前所述,每个数据库制造商都希望在SQL92定义的标准上建立自己的数据类型。这样每个数据库在定义数据类型时都能够设定自己所需要的最大容量限制和其他属性。许多数据库使用的数据类型名字和这里列出来的一样,尽管每种的实现方法都有微小的差别。要确定特定数据类型使用方法的细节最好的方法还是查阅数据库制造商的文档。 已经说过了,希望对标准字符串数据类型有更多的了解就往下看。 串 有两种主要的串行数据类型:字符和位。串行使用数据库里由SQL_TEXT所定义的字符。SQL_92标准同时还提供了NATIONAL CHARACTER(国家字符集)和NATIONAL CHARACTER VARYING(国家字符集变体),这两者都能使用可定义字符集。后者的处理方法和CHARACTER以及CHARACTER VARYING类型一样。 CHARACTER | CHAR 使用方法:CHARACTER(clength) | CHAR(clength) CHARACTER和CHAR这两个关键字是相同的。 CHARACTER类型一个突出的特点是它们能够包含<space(空白)>这个字符。 CHARACTER 类型包含了固定长度的串字符(来自SQL_TEXT的语言集),clength。 <space> 字符在值的长度小于clength时起填充作用。这表示CHARACTER字段的长度是固定的。 你可以把CHARACTER的数据类型字段和相同类型的其他允许不同长度的字段比较,或者和CHARACTER VARYING 数据类型比较。 有些数据库允许和数值数据类型比较。 CHARACTER VARYING | CHAR VARYING | VARCHAR 使用方法:CHARACTER VARYING(maxlength) | CHAR VARYING(maxlength) | VARCHAR(maxlength) CHARACTER VARYING,CHAR VARYING,和VARCHAR这几个关键字是相同的。 这些类型能容纳最大长度的字符串,maxlength。 数据库把字段的长度作为值的实际长度。 你可以把这些数据类型的字段和相同类型的其他允许不同最大长度的字段比较。 BIT 使用方法:BIT(blength) 这种类型包含了带有长度的位字符(1和0),blength。例如,如果我们使用BIT(2),样本值将为“01”。 有的数据库会在串的开头插入空位,其的则会填充它们以符合固定长度的要求。 位字符是串,不是整数。 你可以把BIT数据类型的字段与相同类型的允许不同长度的其它字段比较,或者和BIT VARYING数据类型比较。 有些数据库允许BITS和CHARACTER或者INTEGER类型比较。 BIT VARYING 使用方法: BIT VARYING(maxlength) 这种类型包含了最大长度的位字符,maxlength。 所记录的长度被设为值的实际长度。 数据库允许和其的BIT VARYING数据字段比较,或者和BIT的数据字段比较。 对我们的SQL系列有了一些了解了吗? 请把你的评论、问题或者回应发到下面的讨论栏,或者如果你有关于SQL基础系列的论题,可以发到我们编辑的信箱。 串理论 数据库生产商通过建立这些基础的数据类型来创建你实际要实现的数据类型。对于字符串,这就可能包括相同名字的(不同)类型,例如CHAR或BIT,或者扩展到包括TEXT,SMALL TEXT,以及包含字符串的其他数据类型。 数据从一个数据库迁移到另一个数据库时,这种设计上的弹性产生了一个必须克服的障碍。在一个数据库里,你可能会有一个叫做CHAR的类型,这个类型所允许的最大容量大于你要迁移到的数据库的最大容量。而且,(SQL92)标准中没有明确定义的类型可能会变化较大,这样的话只用遵从惯例来简化迁移。 在ZDNet China最近的文章《BLOB移植的替换方案》中讨论了存在数据类型移植问题时保护数据的一个可能的解决方案。SQL标准没要包括存储二进制数据的指标,这造成了不同数据库制造商产品间的不兼容。软件开发者必须找到提到方案列清除这些障碍。 在这个系列的下一篇文章中,我们会看看SQL92的数字数据类型,每个有什么特点,以及对要实现这些类型的数据库的要求。 跟我学SQL:(七)从子表里删除数据 在这篇文章里我要描述一下如何从表格里删除列,要删除的这些列同时还要依赖于其他表格的标准。要解决这个问题就需要一个很聪明而且完全遵守SQL92子查询声明的应用程序。 我必须提醒读者的是,尽管查询可能会遵守SQL的标准,但是众多的数据库生产商会以不同的句法支持实现SQL。以下这个解决方案应该适合于大多数数据库;但是,如果你的结果有出入,就还是应该查看一下文档。同时,由于这个查询要处理DELETE声明,所以你应该在将其应用于真实的生产环境以前在实验数据上进行测试。 需要更多的背景信息? 查看这些文章就能快速上路: 《SQL基础I查数据查询》涉及到了DELETE查询的使用。 《使用SQL子选项来合并查询》说明子选项查询能够减少对数据库请求的数量,并提供了例子。 《SQL基础:查询多个表》提供了更多关于子选项的信息,还讲到了使用单个查询就能访问多个表格的多种其他方法。 宠物店的例子 要解释如何进行这种类型的列删除,我会使用如下这个数据库的表格,该数据库叫做PetStore,并包含有清单(inventory)信息。在叫做“品种(breed)”的表A里,我存储有每种动物的信息和宠物店库存的信息。在叫做“清单”的表B里,包含有商店里特定动物的信息。 在这个例子里,我们先假设商店把整窝Shitzu小狗都卖完了。我可以使用breed表格里的breed_id字段来删除Shitzu清单里的所有项目,就像这样: DELETE FROM inventory WHERE breed_id IN (SELECT breed_id FROM breed WHERE breed_name = ‘Shitzu’); 首先,我要指定需要删除记录的表格,在这里是清单表格。然后再将识别字段breed_id同子选项子句的结果反复比对。我知道要找的是Shitzus,所以就能直接删掉他们,而不用再在单独的请求里查询breed_id。 我必须要警告你的是,以这种方式使用DELETE声明是危险的,只有在你对数据库的结构很熟悉的情况下才能使用这些声明。DELETE查询会从受影响的表格里删除掉全部列,你应该知道这对你所管理着的数据意味着什么。有个好办法是使用SELETE *这个短语替代DELETE关键字来对DELETE声明的子查询结果进行测试,这样就能保证结果里含有你要删除的所有东西,就像这样: SELECT * FROM inventory WHERE breed_id IN (SELECT breed_id FROM breed WHERE breed_name = ‘Shitzu’); DELETE和JOIN联用 有人问到了解决这个问题另一个可能的办法:把JOIN子句和DELETE声明联合使用。由于以前没有使用过这种方法,我就研究了一下,发现SQL Server的文档声明支持这个方法,尽管它不符合SQL92。在经过测试和询问各种数据库平台的老手之后,我发现把DELETE和JOIN声明联合使用在我测试过的任何平台上都行不通。 从多个表格里一次删除 以上的解决方案还没有解释如何使用父表从多个子表里删除信息。但是SQL92规范里没有提供完成这项任务的标准解决方案。 DELETE的声明不能把多个表格作为一个参数接受。作为一个具有破坏性的查询,这能保证在命令要被执行的地方不会出现歧义。此外,这个限制防止了在单个声明内将AND和多个子查询联用。如果测试SELECT声明的结果用以检查DELETE查询将要影响到的是哪些数据,你会发现SELECT会返回多个表格的清单,DELETE不会影响到的多个子查询不在其中。 有很多可能的方法能够满足你的需求,例如在表格里创建一个字段,用以指明该项目是否为活动的。或者,你可以使用一些数据库里的预存程序在每个所需的DELETE查询里迭代。 跟我学SQL:(八)数值数据类型 SQL92标准定义了若干种基本数据类型,它们是SQL数据库中各种数据类型的基础。在《字符串数据类型》一文中,我们已经详细讨论了SQL92标准所定义的字符串数据类型。现在,我们来进一步讨论数值数据类型。 你最好开始尝试使用不同数据库实现方法并在它们传递数据,这样可以加深你对数值数据类型的理解。本文将给你一个数值数据类型的概要,你可以结合你的数据库的文档资料来学习。 在字符串、数值、datetime和interval这四种数据类型中,数值型的种类最多,约束也最多。在不同数据库实现方法之间交换数据时,数值型的精度也最容易降低。Oracle和SQL服务器之间的实现分歧(同样的数据类型长度不同)导致它们之间的数据传递过程会截短数字、改变它们的数值。因此,在移植程序前,你有必须很明确的了解两个平台间的数据定义差异,以及危及数据精度的风险。 谨记上述警告后,让我们看看SQL92标准的数值类型 基本数值类型 与数值有关的类型统称为数值类型。所有的数值都有精度,精度指的是有效数字位数。有的数值还有标度值(scale value),它用来指示小数点右边的最小有效数字位数。例如,数字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 是一种近似数值类型。 使用二进制精度,最大精度由数据库提供商设定。 其默认精度必须小于DOUBLE PRECISION的默认精度。 DOUBLE PRECISION 用法: DOUBLE PRECISION 是一种近似数值类型。 使用二进制精度,最大精度由数据库提供商设定。 其默认精度必须大于PRECISION的默认精度。 相关理论 数据库提供商在基本数据类型的基础上创建了你实际需要的数据类型。对数值类型来说,它可以包括同名的数据类型,如INT、REAL,也包括为了满足特定场合或者用途而创建的新数据类型。 在我们的下一篇文章,我们将讨论datetime和interval数据类型。