0%

mysql多表查询

  • 多表查询与表联结

★第15章 联结表

15.1 联结

  • SQL最强大的功能之一就是能在数据检索查询的执行中联结(join)表。联结是利用SQL的SELECT能执行的最重要的操作,很好地理解联结及其语法是学习SQL的一个极为重要的组成部分。在能够有效地使用联结前,必须了解关系表以及关系数据库设计的一些基础知识。下面的介绍并不是这个内容的全部知识,但作为入门已经足够了。

15.1.1 关系表

  • 假如有一个包含产品目录的数据库表,其中每种类别的物品占一行。对于每种物品要存储的信息包括产品描述和价格,以及生产该产品的供应商信息。

    现在,假如有由同一供应商生产的多种物品,那么在何处存储供应商信息(如,供应商名、地址、联系方法等)呢?将这些数据与产品信息分开存储的理由如下:

    • 因为同一供应商生产的每个产品的供应商信息都是相同的,对每个产品重复此信息既浪费时间又浪费存储空间。
    • 如果供应商信息改变(例如,供应商搬家或电话号码变动),只需改动一次即可。
    • 如果有重复数据(即每种产品都存储供应商信息),很难保证每次输入该数据的方式都相同。不一致的数据在报表中很难利用。
  • 关键是,相同数据出现多次决不是一件好事,此因素是关系数据库设计的基础。关系表的设计就是要保证把信息分解成多个表,一类数据一个表。各表通过某些常用的值(即关系设计中的关系(relational))互相关联。

  • 在这个例子中,可建立两个表,一个存储供应商信息,另一个存储产品信息。

    vendors表包含所有供应商信息,每个供应商占一行,每个供应商具有唯一的标识。此标识称为主键(primary key)(在第1章中首次提到),可以是供应商ID或任何其他唯一值。

    products表只存储产品信息,它除了存储供应商ID(vendors表的主键)外不存储其他供应商信息。vendors表的主键又叫products的 外键 ,它将vendors表与products表关联,利用供应商ID能从vendors表中找出相应供应商的详细信息。

  • 外键(foreign key):外键为某个表中的一列,它包含另一个表的主键值,定义了两个表之间的关系。

  • 关系数据可以有效地存储和方便地处理。因此,关系数据库的可伸缩性远比非关系数据库要好。

    可伸缩性(scale):能够适应不断增加的工作量而不失败。设计良好的数据库或应用程序称之为可伸缩性好(scale well)。

15.1.2 为什么要使用联结

  • 正如所述,分解数据为多个表能更有效地存储,更方便地处理,并且具有更大的可伸缩性。但这些好处是有代价的。如果数据存储在多个表中,怎样用单条SELECT语句检索出数据?——> 答案是使用联结。简单地说,联结是一种机制,用来在一条SELECT语句中关联表,因此称之为联结。使用特殊的语法,可以联结多个表返回一组输出,联结在运行时关联表中正确的行。

  • 维护引用完整性:重要的是,要理解联结不是物理实体。换句话说,它在实际的数据库表中不存在。联结由MySQL根据需要建立,它存在于查询的执行当中。在使用关系表时,仅在关系列中插入合法的数据非常重要。回到这里的例子,如果在products表中插入拥有非法供应商ID(即没有在vendors表中出现)的供应商生产的产品,则这些产品是不可访问的,因为它们没有关联到某个供应商。为防止这种情况发生,可指示MySQL只允许在products表的供应商ID列中出现合法值(即出现在vendors表中的供应商)。这就是维护引用完整性,它是通过在表的定义中指定主键和外键来实现的。(这将在第21章介绍。)

15.2 创建联结

  • 联结的创建非常简单,规定要联结的所有表以及它们如何关联即可。请看下面的例子:

    SELECT vend_name, prod_name, prod_price FROM vendors, products WHERE vendors.vend_id = products.vend_id ORDER BY vend_name, prod_name;

    我们来考察一下此代码。SELECT语句与前面所有语句一样指定要检索的列。这里,最大的差别是所指定的两个列(prod_name和prod_price)在一个表中,而另一个列(vend_name)在另一个表中。

    现在来看FROM子句。与以前的SELECT语句不一样,这条语句的FROM子句列出了两个表,分别是vendors和products。它们就是这条SELECT语句联结的两个表的名字。这两个表用WHERE子句正确联结,WHERE子句指示MySQL匹配vendors表中的vend_id和products表中的vend_id。

    可以看到要匹配的两个列以 vendors.vend_id 和 products. vend_id指定。这里需要这种完全限定列名,因为如果只给出vend_id, 则MySQL不知道指的是哪一个(它们有两个,每个表中一个)。

15.2.1 WHERE子句的重要性

  • 利用WHERE子句建立联结关系似乎有点奇怪,但实际上,有一个很充分的理由。请记住,在一条SELECT语句中联结几个表时,相应的关系是在运行中构造的。在数据库表的定义中不存在能指示MySQL如何对表进行联结的东西。你必须自己做这件事情。在联结两个表时,你实际上做的是将第一个表中的每一行与第二个表中的每一行配对。WHERE子句作为过滤条件,它只包含那些匹配给定条件(这里是联结条件)的行。没有WHERE子句,第一个表中的每个行将与第二个表中的每个行配对,而不管它们逻辑上是否可以配在一起。

  • 笛卡儿积(cartesian product):由没有联结条件的表关系返回的结果为笛卡儿积。检索出的行的数目将是第一个表中的行数乘以第二个表中的行数。

  • 不要忘了WHERE子句:应该保证所有联结都有WHERE子句,否则MySQL将返回比想要的数据多得多的数据。同理,应该保证WHERE子句的正确性。不正确的过滤条件将导致MySQL返回不正确的数据。

★15.2.2 内部联结INNER JOIN

  • 目前为止所用的联结称为等值联结(equijoin),它基于两个表之间的相等测试。这种联结也称为内部联结。其实,对于这种联结可以使用稍微不同的语法来明确指定联结的类型。下面的SELECT语句返回与前面例子完全相同的数据:

    SELECT vend_name, prod_name, prod_price FROM vendors INNER JOIN products ON vendors.vend_id = products.vend_id ORDER BY vend_name, prod_name;

    此语句中的SELECT与前面的SELECT语句相同,但FROM子句不同。这里,两个表之间的关系是FROM子句的组成部分,以INNER JOIN指定。在使用这种语法时,联结条件用特定的ON子句而不是WHERE子句给出。传递给ON的实际条件与传递给WHERE的相同。

15.2.3 联结多个表

  • SQL对一条SELECT语句中可以联结的表的数目没有限制。创建联结的基本规则也相同。首先列出所有表,然后定义表之间的关系。例如:

    SELECT prod_name, vend_name, prod_price, quantity FROM orderitems, products, vendors WHERE products.vend_id = vendors.vend_id AND orderitems.prod_id = products.prod_id AND order_num = 20005;

    此例子显示编号为20005的订单中的物品。订单物品存储在orderitems表中。每个产品按其产品ID存储,它引用products表中的产品。这些产品通过供应商ID联结到vendors表中相应的供应商,供应商ID存储在每个产品的记录中。这里的FROM子句列出了3个表,而WHERE子句定义了这两个联结条件,而第三个联结条件用来过滤出订单20005中的物品。

  • 现在可以回顾一下第14章中的例子了。该例子如下所示,其SELECT语句返回订购产品TNT2的客户列表:

![mysql子查询](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Mysql_notebook/img/mysql子查询.jpg)

  • 正如第14章所述,子查询并不总是执行复杂SELECT操作的最有效的方法,下面是使用联结的相同查询:

![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Mysql_notebook/img/mysql联结多个表.jpg)

正如第14章所述,这个查询中返回数据需要使用3个表。但这里我们没有在嵌套子查询中使用它们,而是使用了两个联结。这里有3个WHERE子句条件。前两个关联联结中的表,后一个过滤产品TNT2的数据。

★第16章 创建高级联结

16.1 使用表别名

  • 第10章中介绍了如何使用别名引用被检索的表列。给列起别名的语法如下:

    SELECT Concat(RTrim(vend_name), ' (', RTrim(vend_country), ')') AS vend_title FROM vendors ORDER BY vend_name;

    别名除了用于列名和计算字段外,SQL还允许给表名起别名。这样做有两个主要理由:

    • 缩短SQL语句;
    • 允许在单条SELECT语句中多次使用相同的表。
  • 请看下面的SELECT语句。它与前一章的例子中所用的语句基本相同,但改成了使用别名:

![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Mysql_notebook/img/mysql高级联结表别名.jpg)

可以看到,FROM子句中3个表全都具有别名。customers AS c建立c作为customers的别名,等等。这使得能使用省写的c而不是全名customers。在此例子中,表别名只用于WHERE子句。但是,表别名不仅能用于WHERE子句,它还可以用于SELECT的列表、ORDER BY子句以及语句的其他部分。

  • 注意:表别名只在查询执行中使用。与列别名不一样,表别名不返回到客户机

16.2 使用不同类型的联结

★16.2.1 自联结

  • 如前所述,使用表别名的主要原因之一是能在单条SELECT语句中不止一次引用相同的表。下面举一个例子。

    假如你发现某物品(其ID为DTNTR)存在问题,因此想知道生产该品的供应商生产的其他物品是否也存在这些问题。此查询要求首先找到生产ID为DTNTR的物品的供应商,然后找出这个供应商生产的其他物品。下面是解决此问题的一种方法:

    • SELECT prod_id, prod_name FROM products WHERE vend_id = (SELECT vend_id FROM products WHERE prod_id = 'DTNTR');

      这是第一种解决方案,它使用了子查询。内部的SELECT语句做了一个简单的检索,返回生产ID为DTNTR的物品供应商的vend_id。该ID用于外部查询的WHERE子句中,以便检索出这个供应商生产的所有物品。

    • 现在来看使用联结的相同查询:

      ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Mysql_notebook/img/mysql高级联结-自联结.jpg)

      此查询中需要的两个表实际上是相同的表,因此products表在FROM子句中出现了两次。虽然这是完全合法的,但对products的引用具有二义性,因为MySQL不知道你引用的是products表中的哪个实例。为解决此问题,使用了表别名。products的第一次出现为别名p1,第二次出现为别名p2。现在可以将这些别名用作表名。例如,SELECT语句使用p1前缀明确地给出所需列的全名。如果不这样,MySQL将返回错误,因为分别存在两个名为prod_id、prod_name的列。MySQL不知道想要的是哪一个列(即使它们事实上是同一个列)。WHERE(通过匹配p1中 的vend_id和p2中的vend_id)首先联结两个表,然后按第二个表中的prod_id过滤数据,返回所需的数据。

  • 用自联结而不用子查询:自联结通常作为外部语句用来替代从相同表中检索数据时使用的子查询语句。虽然最终的结果是相同的,但有时候处理联结远比处理子查询快得多。应该试一下两种方法,以确定哪一种的性能更好。

  • 自联结的特点:

    1. 把同一张表当做两张表使用。
    2. 需要给表取别名:表名 表别名 或者 表名 AS 表别名
    3. 列名如果不明确,可以指定列的别名:列名 列别名 或者 列名 AS 列别名

16.2.2 自然联结

  • 无论何时对表进行联结,应该至少有一个列出现在不止一个表中(被联结的列)。标准的联结(前一章中介绍的内部联结)返回所有数据,甚至相同的列多次出现。自然联结排除多次出现,使每个列只返回一次。

    怎样完成这项工作呢?答案是,系统不完成这项工作,由你自己完成它。自然联结是这样一种联结,其中你只能选择那些唯一的列。这一般是通过对表使用通配符(SELECT *),对所有其他表的列使用明确的子集来完成的。下面举一个例子:

![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Mysql_notebook/img/mysql高级联结-自然联结.jpg)

  • 在这个例子中,通配符只对第一个表使用。所有其他列明确列出,所以没有重复的列被检索出来。事实上,迄今为止我们建立的每个内部联结都是自然联结,很可能我们永远都不会用到不是自然联结的内部联结。

★16.2.3 外部联结OUTER JOIN

  • 许多联结将一个表中的行与另一个表中的行相关联。但有时候会需要包含没有关联行的那些行。例如,可能需要使用联结来完成以下工作:

    • 对每个客户下了多少订单进行计数,包括那些至今尚未下订单的客户;
    • 列出所有产品以及订购数量,包括没有人订购的产品;
    • 计算平均销售规模,包括那些至今尚未下订单的客户。

    在上述例子中,联结包含了那些在相关表中没有关联行的行。这种类型的联结称为外部联结。

    • 外部联结又分为三种:左外连接 LEFT [OUTER] JOIN、右外连接 RIGHT [OUTER] JOIN 和全外连接 FULL OUTER JOIN

      左外连接右外连接完全外连接
      从左侧表中获取所有行从右侧表中获取所有行从两个表中获取所有行
      内连接 + 左表中所有不匹配的行内连接 + 右表中所有不匹配的行内连接 + 左表和右表中所有不匹配的行
      右表不匹配数据丢失左表不匹配数据丢失无数据丢失
  • 下面的SELECT语句给出一个简单的内部联结。它检索所有客户及其订单:

    SELECT customers.cust_id, orders.order_num FROM customers INNER JOIN orders ON customers.cust_id = orders.cust_id;

    外部联结语法类似。为了检索所有客户,包括那些没有订单的客户,可如下进行:

    SELECT customers.cust_id, orders.order_num FROM customers LEFT OUTER JOIN orders ON customers.cust_id = orders.cust_id;

    类似于上一章中所看到的内部联结,这条SELECT语句使用了关键字OUTER JOIN来指定联结的类型(而不是在WHERE子句中指定)。但是,与内部联结关联两个表中的行不同的是,外部联结还包括没有关联行的行。在使用OUTER JOIN语法时,必须使用RIGHT或LEFT关键字指定包括其所有行的表(RIGHT指出的是OUTER JOIN右边的表,而LEFT指出的是OUTER JOIN左边的表)。上面的例子使用LEFT OUTER JOIN从FROM子句的左边表(customers表)中选择所有行。为了从右边的表中选择所有行,应该使用RIGHT OUTER JOIN,如下例所示:

    SELECT customers.cust_id, orders.order_num FROM customers RIGHT OUTER JOIN orders ON orders.cust_id = customers.cust_id;

  • 没有*=操作符:MySQL不支持简化字符 *= 和 =* 的使用,这两种操作符在其他DBMS中是很流行的。

  • 外部联结的类型:存在两种基本的外部联结形式:左外部联结和右外部联结。它们之间的唯一差别是所关联的表的顺序不同。换句话说,左外部联结可通过颠倒FROM或WHERE子句中表的顺序转换为右外部联结。因此,两种类型的外部联结可互换使用,而究竟使用哪一种纯粹是根据方便而定。

16.3 使用带聚集函数的联结

  • 正如第12章所述,聚集函数用来汇总数据。虽然至今为止聚集函数的所有例子只是从单个表汇总数据,但这些函数也可以与联结一起使用。为说明这一点,请看一个例子。如果要检索所有客户及每个客户所下的订单数,下面使用了COUNT()函数的代码可完成此工作:

![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Mysql_notebook/img/mysql高级联结-聚集函数1.jpg)

此SELECT语句使用INNER JOIN将customers和orders表互相关联。GROUP BY 子句按客户分组数据,因此,函数调用 COUNT (orders.order_num)对每个客户的订单计数,将它作为num_ord返回。

  • 聚集函数也可以方便地与其他联结一起使用。请看下面的例子:

![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Mysql_notebook/img/mysql高级联结-聚集函数2.jpg)

这个例子使用左外部联结来包含所有客户,甚至包含那些没有任何下订单的客户。结果显示也包含了客户Mouse House,它有0个订单。

16.4 使用联结和联结条件

  • 在总结关于联结的这两章前,有必要汇总一下关于联结及其使用的某些要点。
    • 注意所使用的联结类型。一般我们使用内部联结,但使用外部联结也是有效的。
    • 保证使用正确的联结条件,否则将返回不正确的数据。
    • 应该总是提供联结条件,否则会得出笛卡儿积。
    • 在一个联结中可以包含多个表,甚至对于每个联结可以采用不同的联结类型。虽然这样做是合法的,一般也很有用,但应该在一起测试它们前,分别测试每个联结。这将使故障排除更为简单。

第17章 组合查询UNION

17.1 组合查询

  • 多数SQL查询都只包含从一个或多个表中返回数据的单条SELECT语句。MySQL也允许执行多个查询(多条SELECT语句),并将结果作为单个查询结果集返回。这些组合查询通常称为并(union)复合查询(compound query)

    有两种基本情况,其中需要使用组合查询:

    • 在单个查询中从不同的表返回类似结构的数据;
    • 对单个表执行多个查询,按单个查询返回数据。

17.2 创建组合查询

  • 可用UNION操作符来组合数条SQL查询。利用UNION,可给出多条SELECT语句,将它们的结果组合成单个结果集。

17.2.1 使用UNION

  • UNION的使用很简单。所需做的只是给出每条SELECT语句,在各条语句之间放上关键字UNION

  • 举一个例子,假如需要价格小于等于5的所有物品的一个列表,而且还想包括供应商1001和1002生产的所有物品(不考虑价格)。当然,可以利用WHERE子句来完成此工作,不过这次我们将使用UNION。

    正如所述,创建UNION涉及编写多条SELECT语句。首先来看单条语句:

    SELECT vend_id, prod_id, prod_price FROM products WHERE prod_price <= 5;

    SELECT vend_id, prod_id, prod_price FROM products WHERE vend_id IN (1001,1002);

    第一条SELECT检索价格不高于5的所有物品。第二条SELECT使用IN找出供应商1001和1002生产的所有物品。

    为了组合这两条语句,按如下进行:

![img\mysql组合查询](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Mysql_notebook/img/mysql组合查询.jpg)

这条语句由前面的两条SELECT语句组成,语句中用UNION关键字分隔。UNION指示MySQL执行两条SELECT语句,并把输出组合成单个查询结果集。

作为参考,这里给出使用多条WHERE子句而不是使用UNION的相同查询:

SELECT vend_id, prod_id, prod_price FROM products WHERE prod_price <=5 OR vend_id IN (1001,1002);

在这个简单的例子中,使用UNION可能比使用WHERE子句更为复杂。但对于更复杂的过滤条件,或者从多个表(而不是单个表)中检索数据的情形,使用UNION可能会使处理更简单。

17.2.2 UNION规则

  • 正如所见,并是非常容易使用的。但在进行并时有几条规则需要注意。
    • UNION必须由两条或两条以上的SELECT语句组成,语句之间用关键字UNION分隔(因此,如果组合4条SELECT语句,将要使用3个UNION关键字)。
    • UNION中的每个查询必须包含相同的列、表达式或聚集函数(不过各个列不需要以相同的次序列出)。
    • 列数据类型必须兼容:类型不必完全相同,但必须是DBMS可以隐含地转换的类型(例如,不同的数值类型或不同的日期类型)。

17.2.3 包含或取消重复的行UNION ALL

  • UNION从查询结果集中自动去除了重复的行(换句话说,它的行为与单条SELECT语句中使用多个WHERE子句条件一样)。因为供应商1002生产的一种物品的价格也低于5,所以两条SELECT语句都返回该行。在使用UNION时,重复的行被自动取消。

    这是UNION的默认行为,但是如果需要,可以改变它。事实上,如果想返回所有匹配行,可使用UNION ALL而不是UNION

![mysql组合查询-UNION_ALL](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Mysql_notebook/img/mysql组合查询-UNION_ALL.jpg)

使用UNION ALL,MySQL不取消重复的行。

  • UNION与WHERE:UNION几乎总是完成与多个WHERE条件相同的工作。UNION ALL为UNION的一种形式,它完成WHERE子句完成不了的工作。如果确实需要每个条件的匹配行全部出现(包括重复行),则必须使用UNION ALL而不是WHERE。

17.2.4 对组合查询结果排序

  • SELECT语句的输出用ORDER BY子句排序。在用UNION组合查询时,只能使用一条ORDER BY子句,它必须出现在最后一条SELECT语句之后。对于结果集,不存在用一种方式排序一部分,而又用另一种方式排序另一部分的情况,因此不允许使用多条ORDER BY子句

![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Mysql_notebook/img/mysql组合查询-排序.jpg)

这条UNION在最后一条SELECT语句后使用了ORDER BY子句。虽然ORDER BY子句似乎只是最后一条SELECT语句的组成部分,但实际上MySQL将用它来排序所有SELECT语句返回的所有结果

---------------The End---------------