虎虎漫画小说

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

第 5 章 免费阅读

用尽可能少的SQL语句完成尽可能多的事情。相反,拙劣的开发者则倾向于严

格遵循已制订好的各功能步骤,下面是个真实的例子:

就算速度可以接受,这也是段极糟的代码。很不幸,xìng能专家经常遇到这种糟糕的代码。既然

两个值来自于同一表,为什么要分别用两个不同的语句呢?下面用Oracle的bulk collect子句,

一次xìng将两个值放到数组中,这很容易实现,关键在于对rslt_period进行order bycāo作,如下所

示:

-- Get the start of the accounting period

select closure_date

into dtPerSta

from tperrslt

where fiscal_year=to_char(Param_dtAcc,'YYYY')

and rslt_period='1' || to_char(Param_dtAcc,'MM');

-- Get the end of the period out of closure

select closure_date

into dtPerClosure

from tperrslt

where fiscal_year=to_char(Param_dtAcc,'YYYY')

and rslt_period='9' || to_char(Param_dtAcc,'MM');

select closure_date

bulk collect into dtPerStaArray

from tperrslt

where fiscal_year=to_char(Param_dtAcc,'YYYY')

and rslt_period in ('1' || to_char(Param_dtAcc,'MM'),

'9' || to_char(Param_dtAcc,'MM'))

order by rslt_period;

于是,这两个日期被分别保存在数组的第一个和第二个位置。其中,bulk collect 是PL/SQL 语

言特有的,但任何支持显式或隐式数组提取的语言都可如法pào制。

其实甚至数组都是不必要的,用以下的小技巧(注6),这两个值就可以被提取到两个变量中:

在这个例子中,预期返回值为两行数据,所以问题是:如何把原本属于一个字段的两行数据,

以一行数据两个字段的方式检索出来(正如数组提取的例子一样)。为此,我们

检查rslt_period字段,两行数据的rslt_period字段有不同值;如果找到需要的记录,就返回要找

的日期;否则,就返回一个在任何情况下都远比我们所需日期要早的日期(此处选了哈斯丁之

役(battle of Hastings)的日期)。只要每次取出最大值,就可以确保获得需要的日期。这是个

非常实用的技巧,也可以应用在字符或数值数据,第11章会有更详细的说明。

总结:SQL是声明xìng语言(declarative language),所以设法使你的代码超越业务过程的规格

说明。

SQL

的进攻式编程

Offensive Coding with SQL

一般的建议是进行防御式编程(code defensively),在开始处理之前先检查所有参数的合法xìng。

但实际上,对数据库编程而言,尽量同时做几件事情的进攻式编程有切实的优势。

有个很好的例子:进行一连串检查,每当其中一个检查所要求的条件不符时就产生异常。信用

select max(decode(substr(rslt_period, 1, 1), -- Check the first character

'1', closure_date,

-- If it's '1' return the date we want

to_date('14/10/1066', 'DD/MM/YYYY'))),

-- Otherwise scomthing old

max(decode(substr(rslt_period, 1, 1),

'9', closure_date, -- The date we want

to_date('14/10/1066', 'DD/MM/YYYY'))),

into dtPerSta, dtPerClosure

from tperrslt

where fiscal_year=to_char(Param_dtAcc,'YYYY')

and rslt_period in ('1' || to_char(Param_dtAcc,'MM'),

'9' || to_char(Param_dtAcc,'MM'));

卡付款的处理中就涉及类似步骤。例如,检查所提jiāo的客户身份和卡号是否有效,以及两者是

否匹配;检查信用卡是否过期;最后,检查当前的支付额是否超过了信用额度。如果通过了所

有检查,支付cāo作才继续进行。

为了完成上述功能,不熟练的开发者会写出下列语句,并检查其返回结果:

接下来,他会做类似的工作,并再一次检查错误代码:

之后,他才会处理金融jiāo易。

相反,熟练的开发者更喜欢像下面这样编写代码(假设today()返当前日期):

接着,检查被更新的行数。如果结果为0,只需执行下面的一个cāo作即可判断出错原因:

如果此查询没有返回数据,则可断定custcomr_id 的值是错的;如果card_num 是null,则可

断定卡号是错的;等等。其实,多数情况下此查询无需被执行。

注意

你是否注意到,上述第一段代码中使用了count(*)呢?这是个count(*)被误用于存在xìng检测的绝

select count(*)

from custcomrs

where custcomr_id = provided_id

select card_num, expiry_date, credit_limit

from accounts

where custcomr_id = provided_id

update accounts

set balance = balance - purchased_amount

where balance >= purchased_amount

and credit_limit >= purchased_amount

and expiry_date > today()

and custcomr_id = provided_id

and card_num = provided_cardnum

select c.custcomr_id, a.card_num, a.expiry_date,

a.credit_limit, a.balance

from custcomrs c

left outer join accounts a

on a.custcomr_id = c.custcomr_id

and a.card_num = provided_cardnum

where c.custcomr_id = provided_id

佳例子。

“进攻式编程”的本质特征是:以合理的可能xìng(reasonable probabilities)为基础。例如,检查

客户是否存在是毫无意义的——因为既然该客户不存在,那么他的记录根本就不在数据库中!

所以,应该先假设没有事情会出错;但如果出错了,就在出错的地方(而且只在那个地方)采

取相应措施。有趣的是,这种方法很像一些数据库系统中采用的“乐观并发控制(optimistic

concurrency control)”,后者会假设update冲突不会发生,只在冲突真的发生时才进行控制处理。

结果,乐观方法比悲观方法的吞吐量高得多。

总结:以概论为基础进行编程。假设最可能的结果;不是的确必要,不要采用异常捕捉的处理

方式。

精明地使用异常(Exceptions

Discerning Use of Exceptions

勇敢与鲁莽的界线很模糊,我建议进攻式编程,但并不是要你模仿轻步兵旅在Balaclava的自杀

xìng冲锋(注7)。针对异常编程,最终可能落得虚张声势的愚蠢结果,但自负的开发者还是对它“推

『加入书签,方便阅读』