SQL最强大的功能之一就是在数据检索查询的执行中联结(join)表。联结是利用SQL的SELECT能执行的最重要的操作。
假如有一个包含产品目录的数据库表,其中每种类别的物品占一行。对于每种木品要存储的信息包括产品描述和价格,以及生产该产品的供应商消息。
现在,假如有由同一供应商生产的多种物品,那么何处存储供应商信息(如,供应商名、地址、联系方法等)呢?将这些数据与产品信息分开存储的理由如下。
关系数据可以有效地存储和方便地处理。因此,关系数据库的可伸缩性远比非关系数据库要好。
联结是一种机制,用来在一条SELECT
语句中关联表,因此称之为联结。
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.00 sec)
从FROM
子句中可以看出,与以前的SELECT
语句不一样,这条语句的FROM
子句列出了两个表,分别是vendors和products。它们就是这条SELECT
语句联结的两个表的名字,这两个表用WHERE
子句正确联结。
目前为止所有的联结称为等值联结,它基于两个表之间的相等测试。这种联结称为内部联结。其实,对于这种联结可以使用稍微不同的语法来明确指定联结的类型。
mysql> SELECT prod_name, vend_name, prod_price FROM vendors INNER JOIN products ON vendors.vend_id = products.vend_id;
+----------------+-------------+------------+
| prod_name | vend_name | prod_price |
+----------------+-------------+------------+
| .5 ton anvil | Anvils R Us | 5.99 |
| 1 ton anvil | Anvils R Us | 9.99 |
| 2 ton anvil | Anvils R Us | 14.99 |
| Fuses | LT Supplies | 3.42 |
| Oil can | LT Supplies | 8.99 |
| Detonator | ACME | 13.00 |
| Bird seed | ACME | 10.00 |
| Carrots | ACME | 2.50 |
| Safe | ACME | 50.00 |
| Sling | ACME | 4.49 |
| TNT (1 stick) | ACME | 2.50 |
| TNT (5 sticks) | ACME | 10.00 |
| JetPack 1000 | Jet Set | 35.00 |
| JetPack 2000 | Jet Set | 55.00 |
+----------------+-------------+------------+
14 rows in set (0.00 sec)
这两个表之间的关系是FROM
子句的组成部分,以INNER JOIN
指定。联结条件用特定的ON
子句而不是WHERE
子句给出。传递给ON
的实际条件与传递给WHERE
的相同。
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.00 sec)
这里的FROM
子句列出了3个表,而WHERE
子句定义了两个联结条件。
性能考虑:MySQL在运行时关联指定的每个表以联结处理,这种处理可能是非常耗资源的,因此应该仔细,不要联结不必要的表。联结的表越多,性能下降的越厉害。
SELECT cust_name,cust_contact FROM customers AS c, orders AS o,orderitems AS oi WHERE c.cust_id = o.cust_id AND oi.order_num = o.order_num AND prod_id = 'TNT2';
可以看到,FROM
子句中3个表全都具有别名,这样做可以缩短SQL语句。
表别名只在查询执行中使用。与列别名不一样,表别名不返回客户机。
迄今为止,我们使用的只是称为内部联结
或等值联结
的简单联结。还有其它3种联结,它们分别是自联结
,自然联结
和外部联结
。
mysql> SELECT prod_id,prod_name FROM products WHERE vend_id = (SELECT vend_id FROM products WHERE prod_id = 'DTNTR');
+---------+----------------+
| prod_id | prod_name |
+---------+----------------+
| DTNTR | Detonator |
| FB | Bird seed |
| FC | Carrots |
| SAFE | Safe |
| SLING | Sling |
| TNT1 | TNT (1 stick) |
| TNT2 | TNT (5 sticks) |
+---------+----------------+
7 rows in set (0.00 sec)
使用自联结后
mysql> SELECT p1.prod_id,p1.prod_name FROM products AS p1,products AS p2 WHERE p1.vend_id = p2.vend_id AND p2.prod_id = 'DTNTR';
+---------+----------------+
| prod_id | prod_name |
+---------+----------------+
| DTNTR | Detonator |
| FB | Bird seed |
| FC | Carrots |
| SAFE | Safe |
| SLING | Sling |
| TNT1 | TNT (1 stick) |
| TNT2 | TNT (5 sticks) |
+---------+----------------+
7 rows in set (0.01 sec)
查询中需要的两个表实际上是相同的表,因此products表在FROM
子句中出现了两次。虽然这是完全合法的,但对products的引用具有二义性,因此MySQL不知道你引用的是products表中的哪个实例。
为解决此问题,使用了表别名。products的第一次出现为别名p1,第二次出现为别名p2。
无论何时对表进行联结,应该至少有一个列出现在不止一个表中。标准的联结返回所有数据,甚至相同的列出现多次。自然联结排除多次出现,使每个列只返回一次。
系统不完成这项工作,由自己完成。这一般是通过对表使用通配符(SELECT *
),对所有其他表的列使用明确的子集来完成的。
mysql> SELECT c.*, o.order_num, o.order_date,oi.prod_id,oi.quantity FROM customers AS c,orders AS o,orderitems AS oi WHERE c.cust_id = o.cust_id AND oi.order_num = o.order_num AND prod_id = 'FB';
+---------+-------------+----------------+-----------+------------+----------+--------------+--------------+-----------------+-----------+---------------------+---------+----------+
| cust_id | cust_name | cust_address | cust_city | cust_state | cust_zip | cust_country | cust_contact | cust_email | order_num | order_date | prod_id | quantity |
+---------+-------------+----------------+-----------+------------+----------+--------------+--------------+-----------------+-----------+---------------------+---------+----------+
| 10001 | Coyote Inc. | 200 Maple Lane | Detroit | MI | 44444 | USA | Y Lee | [email protected] | 20005 | 2005-09-01 00:00:00 | FB | 1 |
| 10001 | Coyote Inc. | 200 Maple Lane | Detroit | MI | 44444 | USA | Y Lee | [email protected] | 20009 | 2005-10-08 00:00:00 | FB | 1 |
+---------+-------------+----------------+-----------+------------+----------+--------------+--------------+-----------------+-----------+---------------------+---------+----------+
2 rows in set (0.01 sec)
这个例子中,通配符只对第一个表使用。所有其他列明确列出,所以没有重复的列被检索出来。
许多联结表将一个表中的行与另一个表中的行相关联。但有时候会需要包含没有关联行的那些行。
下面SELECT
语句给出一个简单的内部联结。它检索所有客户及其订单。
mysql> SELECT customers.cust_id, orders.order_num FROM customers INNER JOIN orders ON customers.cust_id = orders.cust_id;
+---------+-----------+
| cust_id | order_num |
+---------+-----------+
| 10001 | 20005 |
| 10001 | 20009 |
| 10003 | 20006 |
| 10004 | 20007 |
| 10005 | 20008 |
+---------+-----------+
5 rows in set (0.00 sec)
外部联结语法类似。为了检索所有客户,包括那些没有订单的客户,可如下进行
mysql> SELECT customers.cust_id, orders.order_num FROM customers LEFT OUTER JOIN orders ON customers.cust_id = orders.cust_id;
+---------+-----------+
| cust_id | order_num |
+---------+-----------+
| 10001 | 20005 |
| 10001 | 20009 |
| 10002 | NULL |
| 10003 | 20006 |
| 10004 | 20007 |
| 10005 | 20008 |
+---------+-----------+
6 rows in set (0.00 sec)
与内部联结关联两个表中的行不同的是,外部联结还包括没有关联行的行。在使用OUTER JOIN
语法时,必须使用RIGHT
或LEFT
关键字指定包括所有行的表。上面的例子使用LEFT OUTER JOIN
从FROM
子句的左边表中选择所有行。
注意:应该总是提供联结条件,否则会得出笛卡儿积。