《SQL语言艺术(PDF格式)》第15章


利于多数 SQL 方言,非关联子查询可以被改写成from 子句中的内嵌视图。然而,一定要记住 
的是,in 会隐式地剔除重复项目,当子查询改写为 from 子句中的内嵌视图时,必须要显式地 
消除重复项目。例如: 
select custname 
from customers 
where city = "GOTHAM" 
and custid in 
(select o。custid 
from orders o; 
(select distinct od。ordid 
from orderdetail od; 
articles a 
where a。artname = "BATMOBILE" 
and a。artid = od。artid) x 
where o。ordered 》= somefunc 
and x。ordid = o。ordid) 
编写功能等价的查询时,不同的编写方式就好像同义词。在书面语和口语中,同义词的意思虽 
…………………………………………………………Page 44……………………………………………………………
然大致相同,但又有细微差异,因此某个词在特定语境中更合适。同样,数据和处理的具体实 
现细节可以决定选择哪种查询方式。 
蝙蝠车买主案例总结 
前面讨论的各段SQL语句,看似意义不大的编程技巧练习,实则不然。关键是“擒获(attack)” 
数据的方法有很多,不必按照先customers、然后orders、接着orderdetail和articles的方式来编 
写查询。 
现在以箭头表示搜索条件的强度——条件分辨力越强,箭头就越大。假设 Gotham市的客户非 
常少,但过去六个月的销售业绩不错,卖出了很多蝙蝠车,此时规划图如图4…6所示。虽然商品 
名称之上有个过滤条件,但图中的中等大小的箭头指向了表orderdetail,因为该表是真正重要 
的表。待售商品可能很少,反映出销售收入的百分比;也可能待售商品很多,最畅销的商品之 
一就是蝙蝠车。 
相反,如果我们假设多数客户在 Gotham市,但其中很少的客户买了蝙蝠车,则规划图如图4…7 
所示。很显然,此时表orderdetail 是最大的目标。来自这个表的数据的数据量缩减速度越快, 
查询执行得就越快。 
还要注意的非常重要的一点是,“过去六个月”并不是个非常精确的条件。但如果我们把条件改为 
过去两个月,而库中有十年的销售记录,会发生什么呢?在这种情况下,如果能先访问到近期 
的订单(借助第5章中描述的一些技术,这些数据或许就聚集在一起),查询的效率就会更高些; 
找出近期订单后,一方面选取Gotham 的客户,另一方面则选取蝙蝠车订单。所以,换个角度 
来看,最好的执行计划并不只相依于数据值,还应该随着时间而不断进化。 
好了,总结一下。首先,解决问题的方法不只一种……而且查询的编写方式经常会与数据隐含 
的假设相关。殊途同归,最终的结果集都是一样的,但执行速度可能有极大差异。查询的编写 
方式会影响执行路径,尤其是应用无法在真正的关系环境中表达的条件时。若想让优化器发挥 
极致,我们就必须扩大关系处理的工作量,并确保非关系的部分对最后结果集的影响最小。 
本章前面一直假设代码的执行方式与编写方式一样,但其实,优化器可能改写查询——有时改 
动还很大。你或许认为优化器所做的改写无关紧要,因为 SQL本是一种声明性语言(declarative 
language),用它来说明想要什么,并让 DBMS 予以执行。然而,你也看到了,每次用不同方 
式改写查询时,都必须更新关于数据分布和已有索引的假设。因此有一点非常重要:应预先考 
虑优化器的工作,以确定它能找到所需数据——这可能是索引,也可能是数据相关的详细统计 
信息。 
总结:保证SQL 语句返回正确结果,只是建立最佳 SQL语句的第一步。 
大数据量查询 
Querying Large Quantities of Data 
越快剔除不需要的数据,查询的后续阶段必须处理的数据量就越少,自然查询的效率就越高, 
这听起来显而易见。集合操作符(set operator)是这一原理的绝佳应用,其中的union使用最 
为广泛,我们经常看到通过union操作将几个表“粘”在一起。中等复杂程度的union语句较为常见, 
大多数被连接的表都会同时出现在union两端的select 语句中。例如下面这段代码: 
…………………………………………………………Page 45……………………………………………………………
select 。。。 
fromA; 
B; 
C; 
D; 
E1 
where (condition on E1) 
and (joins and other conditions) 
union 
select 。。。 
fromA; 
B; 
C; 
D; 
E2 
where (condition on E2) 
and (joins and other conditions) 
这类查询是典型的“照搬式”编程。为了提高效率,可以仅对代码中非共用的表(本例中即E1和 
E2)使用union,然后配合筛选条件,把 union 语句降级为内嵌视图。代码如下: 
select 。。。 
fromA; 
B; 
C; 
D; 
(select 。。。 
from E1 
where (condition on E1) 
union 
select 。。。 
from E2 
where (condition on E2)) E 
where (joins and other conditions) 
另一个“查询条件用错了地方”的经典例子,和在含有 group by 子句的查询中进行过滤操作有 
关。你可以过滤分了组的字段,也可以过滤聚合(aggregate)结果(例如检查 count() 的结果 
是否小于某阈值),或者同时过滤两者;SQL 允许在 having 子句中使用这类条件,但应该在 
group by 完成后才进行过滤(比如排序之后再进行聚合操作)。任何影响聚合函数(aggregate 
…………………………………………………………Page 46……………………………………………………………
function)结果的条件都应放在 having 子句中,因为在 group by 之前无从知道聚合函数的结 
果。任何与聚合无关的条件都应放在 where 子句中,从而减少为进行group by而必须执行的排 
序操作所处理的数据量。 
现在回过头来看客户与订单的例子,我承认先前处理订单的方法比较复杂。在订单完成之前, 
必须经历几个阶段,这些都记录在表orderstatus中,该表的主要字段有:ordid(订单ID)、status、 
statusdate(时间戳)等,主键由ordid和statusdate组成。我们的需求是列出所有尚未标记为完 
成状态的订单(假设所有交易都已终止)的下列字段:订单号、客户名、订单的最后状态,以 
及设置状态的时间。最终,我们写出下列查询,滤掉已完成的订单,并找出订单当前状态: 
select c。custname; o。ordid; os。status; os。statusdate 
from customers c; 
orders o; 
orderstatus os 
where o。ordid = os。ordid 
and not exists (select null 
from orderstatus os2 
where os2。status = "PLETE" 
and os2。ordid = o。ordid) 
and os。statusdate = (select max(statusdate) 
from orderstatus os3 
where os3。ordid = o。ordid) 
and o。custid = c。custid 
乍一看,这个查询很合理,但事实上,它让人非常担心。首先,上面代码中有两个子查询,但 
它们嵌入的方式和前一个例子的方式不同,它们只是彼此间接相关的。最让人担心的是,这两 
个子查询访问相同的表,而且该表在外层已经被访问过。我们编写的过滤条件质量如何呢?因 
为只检查了订单是否完成,所以它不是非常精确。 
这个查询如何执行的呢?很显然,可以扫描 orders 表,检查每一条订单记录是否为已完成状 
态——注意,仅通过表 orders 即可找出所要信息似乎令人高兴,但实际情况并非如此,因为 
只有上述活动之后,才能检查最新状态的日期,即必须按照子查询编写的顺序来执行。 
上述两个子查询是关联子查询,这很?
小说推荐
返回首页返回目录