的日期;否则,就返回一个在任何情况下都远比我们所需日期要早的日期(此处选了哈斯丁之 役(battle of Hastings)的日期)。只要每次取出最大值,就可以确保获得需要的日期。这是个 非常实用的技巧,也可以应用在字符或数值数据,第11章会有更详细的说明。 总结:SQL是声明性语言(declarative language),所以设法使你的代码超越业务过程的规格 说明。 SQL SSQQLL的进攻式编程 Offensive Coding with SQL 一般的建议是进行防御式编程(code defensively),在开始处理之前先检查所有参数的合法性。 但实际上,对数据库编程而言,尽量同时做几件事情的进攻式编程有切实的优势。 有个很好的例子:进行一连串检查,每当其中一个检查所要求的条件不符时就产生异常。信用 卡付款的处理中就涉及类似步骤。例如,检查所提交的客户身份和卡号是否有效,以及两者是 否匹配;检查信用卡是否过期;最后,检查当前的支付额是否超过了信用额度。如果通过了所 …………………………………………………………Page 24…………………………………………………………… 有检查,支付操作才继续进行。 为了完成上述功能,不熟练的开发者会写出下列语句,并检查其返回结果: select count(*) from customers where customer_id = provided_id 接下来,他会做类似的工作,并再一次检查错误代码: select card_num; expiry_date; credit_limit from accounts where customer_id = provided_id 之后,他才会处理金融交易。 相反,熟练的开发者更喜欢像下面这样编写代码(假设today()返当前日期): update accounts set balance = balance purchased_amount where balance 》= purchased_amount and credit_limit 》= purchased_amount and expiry_date 》 today() and customer_id = provided_id and card_num = provided_cardnum 接着,检查被更新的行数。如果结果为 0,只需执行下面的一个操作即可判断出错原因: select c。customer_id; a。card_num; a。expiry_date; a。credit_limit; a。balance from customers c leftouter join accounts a on a。customer_id = c。customer_id and a。card_num = provided_cardnum where c。customer_id = provided_id 如果此查询没有返回数据,则可断定customer_id 的值是错的;如果 card_num 是 null,则可 断定卡号是错的;等等。其实,多数情况下此查询无需被执行。 注意 你是否注意到,上述第一段代码中使用了count(*)呢?这是个count(*)被误用于存在性检测的绝 佳例子。 “进攻式编程”的本质特征是:以合理的可能性(reasonableprobabilities)为基础。例如,检查 …………………………………………………………Page 25…………………………………………………………… 客户是否存在是毫无意义的——因为既然该客户不存在,那么他的记录根本就不在数据库中! 所以,应该先假设没有事情会出错;但如果出错了,就在出错的地方(而且只在那个地方)采 取相应措施。有趣的是,这种方法很像一些数据库系统中采用的“乐观并发控制(optimistic concurrency control)”,后者会假设update冲突不会发生,只在冲突真的发生时才进行控制处理。 结果,乐观方法比悲观方法的吞吐量高得多。 总结:以概论为基础进行编程。假设最可能的结果;不是的确必要,不要采用异常捕捉的处理 方式。 SQL 简洁的SSQQLL Succinct SQL 熟练的开发者使用尽可能少的 SQL语句完成尽可能多的事情。相反,拙劣的开发者则倾向于严 格遵循已制订好的各功能步骤,下面是个真实的例子: …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"); 就算速度可以接受,这也是段极糟的代码。很不幸,性能专家经常遇到这种糟糕的代码。既然 两个值来自于同一表,为什么要分别用两个不同的语句呢?下面用Oracle的bulk collect子句, 一次性将两个值放到数组中,这很容易实现,关键在于对rslt_period进行order by操作,如下所 示: 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; …………………………………………………………Page 26…………………………………………………………… 于是,这两个日期被分别保存在数组的第一个和第二个位置。其中,bulkcollect 是 PL/SQL 语 言特有的,但任何支持显式或隐式数组提取的语言都可如法炮制。 其实甚至数组都是不必要的,用以下的小技巧(注6),这两个值就可以被提取到两个变量中: 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 something old max(decode(substr(rslt_period; 1; 1); "9"; closure_date; …The date wewant 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")); 在这个例子中,预期返回值为两行数据,所以问题是:如何把原本属于一个字段的两行数据, 以一行数据两个字段的方式检索出来(正如数组提取的例子一样)。为此,我们 检查rslt_period字段,两行数据的rslt_period字段有不同值;如果找到需要的记录,就返回要找 的日期;否则,就返回一个在任何情况下都远比我们所需日期要早的日期(此处选了哈斯丁之 役(battle of Hastings)的日期)。只要每次取出最大值,就可以确保获得需要的日期。这是个 非常实用的技巧,也可以应用在字符或数值数据,第11章会有更详细的说明。 总结:SQL是声明性语言(declarative language),所以设法使你的代码超越业务过程的规格 说明。 SQL SSQQLL的进攻式编程 Offensive Coding with SQL 一般的建议是进行防御式编程(code defensively),在开始处理之前先检查所有参数的合法性。 但实际上,对数据库编程而言,尽量同时做几件事情的进攻式编程有切实的优势。 有个很好的例子:进行一连串检查,每当其中一个检查所要求的条件不符时就产生异常。信用 …………………………………………………………Page 27…………………………………………………………… 卡付款的处理中就涉及类似步骤。例如,检查所提交的客户身份和卡号是否有效,以及两者是 否匹配;检查信用卡是否过期;最后,检查当前的支付额是否超过了信用额度。如果通过了所 有检查,支付操作才继续进行。 为了完成上述功能,不熟练的开发者会写出下列语?