连接了表 orderdetail 和 orders 之后,来看 articles 表,这不会有问题,因为表 order 包括 artid 字段。最后,检查 articles 中的值是否为 Batmobile 。查询就这样结束了,因为用了 distinct ,通过层层筛选的客户名还必须要排序,以剔除重复项目。
避免在最高层使用 distinct 应该是一条基本规则 。原因在于,即使我们遗漏了连接的某个条件, distinct 也会使查询 " 看似正确 " 地执行 —— 无可否认,发现重复数据容易,发现数据不准确很难,所以避免在最高层使用 distinct 应该是一条基本规则。
发现结果不正确更难,例如,如果恰巧有多位客户都叫 " Wayne " , distinct 不但会剔除由同个客户的多张订单产生的重复项目,也会剔除由名字相同的不同客户产生的重复项目。事实上,应该同时返回具唯一性的客户 ID 和客户名,以保证得到蝙蝠车买家的完整清单。
要摆脱 distinct ,可考虑以下思路:客户在 Gohtam 市,而且满足存在性测试,即在最近六个月订购过蝙蝠车。注意,多数(但非全部) SQL 方言支持以下语法:
| select c.custname from customers c where c.city = 'GOTHAM' and exists (select null from orders o, orderdetail od, articles a where a.artname = 'BATMOBILE' and a.artid = od.artid and od.ordid = o.ordid and o.custid = c.custid and o.ordered >= somefunc ) |
上例的存在性测试,同一个名字可能出现多次,但每个客户只出现一次,不管他有多少订单。有人认为我对 ANSI SQL 语法的挑剔有点苛刻(指 " 蝙蝠车买主 " 的例子),因为上面代码中customers 表的地位并没有降低。其实,关键区别在于,新查询中 customers 表是查询结果的唯一来源(嵌套的子查询会负责找出客户子集),而先前的查询却用了 join 。
这个嵌套的子查询与外层的 select 关系十分密切。如代码第 11 行所示(粗体部分),子查询参照了外层查询的当前记录,因此,内层子查询就是所谓的关联子查询( correlated subquery )。
此类子查询有个弱点,它无法在确定当前客户之前执行。如果优化器不改写此查询,就必须先找出每个客户,然后逐一检查是否满足存在性测试,当来自 Gotham 市的客户非常少时执行效率倒是很高,否则情况会很糟(此时,优秀的优化器应尝试其他执行查询的方式)。
| select custname from customers where city = 'GOTHAM' and custid in (select o.custid from orders o, orderdetail od, articles a where a.artname = 'BATMOBILE' and a.artid = od.artid and od.ordid = o.ordid and o.ordered >= somefunc) |
在这个例子中,内层查询不再依赖外层查询,它已变成了非关联子查询( uncorrelated subquery ),只须执行一次。很显然,这段代码采用了原有的执行流程。在本节的前一个例子 中 ,必须先搜寻符合地点条件的客户(如均来自 GOTHAM ),接着依次检查各个订单。而现在,订购了蝙蝠车的客户,可以通过内层查询获得。










