联结表
Posted 霖行
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了联结表相关的知识,希望对你有一定的参考价值。
联结表
本章将介绍什么是联结,为什么要使用联结,如何编写使用联结的SELECT语句。
联结
SQL最强大的功能之一就是能在数据检索查询的执行中联结(join)表。
在能够有效地使用联结前,必须了解关系表以及关系数据库设计的一些基础知识。
关系表
假如有一个包含产品目录的数据库表,其中每种类别的物品占一行。要存储的信息包括产品描述、价格以及供应商的信息。
我们将供应商信息(供应商名、地址、联系方式)另外存储,原因:
- 同一个供应商生产的每个产品的供应商信息都是相同的,重复存储此信息既浪费时间又浪费存储空间。
- 如果供应商信息改变,只需改动一次供应商表。
- 如果有重复数据(每种产品都存储供应商信息),很难保证每次输入该数据的方式都相同。不一致的数据在报表中很难利用。
关系表的设计就是要保证把信息分解成多个表。各表通过某些常用的值(即关系设计中的关系(relational))互相关联。
我们可以建立两个表,一个存储供应商信息,另一个存储产品信息。vendors表包含所有供应商信息,每个供应商占一行,每个供应商具有唯一的标识。此标识称为主键(primary key),可以是供应商ID或任何其他唯一值。
products表只存储产品信息,它除了存储供应商ID(vendors表的主键)外不存储其他供应商信息。vendors表的主键又叫作products的外键,它将vendors表与products表关联。
外键(foreign key) 外键为某个表中的一列,它包含另一个表的主键值,定义了两个表之间的关系。
建两个表的好处:
- 供应商信息不重复,从而不浪费时间和空间。
- 如果供应商信息变动,可以只更新vendors表中的单个记录,相关表中的数据不用改动。
- 由于数据无重复,显然数据是一致的,这使得处理数据更简单。
关系数据可以有效地存储和方便地处理。因此,关系数据库的可伸缩性比非关系数据库好。
可伸缩性(scale) 能够适应不断增加的工作量而不失败。设计良好的数据库或应用程序称之为可伸缩性好(scale well)。
为什么要使用联结
分解数据为多个表能更有效地存储,更方便的处理,并且具有更大的可伸缩性。
但数据存储在多个表中,很难用单条SELECT语句检索出数据。这时我们可以使用联结。
联结是一种机制,用来在一条SELECT语句中关联表。使用特殊的语法,可以联结多个表返回同一组输出,联结在运行时关联表中正确的行。
- 维护引用完整性 联结不是物理实体,它在实际的数据库表中不存在。联结由mysql根据需要建立,它存在于查询的执行中。
在使用关系表时,仅在关系列中插入合法的数据非常重要。错误的数据将导致行不可访问。
为防止错误数据,可指示MySQL只允许products表的供应商ID中出现已存在值(即出现在vendors表中的供应商)。这就是维护引用完整性,它通过表中定义主键和外键来实现。
创建联结
联结创建只需规定要联结的所有表以及它们如何关联即可:
mysql> SELECT vend_name, prod_name, prod_price
-> FROM vendors, products
-> WHERE vendors.vend_id = products.vend_id
-> ORDER BY vend_name, prod_name;
+-------------+----------------+------------+
| vend_name | prod_name | prod_price |
+-------------+----------------+------------+
| ACME | Bird seed | 10.00 |
| ACME | Carrots | 2.50 |
| ACME | Detonator | 13.00 |
| ACME | Safe | 50.00 |
| ACME | Sling | 4.49 |
| ACME | TNT (1 stick) | 2.50 |
| ACME | TNT (5 sticks) | 10.00 |
| Anvils R Us | .5 ton anvil | 5.99 |
| Anvils R Us | 1 ton anvil | 9.99 |
| Anvils R Us | 2 ton anvil | 14.99 |
| Jet Set | JetPack 1000 | 35.00 |
| Jet Set | JetPack 2000 | 55.00 |
| LT Supplies | Fuses | 3.42 |
| LT Supplies | Oil can | 8.99 |
+-------------+----------------+------------+
14 rows in set (0.02 sec)
此语句特殊在于,所指定的两个列(prod_name和prod_price)在一个表中,而另一个列(vend_name)在另一个表中。
这条语句的FROM子句列出来两个表,vendors和products。它们就是联结的两个表的名字。这两个表用WHERE子句正确联结,指示MySQL匹配vendors表中的vend_id和products表中的vend_id。
要匹配的列用完全限定列名vendors.vend_id和products.vend_id指定。如果只给出vend_id,MySQL不知道指哪一个(每个表都有一个vend_id)。
- 完全限定列名 在引用的列可能出现二义性时,必须使用完全限定列名(用一个点分隔的表名和列名)。
WHERE子句的重要性
利用WHERE子句建立联结关系,原因在于,在一条SELECT语句中联结几个表时,相应的关系是在运行中构造的。
WHERE子句作为过滤条件,它只包含那些匹配给定条件(联结条件)的行。没有WHERE子句,第一个表中的每个行与第二个表中的每个行配对,而不管逻辑上是否正确。
- 笛卡儿积(cartesian product) 由没有联结条件的表关系返回的结果为笛卡儿积。
vendors表与products表的笛卡儿积:
mysql> SELECT vend_name, prod_name, prod_price
-> FROM vendors, products
-> ORDER BY vend_name, prod_name;
+----------------+----------------+------------+
| vend_name | prod_name | prod_price |
+----------------+----------------+------------+
| ACME | .5 ton anvil | 5.99 |
| ACME | 1 ton anvil | 9.99 |
| ACME | 2 ton anvil | 14.99 |
| ACME | Bird seed | 10.00 |
| ACME | Carrots | 2.50 |
| ACME | Detonator | 13.00 |
| ACME | Fuses | 3.42 |
| ACME | JetPack 1000 | 35.00 |
| ACME | JetPack 2000 | 55.00 |
| ACME | Oil can | 8.99 |
| ACME | Safe | 50.00 |
| ACME | Sling | 4.49 |
| ACME | TNT (1 stick) | 2.50 |
| ACME | TNT (5 sticks) | 10.00 |
| Anvils R Us | .5 ton anvil | 5.99 |
| Anvils R Us | 1 ton anvil | 9.99 |
| Anvils R Us | 2 ton anvil | 14.99 |
| Anvils R Us | Bird seed | 10.00 |
| Anvils R Us | Carrots | 2.50 |
| Anvils R Us | Detonator | 13.00 |
| Anvils R Us | Fuses | 3.42 |
| Anvils R Us | JetPack 1000 | 35.00 |
| Anvils R Us | JetPack 2000 | 55.00 |
| Anvils R Us | Oil can | 8.99 |
| Anvils R Us | Safe | 50.00 |
| Anvils R Us | Sling | 4.49 |
| Anvils R Us | TNT (1 stick) | 2.50 |
| Anvils R Us | TNT (5 sticks) | 10.00 |
| Furball Inc. | .5 ton anvil | 5.99 |
| Furball Inc. | 1 ton anvil | 9.99 |
| Furball Inc. | 2 ton anvil | 14.99 |
| Furball Inc. | Bird seed | 10.00 |
| Furball Inc. | Carrots | 2.50 |
| Furball Inc. | Detonator | 13.00 |
| Furball Inc. | Fuses | 3.42 |
| Furball Inc. | JetPack 1000 | 35.00 |
| Furball Inc. | JetPack 2000 | 55.00 |
| Furball Inc. | Oil can | 8.99 |
| Furball Inc. | Safe | 50.00 |
| Furball Inc. | Sling | 4.49 |
| Furball Inc. | TNT (1 stick) | 2.50 |
| Furball Inc. | TNT (5 sticks) | 10.00 |
| Jet Set | .5 ton anvil | 5.99 |
| Jet Set | 1 ton anvil | 9.99 |
| Jet Set | 2 ton anvil | 14.99 |
| Jet Set | Bird seed | 10.00 |
| Jet Set | Carrots | 2.50 |
| Jet Set | Detonator | 13.00 |
| Jet Set | Fuses | 3.42 |
| Jet Set | JetPack 1000 | 35.00 |
| Jet Set | JetPack 2000 | 55.00 |
| Jet Set | Oil can | 8.99 |
| Jet Set | Safe | 50.00 |
| Jet Set | Sling | 4.49 |
| Jet Set | TNT (1 stick) | 2.50 |
| Jet Set | TNT (5 sticks) | 10.00 |
| Jouets Et Ours | .5 ton anvil | 5.99 |
| Jouets Et Ours | 1 ton anvil | 9.99 |
| Jouets Et Ours | 2 ton anvil | 14.99 |
| Jouets Et Ours | Bird seed | 10.00 |
| Jouets Et Ours | Carrots | 2.50 |
| Jouets Et Ours | Detonator | 13.00 |
| Jouets Et Ours | Fuses | 3.42 |
| Jouets Et Ours | JetPack 1000 | 35.00 |
| Jouets Et Ours | JetPack 2000 | 55.00 |
| Jouets Et Ours | Oil can | 8.99 |
| Jouets Et Ours | Safe | 50.00 |
| Jouets Et Ours | Sling | 4.49 |
| Jouets Et Ours | TNT (1 stick) | 2.50 |
| Jouets Et Ours | TNT (5 sticks) | 10.00 |
| LT Supplies | .5 ton anvil | 5.99 |
| LT Supplies | 1 ton anvil | 9.99 |
| LT Supplies | 2 ton anvil | 14.99 |
| LT Supplies | Bird seed | 10.00 |
| LT Supplies | Carrots | 2.50 |
| LT Supplies | Detonator | 13.00 |
| LT Supplies | Fuses | 3.42 |
| LT Supplies | JetPack 1000 | 35.00 |
| LT Supplies | JetPack 2000 | 55.00 |
| LT Supplies | Oil can | 8.99 |
| LT Supplies | Safe | 50.00 |
| LT Supplies | Sling | 4.49 |
| LT Supplies | TNT (1 stick) | 2.50 |
| LT Supplies | TNT (5 sticks) | 10.00 |
+----------------+----------------+------------+
84 rows in set (0.00 sec)
笛卡儿积不是我们想要的。返回的每个供应商匹配了每个商品,无论是否是正确的匹配。
- 不要忘了WHERE子句 应该保证所有联结都有WHERE子句,否则MySQL将返回比想要数据多得多的数据。
- 叉联结 有时我们会听到返回称为叉联结(cross join)的笛卡儿积的联结类型。
内部联结
前面所学的联结称为等值联结(equijoin),它基于两个表之间的相等测试。这也被称为內部联结。而内部联结有不同的语法:
mysql> SELECT vend_name, prod_name, prod_price
-> FROM vendors INNER JOIN products
-> ON vendors.vend_id = products.vend_id;
+-------------+----------------+------------+
| vend_name | prod_name | prod_price |
+-------------+----------------+------------+
| Anvils R Us | .5 ton anvil | 5.99 |
| Anvils R Us | 1 ton anvil | 9.99 |
| Anvils R Us | 2 ton anvil | 14.99 |
| LT Supplies | Fuses | 3.42 |
| LT Supplies | Oil can | 8.99 |
| ACME | Detonator | 13.00 |
| ACME | Bird seed | 10.00 |
| ACME | Carrots | 2.50 |
| ACME | Safe | 50.00 |
| ACME | Sling | 4.49 |
| ACME | TNT (1 stick) | 2.50 |
| ACME | TNT (5 sticks) | 10.00 |
| Jet Set | JetPack 1000 | 35.00 |
| Jet Set | JetPack 2000 | 55.00 |
+-------------+----------------+------------+
14 rows in set (0.01 sec)
此SELECT语句与前文功能一致,但FROM子句不同。FROM子句的组成是两个表的关系,以INNER JOIN指定。联结条件用ON子句而不是WHERE给出。
- 使用哪种语法 ANSI SQL规范首选INNER JOIN语法。
联结多个表
SQL对一条SELECT语句中可以联结的表的数目没有限制。创建联结的基本规则也相同。首先列出所有表,然后定义表之间的关系:
mysql> 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;
+----------------+-------------+------------+----------+
| prod_name | vend_name | prod_price | quantity |
+----------------+-------------+------------+----------+
| .5 ton anvil | Anvils R Us | 5.99 | 10 |
| 1 ton anvil | Anvils R Us | 9.99 | 3 |
| TNT (5 sticks) | ACME | 10.00 | 5 |
| Bird seed | ACME | 10.00 | 1 |
+----------------+-------------+------------+----------+
4 rows in set (0.01 sec)
此例子显示编号为20005的订单中的物品。订单通过供应商ID联结到vendors表中的供应商,通过产品ID联结到products表中的产品。
- 性能考虑 MySQL在运行时关联指定的每个表以处理联结。这种处理可能非常耗费资源,因此,不用联结不必要的表。
回顾十四章(MySQL必知必会——第十四章使用子查询)的例子,返回订购产品TNT2的客户列表:
mysql> SELECT cust_name, cust_contact
-> FROM customers
-> WHERE cust_id IN (SELECT cust_id
-> FROM orders
-> WHERE order_num IN (SELECT order_num
-> FROM orderitems
-> WHERE prod_id = 'TNT2'));
+----------------+--------------+
| cust_name | cust_contact |
+----------------+--------------+
| Coyote Inc. | Y Lee |
| Yosemite Place | Y Sam |
+----------------+--------------+
2 rows in set (0.01 sec)
子查询不总是执行复杂SELECT操作的最有效的方法,有时联结可能更加合适:
mysql> SELECT cust_name, cust_contact
-> FROM customers, orders, orderitems
-> WHERE customers.cust_id = orders.cust_id
-> AND orderitems.order_num = orders.order_num
-> AND prod_id = 'TNT2';
+----------------+--------------+
| cust_name | cust_contact |
+----------------+--------------+
| Coyote Inc. | Y Lee |
| Yosemite Place | Y Sam |
+----------------+--------------+
2 rows in set (0.01 sec)
- 多做实验 为执行任一给定的SQL操作,一般存在不止一种方法。很少有绝对正确或绝对错误的方法。性能可能会受操作类型、表中数据量、是否存在索引或键以及其他的一些条件的影响。因此,我们有必要对不同的选择机制进行实验,以找出最适合具体情况的方法。
以上是关于联结表的主要内容,如果未能解决你的问题,请参考以下文章