[TOC]
第十四章 子查询
在我们的数据库中,有三个表:
- orders表存储订单编号,订单日期,顾客编号;
1 | +-----------+---------------------+---------+ |
- customers表存储顾客信息,包括姓名,地址,联系方式等;
1 | +---------+----------------+---------------------+-----------+------------+----------+--------------+--------------+---------------------+ |
- orderitems表存储订单编号,订单数量,产品编号,价格等。
1 | +-----------+------------+---------+----------+------------+ |
14.1 子查询
现在我们想要知道所有订购了TNT2的所有用户的基本信息,应该怎么办呢?
基本的流程如下:
- TNT2产品是来自orderitems表,我们可以找到订单编号order_num;
- 从订单编号order_num可以找到顾客编号cust_id;
- 从顾客编号cust_id可以查到估计的姓名地址信息。
1 | SELECT order_num FROM orderitems WHERE prod_id = 'TNT2'; |
14.2 计算字段使用子查询
下面看如下的例子,嵌套了一个子查询,但是这个子查询是一个计算字段。
1 | SELECT cust_name,cust_state,(SELECT COUNT(*) |
我们先看最外层,从customers表中,对每一位顾客,获取姓名,所在州以及一个计算字段。
这个计算字段是什么呢,是在orders表中的一个聚集函数,统计每位顾客cust_id 的订单总数,取别名为orders.
这里为什么采用了完全限定名呢,是因为orders,customers里都有名为cust_id的列,为了避免歧义和错误,使用完全限定名。
第十五章 联结表
我们先来看两个表:第一个表存储的是供应商vendors信息,第二个表存储的是产品products目录。
1 | +---------+----------------+-----------------+-------------+------------+----------+--------------+ |
我们来想一想,为什么需要将供应商信息和产品信息分开存储成两个表呢?
- 假设在一个表中,那么每一个供应商的产品都会有一条记录供应商地址,联系方式之列的条目,那么就会出现很多的重复;
- 重复多了,出错的概率就会很大了,也会出现不一致的情况;
- 需要改动的时候,比如供应商搬家了,只需要改一个地方就行了。
所以关系数据库涉及的一个基础就是不允许相同的数据出现多次,那么在把信息分解成多个表之后,就必须靠某种关系(relation)将表进行联结(join)。
15.1 创建联结
products只存储产品信息,它除了存储供应商ID(vendors表的主键)外不存储其他供应商信息。vendors表的主键又叫做products的外键,它是两个表的联结。
外键(foreign key):外键为某个表中的一列,它包含另一个表的主键值,定义了两个表之间的联系。
可伸缩性(scale):能够适应不断增加的工作量而不失败。设计良好的数据库或应用程序称之为可伸缩性好(scale well).
虽然分解数据能够有效地存储,更方便处理数据并且具有很好的伸缩性,但是数据存储在多个表中,无疑给检索带来了困难,所以我们需要用联结来解决多个表的信息检索问题。
1 | SELECT vend_name,prod_name,prod_price -- 来自两个表的列 |
15.1.1 背后的逻辑
1 | SELECT vend_name,prod_name,prod_price |
所以上文中WHERE vendors.vend_id = products.vend_id实际上就是对笛卡尔积的结果进行过滤。
15.1.2 创建内部联结
前面的联结是基于相等测试,所以称为等值联结(equijoin),或者内部联结。
1 | SELECT vend_name,prod_name,prod_price |
与15.1的检索语句结果只是顺序不同而已。
15.2 联结多个表
1 | SELECT prod_name,vend_name,prod_price,quantity |
联结表相当于笛卡尔积,就像是找到了所有的联结方式,这些联结里很多是错误的,于是必须要用过滤条件,不然性能下降很厉害。
现在回顾下第十四章中返回订购产品TNT2的客户列表,我们现在有了如下的解决方案:
1 | SELECT cust_name,cust_contact |
第十六章 创建高级联结
除了内部联结之外,我么还有其他3种常见的联结方式,分别为自连接,自然联结和外部联结。
16.1 使用表别名
在对不同的表进行联结时,给表名起别名可以有如下作用:
- 别名往往更简单,所以每次不需要写表的全频,而只是别名,这样就会缩短SQL语句;
- MySQL允许在单条SELECT语句中多次使用相同的表,比如表与自身进行联结(就会出现至少两个相同的表名),此时可以取别名p1,p2进行区分。
下面给出别名的例子,
1 | SELECT cust_name,cust_contact |
16.2 不同类型的联结
下面我们介绍三种类型的联结。
16.2.1 自联结
自联结就是表与自己进行联结,此时别名很重要。现在假设产品DTNTR出现了质量问题,我们想看看这个产品的供应商的其他产品是不是也有问题,所以我们需要找到产品DTNTR的供应商,然后找这个供应商所有的产品。
1 | SELECT prod_id,prod_name |
上面是通过子查询得到呢,那么如果要通过联结呢?(子查询和联结都可以完成任务,关键是性能上的差异)
1 | SELECT p1.prod_id,p1.prod_name |
16.2.2 自然联结
所谓自然联结,就是将不同列取出来,把重复的列扔掉。那么可以取一个表的所有列,其他表不取之前表中取过的列。
1 | SELECT c.*,o.order_num,o.order_date,oi.prod_id,oi.quantity,oi.item_price |
16.2.3 外部联结
我们通过比较一个内部联结和外部联结的例子进行说明。
1 | SELECT customers.cust_id,orders.order_num |
下面是两个表的信息,我们看结果是怎么来的?
1 | mysql> select * from orders; |
16.3 使用带聚集函数的联结
1 | SELECT customers.cust_name,customers.cust_id |
第十七章 组合查询
所谓组合查询,就是将多条select语句的结果合并成一个,使用Union命令,和集合的并集是一样的,不包含重复元素,如果想要重复元素,可以使用Union All命令。
1 | SELECT vend_id,prod_id,prod_price |
Union的使用规则:
- 两条及以上的SELECT语句,用Union分隔;
- 每个查询必须用相同的列,表达式和聚集函数;
- 列数据类型必须兼容。