3.6 检索记录
除非最终检索它们并利用它们来做点事情,否则将记录放入数据库没什么好处。这就是SELECT 语句的用途,即帮助取出数据。SELECT 大概是SQL 语言中最常用的语句,而且怎样使用它也最为讲究;用它来选择记录可能相当复杂,可能会涉及许多表中列之间的比较。SELECT 语句的语法如下:
除了词“ S E L E C T”和说明希望检索什么的column_list 部分外,语法中的每样东西都是可选的。有的数据库还需要FROM 子句。MySQL 有所不同,它允许对表达式求值而不引用任何表:
在第1章中,我们对SELECT 语句下了很大的功夫,主要集中介绍了列选择的列表和WHERE、GROUP BY、ORDER BY、H AVING 以及LIMIT 子句。本章中,我们将主要精力放在SELECT 语句中最可能令人搞不清的方面,即连接( j o i n)上。我们将介绍MySQL 支持的连接类型、它们的含义、怎样指定它们等。这样做将有助于更有效地使用M y S Q L,因为在许多情况下,解决怎样编写查询的关键是确定怎样将表恰当地连接在一起。还应该参阅一下本章后面3 . 8节“解决方案随笔”。在那一节中将会找到解决几个SQL 问题的方案,它们多数
都涉及SELECT 语句这样或那样的功能。
使用SELECT 的一个问题是,在第一次遇到一种新的问题时,并不总是能够知道怎样编写SELECT 查询来解决它。但在解决以后,再遇到类似的问题时,可利用其中的经验。SELECT 大概是过去的经验在能够有效地使用中起很大作用的语句,这是因为使用它的方法太多的原故。
在有了一定的经验后,可将这些经验用于新问题,您会发现自己思考问题类似于,“噢,是的,它就是一个LEFT JOIN 问题。”或者,“啊哈,这就是一个受各对索引列制约的三路线连接。”(指出这一点,实际上我也感到有点不愿意。听到经验有帮助,您可能受到一定的鼓舞。另外,考虑到您最终能那样思考问题也会令自己有点惊讶。)下几节中介绍怎样利用MySQL 支持的连接操作的格式,多数例子使用了下面的两个表。它们很小,很简单,足以很清楚地看出每种连接的效果。
3.6.1 平凡连接
最简单的连接是平凡连接( trivial join),这种连接中只指定一个表。在此情况下,行从指定的表中选择。如:
有的作者根本就不考虑这种SELECT 连接的形式,仅对从两个或多个表中检索记录的SELECT 语句使用“连接”这个术语。本人认为那只是看法不同而已。
3.6.2 全连接
如果指定多个表,将各个表名用逗号分隔,就指定了全连接。例如,如果连接两个表,来自第一个表中的每行与第二个表中每行进行组合:
全连接也称为叉连接,因为每个表的每行都与其他表中的每行交叉以产生所有可能的组合。这也就是所谓的笛卡儿积。这样连接表潜在地产生数量非常大的行,因为可能得到的行数为每个表中行数之积。三个分别含有1 0 0、2 0 0、3 0 0行的表的全连接将产生1 0 0×2 0 0×3 0 0= 6百万行。即使各表很小,所得到的行数也会很大。在这样的情形下,通常要使用W H E R E
子句来将结果集减少为易于管理的大小。
如果在WHERE 子句中增加一个条件使各表在某些列上进行匹配,此连接就是所谓的等同连接(e q u i - j o i n),因为只选择那些在指定列中具有相等的值的行。如:
J O I N、CROSS JOIN 和INNER JOIN 连接类型都与“,”连接操作符意义相同。STRAIGHT_JOIN 与全连接类似,但各表按FROM 子句中指定的次序进行连接。一般情况下,在全连接中MySQL 优化程序自身完全不考虑安排各表的顺序,以便使记录的检索更快。在有的场合,优化程序将作出非优化的选择,这样将忽略STRAIGHT_JOIN 关键字。在SELECT 语句中,可在两个位置给出S T R A I G H T _ J O I N。一个位置是在SELECT 关键字与选择列表之间,将其放在这里对语句中所有全连接具有整体作用。另一个在FROM 子句中。下面两条语句是等价的:
限定列引用
SELECT 语句中列的引用必须对FROM 子句中指定的每个表是无歧义的。如果FROM 子句中仅指定了一个表,则无歧义存在,因为所有列必须是该表的列。如果指定了多个表,只出现在一个表中的列名也是无歧义的。但是,如果某个列名出现在多个表中,该列的引用必须用表名来限定,用tbl_name.col_name 语法来表明所指的是哪个表。如果表my_tbl1 含有列a 和b,表my_tbl2 含有列b 和c,则列a 和c 的引用是无歧义的,但b 的引用必须限定为my_tbl1.b 或m y _ t b l 2 . b,如:
有时,表名限定符还不能解决列的引用问题。例如,如果在一个查询中多次使用一个表,用表名限定列名没有什么用处。在此情况下,为表达您的想法可使用别名。给表指派一个别名,利用这个别名来引用列,其语法为: alias _ name.col _ name。下面的查询将表与
自身进行连接,给表指派了一个别名,以便应付引用列时有歧义的情况:
3.6.3 左连接
等价连接只给出两个表匹配的行。左连接也给出匹配行,但它还显示左边表中有的但在右边表中无匹配的行。对于这样的行,从右边表中选择的列都显示为N U L L。这样,每一行都从左边表中选出。如果右边表中有一个匹配行,则该行被选中。如果不匹配,行仍然被选中,但它是一个“假”行,其中所有列被设置为N U L L。换句话说,LEFT JOIN 强制结果集包含对应左边表中每一行的行,而不管左边表中的行在右边表中是否有匹配的行。匹配是根据ON 或USING( ) 子句中给出的列进行的。不管所连接的列是否具有相同的名称,都可使用ON。如:
USING( ) 子句类似于O N,但连接列的名称必须在每个表中是相同的。下面的查询将my_tbl1.b 连接到m y _ t b l 2 . b:
在希望只查找出现在左边表而不出现在右边表中的行时, LEFT JOIN 极为有用。可通过增加一条查询右边表中具有NULL 值的列的WHERE 子句来完成这项工作。
一般不用担心选择为NULL 的列,因为没有什么意思。真正要关心的是左边表中不匹配的列,如:
利用LEFT JOIN 时有一件事情需要提防,如果所连接的列未定义为NOT NULL,将会在结果中得出一些无关的行。
LEFT JOIN 有几个同义词和变种。LEFT OUTER JOIN 为LEFT JOIN 的一个同义词。
LEFT JOIN 还有一个为MySQL 所接受的ODBC 表示如下(“o j”意为“outer join”):
N ATURAL LEFT JOIN 类似于LEFT JOIN;它执行一个LEFT JOIN,匹配左边表和右边表中具有相同名称的所有列。
有的数据库还有,RIGHT JOIN,但MySQL 迄今还没有。