虎虎漫画小说

繁体版 简体版
虎虎漫画小说 > > SQL语言艺术最新章节 > 第 14 章

第 14 章 免费阅读

sum(d.tax)

from invoice_detail d,

invoice_extractor e

where (e.pga_status = 0

or e.rd_status = 0)

and suitable_join_condition

and (d.type_code in (3, 7, 2)

or (d.type_code = 4

and d.subtype_code not in

(select trans_code

from trans_description

where trans_category in (6, 7))))

group by what_is_required

having sum(d.tax) != 0

最后一个条件有问题(它使我想起了《绿野仙踪》里的黄砖路,甚至使我做起了“负税率”的白

日梦):

sum(d.tax) != 0

如前所述,换成下列条件更加合理:

and d.tax > 0

上述的例子中,使用集合cāo作符会相当笨拙,因为必须访问invoice_detail表好几次——如你所

料,那不是个轻量级的表。当然,还要看每个条件的可选择xìng,如果type_ code=4很少见,那

么它就是个可选择xìng很高的条件,exists或许会比not in ()更适合。另外,如果trans_description正

好是个小型表(或者相对较小),尝试通过单独cāo作测试存在xìng,并起不到改善xìng能的效果。

另一个表达非存在xìng的方法很有趣——而且通常相当高效——是使用外连接(outer join)。外连

接的主要目的是,返回来自一个表的所有信息及连接表中的对应信息。无对应信息的记录也需

返回——查找另一个表中无对应信息的数据时,这些记录正好是我们的兴趣所在,可通过检查

连接表的字段值是否为null找出它们。

例如:

select whatever

from invoice_detail

where type_code = 4

and subtype_code not in

(select trans_code

from trans_description

where trans_category in (6, 7))

或重写为:

select whatever

from invoice_detail

outer join trans_description

on trans_description.trans_category in (6, 7)

and trans_description.trans_code = invoice_detail.subtype_code

where trans_description.trans_code is null

我故意在join子句中加上trans_category的条件。有人认为它应该出现在where 子句中,实际上,

在连接之前或在连接之后过滤都不影响结果(当然,根据这个条件和连接条件本身的可选择xìng

不同,会有不同的xìng能表现)。然而,在使用空值上的条件时,我们别无选择,只有在连接后才

能做检查。

外连接有时需要加distinct。实际上,通过外连接或not in()非关联子查询,来检查数据是否存在

的差异很小,因为连接所使用的字段,正好与比较子查询结果集的字段完全相同。不过,众所

周知的是,SQL 语言的“查询表达式风格”对“执行模式”影响很大,尽管理论上不是这么说的。

这取决于优化器的复杂程度,以及它是否会以类似方法处理这两类查询。换言之,SQL 不是真

正的声明xìng语言(SQL is not a truly declarative language),尽管优化器不断推陈出新改善SQL的

可靠xìng(reliability)。

最后提醒一下,应密切注意null,这个舞会扫兴者(party-poopers)经常出现。虽然在in ()子查

询中,null与大量非空值连接不会对外层查询造成影响,但在使用not in ()子查询时,由内层查

询返回的null会造成not in()条件不成立。要确保子查询不会返回null并不需要太高的代价,而且

这么做可以避免许多灾难。

总结:数据集可以通过各种技巧进行比较,但一般而言,使用外连接和子集合cāo作符更高效。

当前值

Current Values

当我们只对最近或当前值感兴趣时,如何避免使用嵌套子查询或OLAP 函数(两者都引起排序)

而直接找到适当值,是非常吸引人的设计。如第1章所述,解决该问题的方法之一,就是把每个

值与某个“截止日期”相关联—— 就像麦片外盒上的“保质期(best before)”一样——并让当前

值的“截止日期”是遥远的未来(例如公元2999 年12 月31 日)。这种设计存在一些与实际相

关的问题,下面讨论这些问题。

使用“固定日期”,确定当前值变得非常容易。查询如下所示:

select whatever

from hist_data

where item_id = scomvalue

and record_date = fixed_date_in_the future

接着,通过主键找到正确的记录。(当然,要参照的日期如果不是当前日期,就必须使用子查询

或OLAP 函数了。)然而,这种方法有两个主要缺点。

较明显的缺点:chā入新的历史数据之前,先要更新“当前值”(例如今天),接着,将最新“当

前值”和历史数据一起chā入表中。这个过程导致工作量加倍。更糟的是,关系理论中的主键用于

识别记录,但具有唯一xìng的(item_id, record_date)却不能作为主键,因为我们会对它做“部分更新

(partially update)”。因此,必须有一个能让外键参照的代理键(ID字段或序列号),结果程序

变得更加复杂。大型历史表的麻烦就是,通常它们也经历过高频率的数据chā入,所以数据量才

会这么大。快速查询的好处,能抵销缓慢chā入的缺点吗?这很难说,但绝对是个值得考虑的问

题。

还有个微妙的缺点与优化器有关。优化器使用各种详细程度不同的统计数据,检查字段的最

低值和最高值,尝试评估值的分布情况。假设历史表包含了自2000 年1 月1 日开始的历史

数据。于是,我们的数据组成是“散布在几年间的99.9% 的历史数据”加上“2999 年12 月31 日

的0.1% 的‘当前数据’”。因此,优化器会认为数据散布在一千年的范围内。优化器在数据范围

上的偏差是由于查询中出现的上限日期的误导(即“and record_date = fixed_date_in_the future”)。

此时的问题就是,如果你当查询的不是当前值(例如,你要统计不同时段的数据变化),优化器

可能错误地做出“使用索引”的决定——因为你访问的只是千年中的极小部分——但实际上需要

的是对数据进行扫描。是优化器的评估偏差导致它做出完全错误的执行计划决定,这很难修正。

总结:要理解优化器如何看待你的系统,就必须理解你的数据和数据分布方式。

通过聚合获得结果集

Result Set Obtained by Aggregation

本节讨论一类极常见的情况:对一个或多个主表(main table)中的详细数据进行汇总,动态计

算出结果集。换言之,我们面临数据聚合(aggregation of data)的问题。此时,结果集大小取

决于group by的字段的基数,而不是查询条件的精确xìng。正如第一节“小结果集,直接条件”中所

述,对表进行一趟(a single pass)处理获得的并非真正聚合的结果(否则就需要自连接和多次

处理),但此时聚合函数(或聚合)也相当有用。实际上,最让人感兴趣的SQL聚合使用技巧,

不是明显需要sum或avg的情况,而是如何将过程xìng处理转化为以聚合为基础的纯SQL替代方

案。

如第2章所强调的,编写高效SQL代码的关键,第一是“勇往直前”,即不要预先检查,而是查询

完成后测试是否成功—— 毕竟,蹑手蹑脚地用脚趾试水赢不了游泳比赛。第二是尽量把更多

『加入书签,方便阅读』