16 创建高级联结
16.2 使用不同类型的联结
之前接触到的INNER JOIN是称为内部联结或等值联结(equijoin)的简单联结。
16.2.1 自联结
MariaDB [testdatabase]> 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.001 sec)
此查询中需要的两个表实际上是相同的表,products表在FROM子句中出现了两次。虽然这是完全合法的,但对products的引用具有二义性,因为MySQL不知道你引用的是products表中的哪个实例。
为解决此问题,使用了表别名。如果不这样,MySQL将返回错误,因为分别存在两个名为prod_id、prod_name的列。MySQL不知道想要的是哪一个表中的列(即使它们事实上是同一个列,但MySQL不知道他们是同一个表)。
用自联结而不用子查询 自联结通常作为外部语句用来替代从相同表中检索数据时使用的子查询语句。虽然最终的结果是相同的,但有时候处理联结远比处理子查询快得多。应该试一下两种方法,以确定哪一种的性能更好。
16.2.2 自然联结
无论何时对表进行联结,应该至少有一个列出现在不止一个表中(被联结的列)。标准的联结(前一章中介绍的内部联结)返回所有数据,甚至相同的列多次出现。自然联结排除多次出现,使每个列只返回一次。
自然联结是这样一种联结,其中你只能选择那些唯一的列。这一般是通过对表使用通配符(SELECT *),对所有其他表的列使用明确的子集来完成的
16.2.3 外部联结
许多联结将一个表中的行与另一个表中的行相关联。但有时候会需要包含没有关联行的那些行。
MariaDB [testdatabase]> 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.001 sec)
这条SELECT语句使用了关键字OUTER JOIN来指定联结的类型(而不是在WHERE子句中指定)。与内部联结关联两个表中的行不同的是,外部联结还包括没有关联行的行。在使用OUTER JOIN语法时,必须使用RIGHT或LEFT关键字指定包括其所有行的表(RIGHT指出的是OUTER JOIN右边的表,而LEFT指出的是OUTER JOIN左边的表)。上面的例子使用LEFT OUTER JOIN从FROM子句的左边表(customers表)中选择所有行。
没有*=操作符 MySQL不支持简化字符*=和=*的使用,这两种操作符在其他DBMS中是很流行的。
16.3 使用带聚集函数的联结
MariaDB [testdatabase]> SELECT customers.cust_name,customers.cust_id,COUNT(orders.order_num) AS num_ord FROM customers INNER JOIN orders ON customers.cust_id = orders.cust_id GROUP BY customers.cust_id;
+----------------+---------+---------+
| cust_name | cust_id | num_ord |
+----------------+---------+---------+
| Coyote Inc. | 10001 | 2 |
| Wascals | 10003 | 1 |
| Yosemite Place | 10004 | 1 |
| E Fudd | 10005 | 1 |
+----------------+---------+---------+
4 rows in set (0.001 sec)
此SELECT语句使用INNER JOIN将customers和orders表互相关联。GROUP BY子句按客户分组数据,因此,函数调用COUNT(orders.order_num)对每个客户的订单计数,将它作为num_ord返回。
聚集函数也可以方便地与其他联结一起使用
MariaDB [testdatabase]> SELECT customers.cust_name,customers.cust_id,COUNT(orders.order_num) AS num_ord FROM customers LEFT OUTER JOIN orders ON customers.cust_id = orders.cust_id GROUP BY customers.cust_id;
+----------------+---------+---------+
| cust_name | cust_id | num_ord |
+----------------+---------+---------+
| Coyote Inc. | 10001 | 2 |
| Mouse House | 10002 | 0 |
| Wascals | 10003 | 1 |
| Yosemite Place | 10004 | 1 |
| E Fudd | 10005 | 1 |
+----------------+---------+---------+
5 rows in set (0.000 sec)
MariaDB [testdatabase]> SELECT customers.cust_name,customers.cust_id,COUNT(orders.order_num) AS num_ord FROM customers RIGHT OUTER JOIN orders ON customers.cust_id = orders.cust_id GROUP BY customers.cust_id;
+----------------+---------+---------+
| cust_name | cust_id | num_ord |
+----------------+---------+---------+
| Coyote Inc. | 10001 | 2 |
| Wascals | 10003 | 1 |
| Yosemite Place | 10004 | 1 |
| E Fudd | 10005 | 1 |
+----------------+---------+---------+
4 rows in set (0.001 sec)
个人理解 JOIN语句(FROM语句里的表)左边的为左表,右边的为右表。LEFT JOIN保留左表中所有的内容,RIGHT JOIN保留右表中的所有内容。(JOIN中相对于INNER的OUTER关键词一半省略)
16.4 使用联结和联结条件
汇总一下关于联结及其使用的某些要点
- 注意所使用的联结类型。一般我们使用内部联结,但使用外部联结也是有效的。
- 保证使用正确的联结条件,否则将返回不正确的数据。
- 应该总是提供联结条件,否则会得出笛卡儿积。
- 在一个联结中可以包含多个表,甚至对于每个联结可以采用不同的联结类型。虽然这样做是合法的,一般也很有用,但应该在一起测试它们前,分别测试每个联结。这将使故障排除更为简单。