跟我学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 ‘
用自然语言来说,这条选择语句的含义是这样的:从数据表中选出年龄小于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 = ‘data
WHERE column3 = ‘data
你可以采用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 = ‘data
一旦你数据库中删除某一行数据就不可再后悔了,因此一般来说,最好在数据表中包括一名为“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返回某列所有字段的平均值,该列必须是数字数据类型。该函数用列的名字作为其参数,如果列字段数据类型是非数字类型的则函数返回“
你可以结合子句限制该函数的应用范围。
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数据类型。
跟我学SQL:(九)datetime和interval数据类型
datetime和interval是两种与时间有关的数据类型。它们的作用体现在以下几个方面:创建或者更改记录库中的某条记录、当某个时间发生时运行记录、或者计算某个datetime变量建立后所经历过的时间。本文将介绍SQL92标准对上述两种数据类型的描述。
SQL数据类型
如果你想进一步了解SQL数据类型,请阅读SQL基础:字符串型数据类型,这篇文章的范围覆盖了SQL中的通用数据类型并说明了各种字符串数据类型的用法,SQL基础:数字数据类型详细介绍了数字数据类型。
Datetime
用于表示时间或者日期的数据类型都属于datetime类型。每一种datetime数据类型都有他自己的用于获取值的长度和它所保存信息的手段,如天、月、分钟、秒、秒的小数等等。
实际上,datetime的实现形式随着定义它的标准不同而拥有不同的长度和格式;然而,各个公司定义的类型都内在地符合下述规则。举例来说,时标(timestamp)的某个实现可能没有分隔符,随着细节的规范不同,长度和格式也发生变化,在某些场合以空格做为间隔符。Datetime数据类型包括:DATA、TIME和TIMESTAMP。让我们仔细研究这些分类,首先我们看看DATA。
DATA
用法:DATA
DATA类型允许没有参数,如精度。
DATA的字段包括年、月和日。
DATA的长度为十个字符: YYYY-MM-DD。(Y表示年、M表示月、D表示日。)
它只允许与其它DATA类型字段相比较。
允许的数字必须符合公历的规范。
TIME
用法:TIME(精度)
该类型包含了小时、分和秒,格式为hh:mm:ss(h表示小时、m表示分、s表示秒)
精度可选择,(……)
时间以世界标准时间(Universal Coordinated Time,UTC)为准,即00:00:00表示.格林威治的午夜,服务器的时区隐含的。
如果不需要秒的小数部分,那么TIME的长度为八个字符。否则就是八位长度在加上精度:hh:mm:ss.p。
它只能与其它TIME类型数据进行比较。
如果没有指定精度,精度默认为0。
TIME WITH TIME ZONE
用法:TIME (精度) WITH TIME ZONE
这个值要符合TIME数据类型
TIMEZONE部分表示相对UTC的时差:00:00:00+hh:mm。它的范围为-12:59到13:00。
精度表示秒的小数部分。
带有TIMEZONE的TIME长度为14个字符加上精度,在加上一个分隔符。
只可以与带有TIMEZONE的TIME类型数据进行比较
TIMESTAMP
用法:TIMESTAMP(精度)
该类型包含有年、月、日、时、分、秒,格式为:YYYY-MM-DD hh:mm:ss.。
可以包括秒的小数部分,这由定义的精度决定。
它的日期部分符合公历标准,时间部分为UTC格式。默认为当地时区。
时标的长度为19个字符,加上精度,在加上精度分隔符。
许多系统偏离上述定义的长度,如UNIX风格时标格式为:,YYYY-MM-DD hh:mm:ss.p。
如果没有定义精度,默认值为6,但是许多数据库公司默认为0,所以请参考你的开发文档。
时标只可以与其它TIMESTAMP类型的值相比较。
TIMESTAMP WITH TIME ZONE
用法:TIMESTAMP(精度)WITH TIME ZONE
时标部分符合上述TIMESTAWP的规则。
精度代表秒的小数部分。
时区部分的要求和TIME WITH TIME ZONE一样,即时区符合UTC规范,范围在-12:59到 +13:00之间。
总长度为25个字符,加上精度,加上一个精度分隔符:YYYY-MM-DD hh:mm:ss.p。
它只能与其它TIMESTAMP WITH TIME ZONE类型的数据进行比较。
Interval
Interval用于表示时间尺度。例如,你可以用操作符(将在下面进行解释)去计算两个日期间天数并加以保存。
各个公司在处理interval上有很大的不同——有些公司提供不同的度量单位,如年或者分钟,而有些公司在根本就不支持interval。SQL92标准的interval类型只提供一种子类型:INTERVAL。
INTERVAL
用法: INTERVAL (限定语)
有两种类型的interval:一种为“年份-月份”,即保存年份和月份(YYYY-MM);一种为“天-时间”(DD HH:MM:SS),用来保存天数、小时、分钟和秒。
限定语——在某些数据库中interval前导精度(lead precision)——根据其值来指示interval采用“年份-月份”还是“天-时间”方式。
interval可正可负。
当与其它interval类型变量相比较时,结果保持最大精度,如有必要则补零。
INTERVAL全部由整数组成,除了含有小数的秒之外。
“年份-月份”类型的interval变量只能与其它的“年份-月份”的interval变量进行比较。“天-时间”类型也与此类似。
操作符
操作结果类型
当处理日期时间时,时区保持不变——尽管有些数据库为了比较而将其中的一个时区转换为另一个。存在一些操作关键字,如OVERLAPS和EXTRACT,它们用于操作和比较datetime类型数据。然而,不同的数据库在这些操作关键字用法和支持方式上有着很大的不同。
OVERLAPS用于计算时间交叠的跨度,其操作对象可以是两个datetime也可以是一个datetime和一个interval。EXTRACT用于提取datetime或者interval类型数据的某个部分,如在DATA类型数据中提取月份。
数据类型
由于已建立的标准是为了各公司在现有基础上实现实际上的数据类型的,因此在具体限制、参数和数据类型等方面,你还是需要经常参考你的数据库文档。
全面接触SQL语法(1)
SQL功能与特性 通过SQL命令,程序设计师或数据库管理员(DBA)可以:
(一)建立数据库的表格。(包括设置表格所可以使用之空间)
(二)改变数据库系统环境设置。
(三)针对某个数据库或表格,授予用户存取权限。
(四)对数据库表格建立索引值。
(五)修改数据库表格结构。(新建、删除或是修改表格字段)
(六)对数据库进行数据的新建。
(七)对数据库进行数据的删除。
(八)对数据库进行数据的修改。
(九)对数据库进行数据的查询。
这几项便是通过SQL命令可以完成的事情,看起来是不是比起“查询”两个字所代表的功能要多的多了呢?
SQL语法的分类
其实SQL命令并不是非常多,可是要把SQL用到出神入化,却也只需要短短几个命令便够,因为SQL命令是针对关系型数据库所建立出来的语法叙述,所以SQL在这类数据库中所发挥的功能非常的强,以下将针对在VB中常用的SQL语法基本命令加以分类介绍。在说明SQL的命令以及使用语法之前,以下将SQL做了的分类,在致上SQL语法所使用到的类型,可以说都已包含在这些类别当中。
第一类、属性词(Predicates) 在SQL命令中用来指明所要选择的记录的方式。如ALL、TOP与DISTINCT等等。
第二类、声明(Declaration) 针对SQL Parameter或Parameter Query 的名称与数据类型做声明,如PARAMETERS的声明等等。
第三类、条件子句(Clause) 在SQL的查询中,利用一些表达式定义出查询的条件,以缩小寻找的范围,如WHERE。
第四类、运算符(Operator)与操作数(Operation) 在SQL的查询中,与Operation共同组成表达式(Expression),如BETWEEN....AND 运算符与INNER JOIN操作数。
第五类、函数(Function) 一些SQL常见的函数,像是AVG()是求算数平均数的函数。
第六类、SQL语句(Statement) SQL的语句,可以说是SQL语法的主体,用来对某一个特定的数据库发出指示,并返回相关的数据,而SQL的语法结构,基本上可以利用下面的式子来表示:命令+条件子句
例如: SELECT*FROM TAB WHERE TAB.NAME='A'
其中的“FROM....WHERE”便是一个条件子句,其实SQL的语法并不难,您只需记住这样的一个规则,相信可以很快的了解SQL用法。
SQL语法与命令
SELECT 语句
SELECT[predicate]{*|table.*|[table.]field [,[table.]field2[,...]]} [AS alias1 [,alias2[,...]]]
FROM tableexpression [,...][IN externaldatabase] [WHERE...] [GROUP BY...] [HAVING...] [ORDER BY...]
[WITH OWNERACCESS OPTION]
SELECT 语句包括下面几个部分
predicate 如前面所述,包括了ALL,DISTINCT,DISTINCTROW,与TOP 我们可以利用这样的语句去限制查询后所得的结果。
* 从指定表格中指定所有的字段。
table 针对被选择出的记录的字段,所指定表格的名称。
field1,field2 想要读取数据的字段名称,如果包含了一个以上的字段,会依照列出的顺序来读取数据。
alias1,alias2 用来替代在表格实际字段名称的化名。
tableexpression 表格名称或包含我们所想要的数据的表格。
externaldatabase 若使用到不是目前的数据库则将其名字定义在externaldatabase当中。
ALL,DISTINCT,DISTINCTROW,TOP属性词用法
SELECT [ALL|DISTINCT|DISTINCTROW|[TOP n[PERCENT]]] FROM table
ALL 若是您不指定任何的字段数据,则Microsoft Jet数据库引擎(database engine)将会选择所有的字段,并依据所定的条件查询出需求数据集。
例如下面这两个例子将会具有相同的效果,都会从职员表格中返回所有字段的数据。
例如: 若是我们要查询出职员表格中的所有记录,可以通过下面的语句来完成。
SELECT ALL* FROM 职员表格; DISTINCT
对某个表格所选择的字段数据,略过重复的情况,也就是说,针对某个字段查询出来的记录结果是唯一的。例如有许多存放在职员表格的职员数据,也许会具有相同的姓名,所以若是我们用SQL语句中的SELECT DISTINCT,则查询出来的结果将会针对不一样的姓名加以筛选。若是您把
DISTINCT 加以省略,则这样的查询会显示所有的记录。
DISTINCTROW 将整条记录重复的记录忽略掉,而不是只有针对某一个字段的数据。
table 指定查询记录所需要的表格。
例如: SELECT DISTINCTROW 公司名称 FROM 顾客表格 INNER JOIN 订单表格 ON 顾客表格.顾客ID=订单表格.顾客ID
ORDER BY 公司名称;
如果您忽略 DISTINCTROW 则会对每个公司产生一行以下的订单数据。此外,若是DISTINCTROW只有用在一个表格当中,则会被省略掉。
TOP 从第一条或最后一条开始(利用ORDER BY条件子句),返回特定条数的数据。
例如: 当您想要知道在2000年,班上前25名的学生姓名数据时,您可以输入这样的语句:
SELECT TOP 25 学生姓名 FORM 学生表格 WHERE 毕业年份=1994 ORDER BY 毕业成绩平均分数 DESC;
如果您没有加上ORDER BY 这行条件的话,您所得到的数据,将会随机的数据。此外,在TOP语句之后,除了可以加上数字以外,还可以利用保留字PERCENT来查询。
例如: SELECT TOP 10 PERCENT学生姓名
FROM学生表格 WHERE毕业年份=1994 ORDER BY毕业成绩平均DESC;
PARAMETERS(参数)声明的用法
对于参数型的查询语法中,对参数的名称以及数据类型作 声明的操作。
PARAMETERS name datatype[,name datatype[,...]]
name PARAMETERS的名称。您可以把参数名称当作字符串来使用,若是名称中包含了空字符串,可以利用中括号来处理,例如:“VBeden”。
datatype 输入参数的数据类型。
例如: 若是您在查询时,需要机动的输入姓名 ,可以利用下列的方式完成:
PARAMETERS “输入姓名” Text;
SELECT* FROM 职员表格 WHERE姓名=“输入姓名:”
ORDER BY条件语句
此条件子句,通常与SELECT语句合并使用目的是将查询的结果,依照指定字段加以排序。
SELECT fieldlist FROM table WHERE selectcriteria ORDER BY field[ASC|DESC][,field2[ASC|DESC][,...]]
fieldlist 欲查询的字段名称。其中可以与ALL,DISTINCT,DISINCTROW,或TOP一起来使用。
table 欲查询的表格名称。
selectcriteria 查询的标准设置。
field1 指定要依照那个字段作为排序的依据,若是你没有加上ORDER BY查询出的数据集将不会作排序的操作。
ASC 递增顺序类别。(默认值)
DESC 递减顺序类别。
例如: 或是我们要将输出数据依据出生的先后次序排列,可以利用下面的命令。
SELECT 姓名,生日 FROM 职员表格 ORDER BY 生日
SELECT LastName,FirstName FROM Employees ORDER BY LastName ASC;
IN 条件子句
指定要速胜哪一个外部数据库的表格。(必须是Microsoft Jet数据库引擎所可以连接的数据库,如dBase,Paradox等等)
SELECT|INSERT]INTO destination IN {path|["path" "type"]|[""[type;DATABASE=path]]}
FROM tableexpression IN {path|["path" "type"]|[""[type;DATABASE=path]]}
destination 欲插入数据的外部表格名称。
tableexpression 表格名称或是被读取数据的表格名称。这个参数可以是一个单一的表格名称,或是一段已经被存储的SQL查询等。
path 包含该表格的完整路径名称。
type 数据库的类型名称, 通常是当数据库部属于Jet database时才会使用。(例如:dBASE III,dBASE IV,Paradox 3.x,Paradox 4.x,或 Btrieve)
例如:下面这两段的意义相同
PartA....FROM Table IN ""[dBASE IV;DATABASE=C:\DBASE\DATA\SALES;];
PartB....FROM Table IN "C:\DBASE\DATA\SALES" "dBASE IV;"
例如:Microsoft Jet database
SELECT 顾客编号 FROM 顾客表格 IN CUSTOMER.MDB WHERE 顾客编号 Like "A*";
其中CUSTOMER.MDBO 为Jet database 的数据库名称,其中包含了顾客表格。
例如:dBASE III or IV
SELECT 顾客编号 FROM 顾客表格 IN "C:\DBASE\DATA\SALES" "dBASE IV;" WHERE 顾客编号 Like "A*";
所以当我们使用不同于ACCESS 的数据库时,必须指明该数据库的类型名称。
HAVING 条件子句
指定一特定的分组记录,并满足HAVING 所指定的条件或状态,但条件是针对分组的条件设置。
SELECT fieldlist FROM table WHERE selectcriteria GROUP BY groupfieldlist HAVING groupcriteria
fieldlist 显示被查询的字段名称。(可与ALL,DISTINCT,DISTINCTROW,或TOP相结合)
table 欲查询数据的表格名称。
selectcriteria 选取标准。
groupfieldlist 分组记录的字段名称,到多10个字段。而这些字段的顺序决定最高到最低的分组阶层。
groupcriteria 决定什么样的分组记录要被显示。
HAVING跟WHERE 的用法相当类似,不同之处在于HAVING必须用于GROUP之后的分组数据上。
例如:
SELECT 分类编,Sum(库存数量) FROM 产品表格 GROUP BY 分类编号 HAVING Sum(库存数量)>100 AND 产品名称 LIKE "*纸";
GROUP BY 条件子句
依据指定的字段,将具有相同数值的记录合并成一条。
SELECT fieldlist FROM table WHERE criteria GROUP BY groupfieldlist
fieldlist 欲读取的字段名称。(可与ALL,DISTINCT,DISTINCTROW,或TOP合并使用)
table 被查询的表格名称。
groupfieldlist 分组记录的字段名称,到多10个字段,而这些字段的顺序决定最高到最低的分组层次。
例如:
SELECT 姓名,Count(姓名)AS 职员姓名 FROM 职员表格 WHERE 部门名称='业务部' GROUP BY 姓名
FROM 条件子句
指定表格名称或是查询,其中包含列在SELECT语句的字段数据。
SELECT fieldlist FROM tableexpression[IN externaldatabase]
fieldlist 表格中的字段名称。(可与ALL,DISTINCT,DISTINCTROW,或TOP相结合)
tableexpression 表格名称,或多个表格的算式。
externaldatabase 若该表格参考到外部的数据库时,将其完整的路径名称记下。
例如:从职员表格下,查询出所有姓名字段的数据(只有姓名字段被查询,其他则不显示)。
SELECT 姓名 FROM 职员表格;
WHERE 条件子句
指定查询的条件与限制。
SELECT fieldlist FROM tableexpression WHERE criteria
fieldlist 字段名称。(可与ALL,DISTINCT,DISTINCTROW,或TOP相结合)
tableexpression 表格名称,或多个表格的算式。
criteria 查询的结果,必须依照这一限制标准。
例如:要查询出职员表格中,所有姓氏是李的数据,可以用下面的语句。
SELECT 姓名 FROM 职员表格 WHERE 姓氏='李';
BETWEEN...AND 运算符
决定某一人数值是否介于特定的范围之内,此运算符只可以用在SQL的语句中。
expr[Not]BETWEEN value1 AND value2
expr 指定要加以计算的字段与表达式的组合。
value1,value2 所指明的数值范围。
例如:若是要从职员表格查询出所有年龄介于25-30岁的员工,可以利用下面的程序来做。
SELECT 姓名,年龄 BETWEEN 25 AND 30 FROM 职员表格;
LIKE 操作数
用来将一字符串与另一特定字符串样式(pattern)比较,并将符合该字符串样式的记录过滤出来。
expression LIKE "pattern"
expression 使用在WHERE条件子句,SQL表达式。
pattern 用以比较的字符串样式。
例如:若是你要查询出所有以“李”为首的姓氏,可以利用下面的式子。
Like "李*"
LIKE操作数的多种范例:
1、多个字符:
(1)"a*a" 可筛选:"aa","aBa","aBBBa",不能筛选:"aBC"
(2)"*ab*" 可筛选:"abc","AABB","Xab",不能筛选:"aZb","bac"
2、特殊字符: "a“*”a" 可筛选:"a*a",不能筛选:"aaa"
3、单一字符: "a?a" 可筛选:"aaa","a
4、单一数字: "a#a" 可筛选:"a
5、字符范围: "“a-z”" 可筛选:"f","p","j",不能筛选:"2","&"
6、指定字符以外部范围: "“!a-z”"
7、指定非数字: "“!0
8、组合式结构: "a“!b-m”#" 可筛选:"An9","az0","a99",不能筛选:"abc","aj0"
SQL数字函数
1、AVG:算数平均数
AVG(expr)
Expr 字段名称或表达式。
例如: 若要计算职员身高超过
SELECT Avg(身高) AS 平均身高 FROM 职员表格 WHERE 身高>165;
2、COUNT:计算记录条数
COUNT(expr)
expr 字段名称或表达式。
例如: 若是要统计出业务部门的职员人数,并查询出职员的姓名,可以利用下面的程序。
SELECT Count(姓名) AS 职员姓名 FROM 职员表格 WHERE 部门名称='业务部';
3、FIRST与LAST:返回某字段的第一条数据与最后一条数据。
FIRST(expr) LAST(expr)
expr 字段名称或表达式。
例如: 若是要找出货品数量字段的第一条数据与货品价格字段的最后一条数据时,可以利用下面的查询方式。
SELECT FIRST(货品数量),LAST(货品价格) FROM 订单表格
4、MAX,与MIN:返回某字段的最大值与最小值。
用法同FIRST与LAST。
5、SUM:返回某特定字段或是运算的总和数值。
SUM(expr)
expr 字段名称或表达式。
例如: 要计算出货品总价,可使用下面的程序。
SELECT Sum(单位价格*货品数量) AS 货品总价 FROM 订单表格
多层SQL查询
顾名思义,多层的SQL查询的便在于:“在一个SQL语句中可以包含另一个SQL查询语句,形成内部嵌套的查询类型。”
comparison[ANY|ALL|SOME](sqlstatement)
expression[NOT]IN (sqlstatement)
[NOT]EXISTS(sqlstatement)
comparison 将表达式与内层查询的结果比较的操作。
expression 对内层查询的结果作搜索的表达式。
sqlstatement 为SELECT语句构成的SQL查询,必须用()将该语句括起来。
例如: 我们先从订单表格当中,查询出所有的单位,再将产品表格中的单位与的一一对比,查询出所有高于订单表格的单位价格的记录。
SELECT * FROM 产品表格 WHERE 单位价格>ANY (SELECT 单位价格 FROM 订单表格 WHERE 折扣>=.25);
SQL与数据库的维护
表格的建立
将SQL中的基本语法作了一番介绍以后,但大多是偏向于数据库数据的查询与过滤,但实际上,我们通过SQL命令所可以做的事还有很多,接下来要介绍的便是如何利用SQL的语法命令来建立一个数据库中的表格。
CREATE TABLE语句
我们可以利用这个命令,来建立一个全新的表格,但前提则是:数据库必须已经存在。
CREATE TABLE table(field1 type[(size)][index1][,field2 type[(size)][index2][,...]][,nultifieldindex[,...]])
table 欲建立的新的表格名称。
field1,field2 在新表格中的新的字段名称,到少要一个字段以上。
type 字段的数据类型。
size 字段的大小。
index1,index2 利用CONSTRAINT条件子句定义一个单一字段的索引名称。
multifieldindex 利用CONSTRAINT条件子句定义一个多重字段的索引名称。
例如: 建立一个拥有职员姓名与部门字段的表格。
CREATE TABLE 职员表格 (姓名TEST,部门TEST,职员编号 INTEGER CONSTRAINT职员字段索引PRIMARY KEY)
在这一个范例中,我们建立了一个表格名称为“职员表格”的表格,并且定义了该表格的主键值,以限制数据不能重复输入。
表格索引的建立
CREATE INDEX语句 这个命令主要是对一个已存在的表格建立索引,其用法如下:
CREATE[UNIQUE]INDEX index ON table(field[ASC|DESC][,field[ASC|DESC],...])[WITH {PRIMARY|DISALLOWNULL|IGNORENULL}]
index 欲被建立的索引名称。
table 欲建立索引的表格名称。
field 欲被建立的索引的字段名称。并可通过DESC保留字,来决定索引的顺序。
例如:在职员表格中建立一个索引。
CREATE INDEX 新索引名称 ON 职员表格(姓名部门);
表格的字段更新
CONSTRAINT条件子句
CONSTRAINT 的功能是类似索引(INDEX)的,虽然CONSTRAINT 也可以建立表格之间的关联性。
单一字段索引:
CONSTRAINT name{PRIMARY KEY|UNIQUE|REFERENCES foreigntable[(foreignfield1,foreignfield2)]}
多字段索引:
CONSTRAINT name
{PRIMARY KEY(primary1[,primary2[,...]])
|UNIQUE(unique1[,unique2[,...]])
|FOREIGN KEY (ref1[,ref2[,...]])
|REFERENCES foreigntable[(foreignfield1[,foreignfield2[,...]])]}
name 要被建立的CONSTRAINT名称。
primary1,primary2 被用来设计成主键值的字段名称(可一个以上)。
unique1,unique2 被用来设计成唯一键值的字段名称(可一个以上)。
foreign key 字段名称,或是参考到别的表格中字段的字段名称。
foreigntable 如前所述,被参考到的表格。
foreignfield1,foreignfield2 在参考到的表格当中,被ref1,ref2字段所指定的字段。如果被参考的字段是参考表格中的主键值,你也可以省略这个条件子句。
例如: 当我们要建立一个新的职员数据表格,表格包含姓名、部门名称与生日三个字段,且由这三个字段建立一个唯一的索引时,可以使用下面这段SQL的语句。
CREATE TABLE 职员数据表格
(姓名 TEST,部门名称 TEST,生日 DATETIME,CONSTRAINT 职员数据表格限制 UNIQUE(姓名,部门名称,生日));
以上是SQL中,与数据库表格建立相关的命令,你可以利用这些命令,通过SQL的语句,将数据库表格完整的建立出来,接下来的章节,将针对数据库建立之后的维护与增删所要使用的SQL语句作一介绍。
表的删除
DELETE语句 我们可以利用DELETE语句,将表格中的记录删除。(注意:记录被删除后,无法再复原,所以条件设置要正确)
DELETE[table.*] FROM tableexpression WHERE criteria
table 欲删除记录的表格名称,也可以用*来取代。
tableexpression 一个或一个以上表格的名称。此一参数可以为单一的表格名称或是从INNER JOIN,LEFT JOIN,或RIGHTJOIN 等运算所得到的结果。
criteria 决定表格中记录要被删除的标准。
例如: 若是我们要将职员表格中姓名姓名叫做'李名'的记录删除,我们可以利用下面的SQL语句来完成。
DELETE * FROM 职员表格 WHERE 姓名='李名';
数据库表格相关的操作命令
SQL除了可以作为查询与数据库表格的建立的工具外,对于数据库与表格的新建、删修、与维护,与具有相当不错的功能,若是读者使用SQL命令得宜,对于整个效率的提高有着很大的帮助,所以对于SQL语句所带来的优势,常常会遇到一个情况,就是:“当我们对多个表格作复杂与多步骤的处理时,或许SQL只要一个语句就可以完成所有的需求与目标”,乍看一下,或许觉得有些玄妙,但是接下来的章节,会让你了解其中的妙处。
SELECT...INTO语句 我们可以通过这个命令,利用既存表格查询,来建立一个新表格的查询语句。
SELECT field1[,field2[,...]]INTO newtable[IN externaldatabase] FROM source
field1,field2 欲拷贝到新表格的字段名称。
newtable 欲建立之新表格的名称,不可是已经存在的表格。
externaldatabase 若是该表格在另外的外部数据库时,该数据库的名称。
source 记录数据拷贝的来源表格名称,可以是单一的表格或是一段SQL查询之语句。
例如:你可以通过下面的SQL语句,来建立一个新的“训练名册”表格。
SELECT 职员表格.姓名,职员表格.部门 INTO 训练名册 FROM 职员表格 WHERE 职称='新进人员';
INNER JOIN操作数
当某一个共同的字段数据相等时,将两个表格的记录加以组合。
SELECT fields FROM table1 INNER JOIN table2 ON table1.field1 compopr table2.field2
table1,table2 欲进行记录组合的表格名称。
field1,field2 欲组合的字段名称。(必须具有相同的数据类型)
compopr 比较关系运算符如下:“=”,“<”,“>”,“<=”,“<>”等。
例如:若是你要把分类表格与产品表格作组合,可参考下面的SQL语句。
SELECT 分类名称,产品名称 FROM 分类表格 INNER JOIN 产品表格 ON 分类表格.分类编号=产品表格.分类编号;
UNION操作数
我们可以通过UNION操作数来建立连接的查询条件,UNION操作数可以将两个以上的表格或是查询的结果组合起来。
[TABLE]query1 UNION [ALL][TABLE]query2 [
query1,query2,queryn 为一个SELECT的语句,或是一个已存在的查询名称,或是一个已存在的表格名称。
例如: 你可以利用下面的SQL语句,将订单数量超过1000的顾客表格记录,与新客户表格作UNION的操作。
TABLE 新客户表格 UNION ALL SELECT * FROM 顾客表格 WHERE 订单数量>1000;
ALTER语句 在一个表格被建立之后,利用ALTER语句,我们可以去修改表格的字段设计。
ALTER TABLE table
{ADD {COLUMN field type[(size)][CONSTRAINT index]
|CONSTRAINT multifieldindex}
|DROP {COLUMN field|CONSTRAINT indexname}}
table 欲被ALTER的表格名称。
field 要被增加或删除的字段名称。
type 字段数据类型。
size 字段大小。
index 对此字段的索引。
例如:在职员表格中新建一个“薪水”的字段。
ALTER TABLE 职员表格 ADD COLUMN 薪水 CURRENCY;
例如:在职员表格中删除一个“薪水”的字段。
ALTER TABLE 职员表格 DROP COLUMN 薪水;
DROP语句 针对所指定的表格或字段加以删除,或是把索引删除。
DROP {TABLE table|INDEX index ON table}
table 欲删除之表格或索引依附之表格名称。
index 欲从表格中删除的索引名称。
例如:从职员表格中,删除编号索引。
DROP INDEX MyIndex ON Employees;
例如:从数据库中,删除整个表格。
DROP TABLE 职员表格;
INSERT INTO语句 新建一条数据到表格当中。
多条记录新建查询:
INSERT INTO target [IN externaldatabase][(field1[,field2[,...]])]
SELECT [source.]field1[,field2[,...] FROM tableexpression
单条记录新建查询:
INSERT INTO target[(field1[,field2[,...]])] VALUES(value1[,value2[,...]]
target 新建记录的表格名称。
externaldatabase 外部数据库的路径,搭配IN条件子句使用。
source 若是记录从其它表格中拷贝时,指明该表格的名称。
field1,field2 欲增加数据的字段名称。
tableexpression 表格名称或描述记录是从哪一个表格中插入的表格名称。配合INNER JOIN,LEFT JOIN,或 RIGHT JOIN 运算符一起使用。
value1,value2 欲插入表格中的数值。
例如:在客户数据表格中,从新的表格插入数据。
INSERT INTO 客户数据表格 SELECT 新客户数据表格.* FROM 新客户数据表格;
例如:在职员表格中插入数据。
INSERT INTO 职员表格(姓名,生日,职称) VALUES("王荣","
例如:从训练人员表格中,将职员雇用时间超过30天者,加入到正式职员表格中。
INSERT INTO 职员表格 SELECT 训练人员表格.* FROM 训练人员表格 WHERE 雇用天数>30;
UPDATE语句
建立一个UPDATE的查询,通过条件的限制来修改特定的数据。
UPDATE table SET newvalue WHERE criteria;
table 欲修改数据的表格名称。
newvalue 欲修改成的数值(将该项数值插入到特定的字段当中)。
criteria 查询条件,用来决定要修改哪些记录。
例如: 若是要把订单表格中的订单数量修改成1.1倍,运费为1.03倍,可利用下列之SQL语句来完成。
UPDATE 订单表格 SET 订单数量=订单数量 * 1.1,运费=运费 * 1.03倍 WHERE 运达地点='美国';
当我们完成修改后,你可以利用 SELECT 语句,配合同样的 WHERE 条件子句,来察看修改的数据是否正确。
事实上,要利用SQL完成某些看似复杂的操作,并不需要繁琐的SQL命令组合,或是许许多多的步骤才能完成,其实最重要的还是要活用SQL命令,才会在最精简的SQL语句里获得最高的效率。
五种提高 SQL 性能的方法
有时,为了让应用程序运行得更快,所做的全部工作就是在这里或那里做一些很小调整。啊,但关键在于确定如何进行调整!迟早您会遇到这种情况:应用程序中的 SQL 查询不能按照您想要的方式进行响应。它要么不返回数据,要么耗费的时间长得出奇。如果它降低了报告或您的企业应用程序的速度,用户必须等待的时间过长,他们就会很不满意。就像您的父母不想听您解释为什么在深更半夜才回来一样,用户也不会听你解释为什么查询耗费这么长时间。(“对不起,妈妈,我使用了太多的 LEFT JOIN。”)用户希望应用程序响应迅速,他们的报告能够在瞬间之内返回分析数据。就我自己而言,如果在 Web 上冲浪时某个页面要耗费十多秒才能加载(好吧,五秒更实际一些),我也会很不耐烦。
为了解决这些问题,重要的是找到问题的根源。那么,从哪里开始呢?根本原因通常在于数据库设计和访问它的查询。在本月的专栏中,我将讲述四项技术,这些技术可用于提高基于 SQL Server? 的应用程序的性能或改善其可伸缩性。我将仔细说明 LEFT JOIN、CROSS JOIN 的使用以及 IDENTITY 值的检索。请记住,根本没有神奇的解决方案。调整您的数据库及其查询需要占用时间、进行分析,还需要大量的测试。这些技术都已被证明行之有效,但对您的应用程序而言,可能其中一些技术比另一些技术更适用。
从 INSERT 返回 IDENTITY
我决定从遇到许多问题的内容入手:如何在执行 SQL INSERT 后检索 IDENTITY 值。通常,问题不在于如何编写检索值的查询,而在于在哪里以及何时进行检索。在 SQL Server 中,下面的语句可用于检索由最新在活动数据库连接上运行的 SQL 语句所创建的 IDENTITY 值:
SELECT @@IDENTITY
这个 SQL 语句并不复杂,但需要记住的一点是:如果这个最新的 SQL 语句不是 INSERT,或者您针对非 INSERT SQL 的其他连接运行了此 SQL,则不会获得期望的值。您必须运行下列代码才能检索紧跟在 INSERT SQL 之后且位于同一连接上的 IDENTITY,如下所示:
INSERT INTO Products (ProductName) VALUES ('Chalk')
SELECT @@IDENTITY
在一个连接上针对 Northwind 数据库运行这些查询将返回一个名称为 Chalk 的新产品的 IDENTITY 值。所以,在使用 ADO 的 Visual Basic? 应用程序中,可以运行以下语句:
Set oRs = oCn.Execute("SET NOCOUNT ON;INSERT INTO Products _
(ProductName) VALUES ('Chalk');SELECT @@IDENTITY")
lProductID = oRs(0)
此代码告诉 SQL Server 不要返回查询的行计数,然后执行 INSERT 语句,并返回刚刚为这个新行创建的 IDENTITY 值。SET NOCOUNT ON 语句表示返回的记录集有一行和一列,其中包含了这个新的 IDENTITY 值。如果没有此语句,则会首先返回一个空的记录集(因为 INSERT 语句不返回任何数据),然后会返回第二个记录集,第二个记录集中包含 IDENTITY 值。这可能有些令人困惑,尤其是因为您从来就没有希望过 INSERT 会返回记录集。之所以会发生此情况,是因为 SQL Server 看到了这个行计数(即一行受到影响)并将其解释为表示一个记录集。因此,真正的数据被推回到了第二个记录集。当然您可以使用 ADO 中的 NextRecordset 方法获取此第二个记录集,但如果总能够首先返回该记录集且只返回该记录集,则会更方便,也更有效率。
此方法虽然有效,但需要在 SQL 语句中额外添加一些代码。获得相同结果的另一方法是在 INSERT 之前使用 SET NOCOUNT ON 语句,并将 SELECT @@IDENTITY 语句放在表中的 FOR INSERT 触发器中,如下面的代码片段所示。这样,任何进入该表的 INSERT 语句都将自动返回 IDENTITY 值。
CREATE TRIGGER trProducts_Insert ON Products FOR INSERT AS
SELECT @@IDENTITY
GO
触发器只在 Products 表上发生 INSERT 时启动,所以它总是会在成功 INSERT 之后返回一个 IDENTITY。使用此技术,您可以始终以相同的方式在应用程序中检索 IDENTITY 值。
内嵌视图与临时表
某些时候,查询需要将数据与其他一些可能只能通过执行 GROUP BY 然后执行标准查询才能收集的数据进行联接。例如,如果要查询最新五个定单的有关信息,您首先需要知道是哪些定单。这可以使用返回定单 ID 的 SQL 查询来检索。此数据就会存储在临时表(这是一个常用技术)中,然后与 Products 表进行联接,以返回这些定单售出的产品数量:
CREATE TABLE #Temp1 (OrderID INT NOT NULL, _
OrderDate DATETIME NOT NULL)
INSERT INTO #Temp1 (OrderID, OrderDate)
SELECT TOP 5 o.OrderID, o.OrderDate
FROM Orders o ORDER BY o.OrderDate DESC
SELECT p.ProductName, SUM(od.Quantity) AS ProductQuantity
FROM #Temp1 t
INNER JOIN [Order Details] od ON t.OrderID = od.OrderID
INNER JOIN Products p ON od.ProductID = p.ProductID
GROUP BY p.ProductName
ORDER BY p.ProductName
DROP TABLE #Temp1
这些 SQL 语句会创建一个临时表,将数据插入该表中,将其他数据与该表进行联接,然后除去该临时表。这会导致此查询进行大量 I/O 操作,因此,可以重新编写查询,使用内嵌视图取代临时表。内嵌视图只是一个可以联接到 FROM 子句中的查询。所以,您不用在 tempdb 中的临时表上耗费大量 I/O 和磁盘访问,而可以使用内嵌视图得到同样的结果:
SELECT p.ProductName,
SUM(od.Quantity) AS ProductQuantity
FROM (
SELECT TOP 5 o.OrderID, o.OrderDate
FROM Orders o
ORDER BY o.OrderDate DESC
) t
INNER JOIN [Order Details] od ON t.OrderID = od.OrderID
INNER JOIN Products p ON od.ProductID = p.ProductID
GROUP BY
p.ProductName
ORDER BY
p.ProductName
此查询不仅比前面的查询效率更高,而且长度更短。临时表会消耗大量资源。如果只需要将数据联接到其他查询,则可以试试使用内嵌视图,以节省资源。
避免 LEFT JOIN 和 NULL
当然,有很多时候您需要执行 LEFT JOIN 和使用 NULL 值。但是,它们并不适用于所有情况。改变 SQL 查询的构建方式可能会产生将一个花几分钟运行的报告缩短到只花几秒钟这样的天壤之别的效果。有时,必须在查询中调整数据的形态,使之适应应用程序所要求的显示方式。虽然 TABLE 数据类型会减少大量占用资源的情况,但在查询中还有许多区域可以进行优化。SQL 的一个有价值的常用功能是 LEFT JOIN。它可以用于检索第一个表中的所有行、第二个表中所有匹配的行、以及第二个表中与第一个表不匹配的所有行。例如,如果希望返回每个客户及其定单,使用 LEFT JOIN 则可以显示有定单和没有定单的客户。
此工具可能会被过度使用。LEFT JOIN 消耗的资源非常之多,因为它们包含与 NULL(不存在)数据匹配的数据。在某些情况下,这是不可避免的,但是代价可能非常高。LEFT JOIN 比 INNER JOIN 消耗资源更多,所以如果您可以重新编写查询以使得该查询不使用任何 LEFT JOIN,则会得到非常可观的回报(请参阅图 1 中的图)。
图 1 查询
加快使用 LEFT JOIN 的查询速度的一项技术涉及创建一个 TABLE 数据类型,插入第一个表(LEFT JOIN 左侧的表)中的所有行,然后使用第二个表中的值更新 TABLE 数据类型。此技术是一个两步的过程,但与标准的 LEFT JOIN 相比,可以节省大量时间。一个很好的规则是尝试各种不同的技术并记录每种技术所需的时间,直到获得用于您的应用程序的执行性能最佳的查询。
测试查询的速度时,有必要多次运行此查询,然后取一个平均值。因为查询(或存储过程)可能会存储在 SQL Server 内存中的过程缓存中,因此第一次尝试耗费的时间好像稍长一些,而所有后续尝试耗费的时间都较短。另外,运行您的查询时,可能正在针对相同的表运行其他查询。当其他查询锁定和解锁这些表时,可能会导致您的查询要排队等待。例如,如果您进行查询时某人正在更新此表中的数据,则在更新提交时您的查询可能需要耗费更长时间来执行。
避免使用 LEFT JOIN 时速度降低的最简单方法是尽可能多地围绕它们设计数据库。例如,假设某一产品可能具有类别也可能没有类别。如果 Products 表存储了其类别的 ID,而没有用于某个特定产品的类别,则您可以在字段中存储 NULL 值。然后您必须执行 LEFT JOIN 来获取所有产品及其类别。您可以创建一个值为“No Category”的类别,从而指定外键关系不允许 NULL 值。通过执行上述操作,现在您就可以使用 INNER JOIN 检索所有产品及其类别了。虽然这看起来好像是一个带有多余数据的变通方法,但可能是一个很有价值的技术,因为它可以消除 SQL 批处理语句中消耗资源较多的 LEFT JOIN。在数据库中全部使用此概念可以为您节省大量的处理时间。请记住,对于您的用户而言,即使几秒钟的时间也非常重要,因为当您有许多用户正在访问同一个联机数据库应用程序时,这几秒钟实际上的意义会非常重大。
灵活使用笛卡尔乘积
对于此技巧,我将进行非常详细的介绍,并提倡在某些情况下使用笛卡尔乘积。出于某些原因,笛卡尔乘积 (CROSS JOIN) 遭到了很多谴责,开发人员通常会被警告根本就不要使用它们。在许多情况下,它们消耗的资源太多,从而无法高效使用。但是像 SQL 中的任何工具一样,如果正确使用,它们也会很有价值。例如,如果您想运行一个返回每月数据(即使某一特定月份客户没有定单也要返回)的查询,您就可以很方便地使用笛卡尔乘积。 图 2 中的 SQL 就执行了上述操作。
虽然这看起来好像没什么神奇的,但是请考虑一下,如果您从客户到定单(这些定单按月份进行分组并对销售额进行小计)进行了标准的 INNER JOIN,则只会获得客户有定单的月份。因此,对于客户未订购任何产品的月份,您不会获得 0 值。如果您想为每个客户都绘制一个图,以显示每个月和该月销售额,则可能希望此图包括月销售额为 0 的月份,以便直观标识出这些月份。如果使用 图 2 中的 SQL,数据则会跳过销售额为 0 美元的月份,因为在定单表中对于零销售额不会包含任何行(假设您只存储发生的事件)。
图 3 中的代码虽然较长,但是可以达到获取所有销售数据(甚至包括没有销售额的月份)的目标。首先,它会提取去年所有月份的列表,然后将它们放入第一个 TABLE 数据类型表 (@tblMonths) 中。下一步,此代码会获取在该时间段内有销售额的所有客户公司的名称列表,然后将它们放入另一个 TABLE 数据类型表 (@tblCus-tomers) 中。这两个表存储了创建结果集所必需的所有基本数据,但实际销售数量除外。第一个表中列出了所有月份(12 行),第二个表中列出了这个时间段内有销售额的所有客户(对于我是 81 个)。并非每个客户在过去 12 个月中的每个月都购买了产品,所以,执行 INNER JOIN 或 LEFT JOIN 不会返回每个月的每个客户。这些操作只会返回购买产品的客户和月份。
笛卡尔乘积则可以返回所有月份的所有客户。笛卡尔乘积基本上是将第一个表与第二个表相乘,生成一个行集合,其中包含第一个表中的行数与第二个表中的行数相乘的结果。因此,笛卡尔乘积会向表 @tblFinal 返回 972 行。最后的步骤是使用此日期范围内每个客户的月销售额总计更新 @tblFinal 表,以及选择最终的行集。
如果由于笛卡尔乘积占用的资源可能会很多,而不需要真正的笛卡尔乘积,则可以谨慎地使用 CROSS JOIN。例如,如果对产品和类别执行了 CROSS JOIN,然后使用 WHERE 子句、DISTINCT 或 GROUP BY 来筛选出大多数行,那么使用 INNER JOIN 会获得同样的结果,而且效率高得多。如果需要为所有的可能性都返回数据(例如在您希望使用每月销售日期填充一个图表时),则笛卡尔乘积可能会非常有帮助。但是,您不应该将它们用于其他用途,因为在大多数方案中 INNER JOIN 的效率要高得多。
拾遗补零
这里介绍其他一些可帮助提高 SQL 查询效率的常用技术。假设您将按区域对所有销售人员进行分组并将他们的销售额进行小计,但是您只想要那些数据库中标记为处于活动状态的销售人员。您可以按区域对销售人员分组,并使用 HAVING 子句消除那些未处于活动状态的销售人员,也可以在 WHERE 子句中执行此操作。在 WHERE 子句中执行此操作会减少需要分组的行数,所以比在 HAVING 子句中执行此操作效率更高。HAVING 子句中基于行的条件的筛选会强制查询对那些在 WHERE 子句中会被去除的数据进行分组。
另一个提高效率的技巧是使用 DISTINCT 关键字查找数据行的单独报表,来代替使用 GROUP BY 子句。在这种情况下,使用 DISTINCT 关键字的 SQL 效率更高。请在需要计算聚合函数(SUM、COUNT、MAX 等)的情况下再使用 GROUP BY。另外,如果您的查询总是自己返回一个唯一的行,则不要使用 DISTINCT 关键字。在这种情况下,DISTINCT 关键字只会增加系统开销。
您已经看到了,有大量技术都可用于优化查询和实现特定的业务规则,技巧就是进行一些尝试,然后比较它们的性能。最重要的是要测试、测试、再测试。在此专栏的将来各期内容中,我将继续深入讲述 SQL Server 概念,包括数据库设计、好的索引实践以及 SQL Server 安全范例。
如有向 Johnny 提出的问题和建议,请发送电子邮件到 mmdata@microsoft.com
SQL高手篇:精妙SQL语句介绍
文章类别:SQL技巧 发表时间:2004-8-22 19:36:18浏览:522 次
http://www.net118.com/article/2004822193618.htm
说明:复制表(只复制结构,源表名:a 新表名:b)
SQL: select * into b from a where 1<>1
说明:拷贝表(拷贝数据,源表名:a 目标表名:b)
SQL: insert into b(a, b, c) select d,e,f from b;
说明:显示文章、提交人和最后回复时间
SQL: select a.title,a.username,b.adddate from table a,(select max(adddate) adddate from table where table.title=a.title) b
说明:外连接查询(表名1:a 表名2:b)
SQL: select a.a, a.b, a.c, b.c, b.d, b.f from a LEFT OUT JOIN b ON a.a = b.c
说明:日程安排提前五分钟提醒
SQL: select * from 日程安排 where datediff('minute',f开始时间,getdate())>5
说明:两张关联表,删除主表中已经在副表中没有的信息
SQL: delete from info where not exists ( select * from infobz where info.infid=infobz.infid )
说明:--
SQL:
SELECT A.NUM, A.NAME, B.UPD_DATE, B.PREV_UPD_DATE FROM TABLE1,(SELECT X.NUM, X.UPD_DATE, Y.UPD_DATE PREV_UPD_DATE FROM (SELECT NUM, UPD_DATE, INBOUND_QTY, STOCK_ONHAND FROM TABLE2 WHERE TO_CHAR(UPD_DATE,'YYYY/MM') = TO_CHAR(SYSDATE, 'YYYY/MM')) X, (SELECT NUM, UPD_DATE, STOCK_ONHAND FROM TABLE2 WHERE TO_CHAR(UPD_DATE,'YYYY/MM') = TO_CHAR(TO_DATE(TO_CHAR(SYSDATE, 'YYYY/MM') ¦¦ '/01','YYYY/MM/DD') - 1, 'YYYY/MM') ) Y, WHERE X.NUM = Y.NUM (+)AND X.INBOUND_QTY + NVL(Y.STOCK_ONHAND,0) <> X.STOCK_ONHAND ) B WHERE A.NUM = B.NUM
说明:--
SQL: select * from studentinfo where not exists(select * from student where studentinfo.id=student.id) and 系名称='"&strdepartmentname&"' and 专业名称='"&strprofessionname&"' order by 性别,生源地,高考总成绩
说明: 从数据库中去一年的各单位电话费统计(电话费定额贺电化肥清单两个表来源)
SQL:
SELECT a.userper, a.tel, a.standfee, TO_CHAR(a.telfeedate, 'yyyy') AS telyear, SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '01', a.factration)) AS JAN, SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '02', a.factration)) AS FRI, SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '03', a.factration)) AS MAR, SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '04', a.factration)) AS APR, SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '05', a.factration)) AS MAY, SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '06', a.factration)) AS JUE,SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '07', a.factration)) AS JUL, SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '08', a.factration)) AS AGU, SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '09', a.factration)) AS SEP, SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '10', a.factration)) AS OCT, SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '11', a.factration)) AS NOV,SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '12', a.factration)) AS DEC FROM (SELECT a.userper, a.tel, a.standfee, b.telfeedate, b.factration FROM TELFEESTAND a, TELFEE b WHERE a.tel = b.telfax) a GROUP BY a.userper, a.tel, a.standfee, TO_CHAR(a.telfeedate, 'yyyy')
说明:四表联查问题:
SQL: select * from a left inner join b on a.a=b.b right inner join c on a.a=c.c inner join d on a.a=d.d where .....
说明:得到表中最小的未使用的ID号
SQL:
SELECT (CASE WHEN EXISTS(SELECT * FROM Handle b WHERE b.HandleID = 1) THEN MIN(HandleID) + 1 ELSE 1 END) as HandleID FROM Handle WHERE NOT HandleID IN (SELECT a.HandleID - 1 FROM Handle a)
联系客服