SQL最强大的功能之一就是能在数据检索查询的执行中联结(join) 表。联结是利用SQL的SELECT能执行的最重要的操作,很好地理解联结 及其语法是学习SQL的一个极为重要的组成部分。
在能够有效地使用联结前,必须了解关系表以及关系数据库设计的 一些基础知识。下面的介绍并不是这个内容的全部知识,但作为入门已 经足够了。
理解关系表的最好方法是来看一个现实世界中的例子。
假如有一个包含产品目录的数据库表,其中每种类别的物品占一行。 对于每种物品要存储的信息包括产品描述和价格,以及生产该产品的供应商信息。
现在,假如有由同一供应商生产的多种物品,那么在何处存储供应 商信息(如,供应商名、地址、联系方法等)呢?将这些数据与产品信 息分开存储的理由如下。
因为同一供应商生产的每个产品的供应商信息都是相同的,对每 个产品重复此信息既浪费时间又浪费存储空间。
如果供应商信息改变(例如,供应商搬家或电话号码变动),只需 改动一次即可。
如果有重复数据(即每种产品都存储供应商信息),很难保证每次 输入该数据的方式都相同。不一致的数据在报表中很难利用。
关键是,相同数据出现多次决不是一件好事,此因素是关系数据库 设计的基础。关系表的设计就是要保证把信息分解成多个表,一类数据 一个表。各表通过某些常用的值(即关系设计中的关系(relational))互 相关联。
在这个例子中,可建立两个表,一个存储供应商信息,另一个存储 产品信息。vendors表包含所有供应商信息,每个供应商占一行,每个供 应商具有唯一的标识。此标识称为主键(primary key)(在第1章中首次 提到),可以是供应商ID或任何其他唯一值。
products表只存储产品信息,它除了存储供应商ID(vendors表的主 键)外不存储其他供应商信息。vendors表的主键又叫作products的外键, 它将vendors表与products表关联,利用供应商ID能从vendors表中找出 相应供应商的详细信息。
外键(foreignkey) 外键为某个表中的一列,它包含另一个表 的主键值,定义了两个表之间的关系。
这样做的好处如下:
供应商信息不重复,从而不浪费时间和空间;
如果供应商信息变动,可以只更新vendors表中的单个记录,相
关表中的数据不用改动;
由于数据无重复,显然数据是一致的,这使得处理数据更简单。
总之,关系数据可以有效地存储和方便地处理。因此,关系数据库 的可伸缩性远比非关系数据库要好。
可伸缩性(scale) 能够适应不断增加的工作量而不失败。设 计良好的数据库或应用程序称之为可伸缩性好(scale well)。
正如所述,分解数据为多个表能更有效地存储,更方便地处理,并
且具有更大的可伸缩性。但这些好处是有代价的。
如果数据存储在多个表中,怎样用单条SELECT语句检索出数据?
答案是使用联结。简单地说,联结是一种机制,用来在一条SELECT 语句中关联表,因此称之为联结。使用特殊的语法,可以联结多个表返 回一组输出,联结在运行时关联表中正确的行。
联结的创建非常简单,规定要联结的所有表以及它们如何关联即可。 请看下面的例子:
利用WHERE子句建立联结关系似乎有点奇怪,但实际上,有一个很充 分的理由。请记住,在一条SELECT语句中联结几个表时,相应的关系是 在运行中构造的。在数据库表的定义中不存在能指示MySQL如何对表进 行联结的东西。你必须自己做这件事情。在联结两个表时,你实际上做 的是将第一个表中的每一行与第二个表中的每一行配对。WHERE子句作为 过滤条件,它只包含那些匹配给定条件(这里是联结条件)的行。没有WHERE子句,第一个表中的每个行将与第二个表中的每个行配对,而不管它们逻辑上是否可以配在一起。
笛卡儿积(cartesian product) 由没有联结条件的表关系返回的结果为笛卡儿积。检索出的行的数目将是第一个表中的行数乘 以第二个表中的行数。
本章将讲解另外一些联结类型(包括它们的含义和使用方法),介 绍如何对被联结的表使用表别名和聚集函数。
第10章中介绍了如何使用别名引用被检索的表列。给列起别名的语
法如下:
迄今为止,我们使用的只是称为内部联结或等值联结(equijoin)的简
单联结。现在来看3种其他联结,它们分别是自联结、自然联结和外部联结。
如前所述,使用表别名的主要原因之一是能在单条SELECT语句中不 止一次引用相同的表。下面举一个例子。
假如你发现某物品(其ID为DTNTR)存在问题,因此想知道生产该物 品的供应商生产的其他物品是否也存在这些问题。此查询要求首先找到 生产ID为DTNTR的物品的供应商,然后找出这个供应商生产的其他物品。 下面是解决此问题的一种方法:
为解决此问题,使用了表别名。products的第一次出现为别名p1, 第二次出现为别名p2。现在可以将这些别名用作表名。例如,SELECT语 句使用p1前缀明确地给出所需列的全名。如果不这样,MySQL将返回错 误,因为分别存在两个名为prod_id、prod_name的列。MySQL不知道想 要的是哪一个列(即使它们事实上是同一个列)。WHERE(通过匹配p1中 的vend_id和p2中的vend_id)首先联结两个表,然后按第二个表中的 prod_id过滤数据,返回所需的数据。
无论何时对表进行联结,应该至少有一个列出现在不止一个表中(被联结的列)。标准的联结(前一章中介绍的内部联结)返回所有数据,甚 至相同的列多次出现。自然联结排除多次出现,使每个列只返回一次。
怎样完成这项工作呢?答案是,系统不完成这项工作,由你自己完成它。自然联结是这样一种联结,其中你只能选择那些唯一的列。这一 般是通过对表使用通配符(SELECT *),对所有其他表的列使用明确的子 集来完成的。下面举一个例子:
事实上,迄今为止我们建立的每个内部联结都是自然联结,很可能 我们永远都不会用到不是自然联结的内部联结。
许多联结将一个表中的行与另一个表中的行相关联。但有时候会需
要包含没有关联行的那些行。例如,可能需要使用联结来完成以下工作:
对每个客户下了多少订单进行计数,包括那些至今尚未下订单的 客户;
列出所有产品以及订购数量,包括没有人订购的产品;
计算平均销售规模,包括那些至今尚未下订单的客户。
在上述例子中,联结包含了那些在相关表中没有关联行的行。这种 类型的联结称为外部联结。
下面的SELECT语句给出一个简单的内部联结。它检索所有客户及其订单:
正如第12章所述,聚集函数用来汇总数据。虽然至今为止聚集函数
的所有例子只是从单个表汇总数据,但这些函数也可以与联结一起使用。
为说明这一点,请看一个例子。如果要检索所有客户及每个客户所
下的订单数,下面使用了COUNT()函数的代码可完成此工作:
在总结关于联结的这两章前,有必要汇总一下关于联结及其使用的
某些要点。
注意所使用的联结类型。一般我们使用内部联结,但使用外部联 结也是有效的。
保证使用正确的联结条件,否则将返回不正确的数据。
应该总是提供联结条件,否则会得出笛卡儿积。
在一个联结中可以包含多个表,甚至对于每个联结可以采用不同的联结类型。虽然这样做是合法的,一般也很有用,但应该在一 起测试它们前,分别测试每个联结。这将使故障排除更为简单。
本章讲述如何利用UNION操作符将多条SELECT语句组合成一个结果集。
多数SQL查询都只包含从一个或多个表中返回数据的单条SELECT语句。MySQL也允许执行多个查询(多条SELECT语句),并将结果作为单个 查询结果集返回。这些组合查询通常称为并(union)或复合查询(compound query)。
有两种基本情况,其中需要使用组合查询:
在单个查询中从不同的表返回类似结构的数据;
可用UNION操作符来组合数条SQL查询。利用UNION,可给出多条SELECT语句,将它们的结果组合成单个结果集。
UNION的使用很简单。所需做的只是给出每条SELECT语句,在各条语 句之间放上关键字UNION。
举一个例子,假如需要价格小于等于5的所有物品的一个列表,而且 还想包括供应商1001和1002生产的所有物品(不考虑价格)。当然,可以 利用WHERE子句来完成此工作,不过这次我们将使用UNION。
正如所述,创建UNION涉及编写多条SELECT语句。首先来看单条语句:
正如所见,并是非常容易使用的。但在进行并时有几条规则需要注意。
UNION必须由两条或两条以上的SELECT语句组成,语句之间用关 键字UNION分隔(因此,如果组合4条SELECT语句,将要使用3个 UNION关键字)。
UNION中的每个查询必须包含相同的列、表达式或聚集函数(不过各个列不需要以相同的次序列出)。
列数据类型必须兼容:类型不必完全相同,但必须是DBMS可以
隐含地转换的类型(例如,不同的数值类型或不同的日期类型)。 如果遵守了这些基本规则或限制,则可以将并用于任何数据检索任务。
请返回到17.2.1节,考察一下所用的样例SELECT语句。我们注意到, 在分别执行时,第一条SELECT语句返回4行,第二条SELECT语句返回5行。 但在用UNION组合两条SELECT语句后,只返回了8行而不是9行。
UNION从查询结果集中自动去除了重复的行(换句话说,它的行为与单条SELECT语句中使用多个WHERE子句条件一样)。因为供应商1002生产的一种物品的价格也低于5,所以两条SELECT语句都返回该行。在使用 UNION时,重复的行被自动取消。
这是UNION的默认行为,但是如果需要,可以改变它。事实上,如果想返回所有匹配行,可使用UNION ALL而不是UNION。
请看下面的例子:
SELECT语句的输出用ORDER BY子句排序。在用UNION组合查询时,只 能使用一条ORDER BY子句,它必须出现在最后一条SELECT语句之后。对 于结果集,不存在用一种方式排序一部分,而又用另一种方式排序另一 部分的情况,因此不允许使用多条ORDER BY子句。
为了进行全文本搜索,必须索引被搜索的列,而且要随着数据的改 变不断地重新索引。在对表列进行适当设计后,MySQL会自动进行所有 的索引和重新索引。
在索引之后,SELECT可与Match()和Against()一起使用以实际执行搜索。
一般在创建表时启用全文本搜索。CREATE TABLE语句(第21章中介
绍)接受FULLTEXT子句,它给出被索引列的一个逗号分隔的列表。 下面的CREATE语句演示了FULLTEXT子句的使用:
在索引之后,使用两个函数Match()和Against()执行全文本搜索,
其中Match()指定被搜索的列,Against()指定要使用的搜索表达式。
查询扩展用来设法放宽所返回的全文本搜索结果的范围。考虑下面 的情况。你想找出所有提到anvils的注释。只有一个注释包含词anvils, 但你还想找出可能与你的搜索有关的所有其他行,即使它们不包含词anvils。
MySQL支持全文本搜索的另外一种形式,称为布尔方式(boolean
mode)。以布尔方式,可以提供关于如下内容的细节:
要匹配的词;
要排斥的词(如果某行包含这个词,则不返回该行,即使它包含
其他指定的词也是如此);
排列提示(指定某些词比其他词更重要,更重要的词等级更高); 表达式分组;
另外一些内容。
排列而不排序 在布尔方式中,不按等级值降序排序返回的行。
在结束本章之前,给出关于全文本搜索的某些重要的说明。
在索引全文本数据时,短词被忽略且从索引中排除。短词定义为 那些具有3个或3个以下字符的词(如果需要,这个数目可以更改)。
MySQL带有一个内建的非用词(stopword)列表,这些词在索引
全文本数据时总是被忽略。如果需要,可以覆盖这个列表(请参
阅MySQL文档以了解如何完成此工作)。
许多词出现的频率很高,搜索它们没有用处(返回太多的结果)。
因此,MySQL规定了一条50%规则,如果一个词出现在50%以上 的行中,则将它作为一个非用词忽略。50%规则不用于IN BOOLEAN MODE。
如果表中的行数少于3行,则全文本搜索不返回结果(因为每个词 或者不出现,或者至少出现在50%的行中)。
忽略词中的单引号。例如,don’t索引为dont。
不具有词分隔符(包括日语和汉语)的语言不能恰当地返回全文
本搜索结果。
如前所述,仅在MyISAM数据库引擎中支持全文本搜索。