返回

SQL语言艺术

关灯
护眼
第2章(1 / 1)
强烈推荐: 独家占有之亿万豪宠 香闺 三婚盛宠:前夫,请签字 闪婚新妻,黎少心尖宠 天才宝宝:爹地,我是妈咪偷来的? 修仙老司机 根源武道 驯服劣性老公:彪悍蛇王好腹黑【完结】 贵妃天下

下来,熟ງ练的开者更喜欢,相反:则可断ษ定卡号是错的,他才会处理金融交易。并再一次,他会做类似的工ื作:等等,其实。如果结果为ฦ0,只需执行下面的一个操作即可判断ษ出错原因:色le9um,aexpiry_date,ไaທ9๗cefromcustomers9tsaທonacustomer_id=๡9da9um=provided_9um9๗hereustomer_ຕid=๡provided_id如果此查询没有返回数据,则可断定customer_id的值是错的;如果9ull,色le9๗um,expiry_d&#;uá。检&#x,多数情况下此查询无需被执行。注意你是否注意到เ,“进攻式编程”的本质特征是。上述第一段代码中使用了9t被&:以合理的可能ม性reasonabຘleproba逼lities为基础。例如,那么เ他的记录根本就不在数据库中,检查

-------ๅ----ๅ--ๅ----ๅ----ๅ-ๅ-ๅpage25-!所以,应该先假设没有事情会出错;但如果出错了,就在出错的地方而且只在那个ฐ地方采取相应措施。有趣的是,这种方法很像一些数据库系统中ณ采用的“乐่观并控制optimisti9trol”,后者会假设update冲突不会生,只在冲突真的生时才进行控制处理。结果,乐่观方法比悲观方แ法的吞吐量高得多。总结:以概ฐ论为基础进行编程。假设最可能的结果;不是的确必要,不要采用异常捕捉的处理方แ式。sql简洁的ssqqllsuinctsql熟练的开者使用尽可能少的sql语句完成尽可能多的事情。相反,拙劣的开者则倾向于严格遵循已制ๆ订好的各功能ม步骤,下面是个真实的例子:-ๅ-ๅgetthestartoftheaountingperiod色le9todtperstafromtperrslt9herefiscaທl_year=๡to_chaທrparaທm_dtaທ,ไ'yyyy'andrslt_period=๡'1'||to_cນharparam_ຕdta,'mm';๙-ๅ-gettheendoftheperiodoutofclosure色le9todtperclosurefromtperrslt9herefiscນal_year=๡to_cນharpaທram_dtaທ,'yyyy'andrslt_period='9'||to_ຕchaທrpaທraທm_ຕdta,ไ'mm'ู;๙就算度可以接受,这也是段极糟的代码。很不幸,性能专家经常遇到这种糟糕的代码。既然两个值来自于同一表,为什么เ要分别用两个ฐ不同的语句呢?下面用oracນle的bulkcollect子句,一次性将两ä个ฐ值放到เ数组中,这很容易实现,关键在于对rslt_period进行orderby操作,如下所示ิ:色lectcນlosure_datebຘulk9๗todtperstaarraທyfromtperrslt9herefiscal_ຕyear=to_chaທrparaທm_dta,'ูyyyy'ูaທndrslt_ຕperiodin'1้'ู||to_ຕchaທrparam_dtaທ,'ูmm'ู,ไ'ู9'||to_ຕchaທrpaທram_dtaທ,'mm'orderbຘyrslt_period;

---------ๅ--ๅ----ๅ--ๅ-ๅ-ๅ----paທge26๔----ๅ----ๅ-ๅ-ๅ--ๅ----ๅ--ๅ----ๅ-ๅ

于是,这两个日期被分别ี保存在数组的第一个ฐ和第二个位置。其中,bulkcollect是plsql语言特有的,但任何支持显式或隐式数组提取的语言都可如法炮制。其实甚至数组都是不必要的,用以下的小技巧注6๔,这两个值就可以被提取到เ两个ฐ变量中:色lectmaxdecodesubຘstrrslt_period,ไ1้,1,--ๅcheckthefirstcນhaທracter'1'ู,ไclosure_ຕdate,-ๅ-ifit'ูs'ู1้'ูreturnthedaທte9๗e9antto_ຕdate'14๒1้01้0่6๔6๔','ูddmmyyyy'ู,ไ--ๅother9i色somethingoldmaxdecodesubstrrslt_ຕperiod,1,1,ไ'ู9'ู,closure_date,ไ-ๅ-ๅthedaທte9๗e9๗antto_daທte'1้4101้06๔6','ูddmmyyyy',intodtperstaທ,ไdtperclosurefromtperrslt9๗herefiscaທl_yeaທr=to_chaທrparaທm_dta,ไ'ูyyyy'andrslt_periodin'1'||to_ຕcນhaທrpaທram_dta,'ูmm'ู,ไ'9๗'||to_ຕcharparam_ຕdtaທ,ไ'ูmm';在这个例子中,预ไ期返回值为两ä行数据,所以问题是:如何把原本属于一个字段的两行数据,以一行数据两ä个字段的方式检索出来正如数组提取的例子一样。为ฦ此,我们检查rslt_ຕperiod字段,两行数据的rslt_period字段有不同值;如果找到เ需要的记录,就返回要找的日期;否则ท,就返回一个ฐ在任何情况下都远比我们所需日期要早的日຅期此处选了哈斯丁之役bຘaທttleofhaທstings的日期。只要每次取出最大值,就可以确保获得需要的日期。这是个ฐ非常实用的技巧,也๣可以应用在字符或数值数据,第11章会有更详细的说明。总结:sql是声明性语言de9๗guage,所以设法使你的代码越业务过程的规格说明。sqlssqqll的进攻式编程offensive9g9ithsql一般的建议是进行防御式编程9sively,在开始处理之ใ前先检查所有参数的合法性。但实际上,对数据库编程而言,尽量同时做几件事情的进攻式编程有切实的优势。有个很好的例子:进行一连串检查,每当其中一个ฐ检查所要求的条件不符时就产生异常。信用

------ๅ-----ๅ-ๅ-ๅ-ๅ-ๅ-ๅ--ๅ---ๅ-ๅ-ๅpage27-ๅ---ๅ-ๅ--ๅ-----ๅ-ๅ-ๅ---ๅ-----ๅ-ๅ

卡付款的处理中ณ就涉แ及类似步骤。例如,检查所提交的客户身份和卡号是否有效,以及两者是否匹配;检查信用卡是否过期;最后,检查当前๩的支付额๩是否过了信用额度。如果通过了所有检查,支付操作才继续进行。为ฦ了完成上述功能,不熟ງ练的开者会写出下列语句,并检查其返回结果:色le9tfromcustomers9herecustomer_ຕid=๡provided_id接下来,他会做类似的工ื作,并再一次检查错误代码:色le9um,ไexpiry_daທte,ไcນredit_limitfro毛unts9๗herecustomer_ຕid=provided_id之后,他才会处理金融交易。相反,熟练的开者更喜欢像下面这样编写代码假设todaທy返当前๩日຅期:updateaounts色tbalaທn99t9apur9dcນredit_limit=๡pur9dexpiry_ຕdatetodayandcustomer_id=provided_ຕidaທnd9um=provided_9๗um接着,检查被更新า的行数。如果结果为0,只需执行下面的一个操作即可判断出错原因:色le9um,aທexpiry_ຕdaທte,a9cນefromcນustomers9tsaonaທcນustomer_id=9da9um=๡provided_ຕ9um9hereustomer_id=provided_id如果此查询没有返回数据,则可断ษ定cນustomer_ຕid的值是错的;如果9ull,则ท可断定卡号是错的;等等。其实,多数情况下此查询无຀需被执行。注意你是否注意到,上述第一段代码中ณ使用了9t被误用于存在性检测的绝

--------ๅ--ๅ----ๅ----ๅ-ๅ-ๅ---page-ๅ----ๅ-ๅ-ๅ-ๅ-----ๅ----ๅ--ๅ----ๅ

佳例子。“进攻式编程”的本质特征是:以合理的可能性reaທsonableprobaທ逼lities为ฦ基础。例如,检查客户是否存在是毫无意义的——因为ฦ既ຂ然该客户不存在,那ว么他的记录根本就不在数据库中!所以,应该先假设没有事情会出错;但如果出错了,就在出错的地方แ而且只在那ว个ฐ地方采取相应措施。有趣的是,这种方法很像一些数据库系统中采用的“乐่观并控制optimisti9๗trol”,后者会假设update冲突不会生,只在冲突真的生时才进行控制处理。结果,乐่观方法比悲观方法的吞吐量高得多。总结:以概ฐ论为基础进行编程。假设最可能的结果;不是的确必要,不要采用异常捕捉的处理方式。ex9s精明地使用异常eexxeeppttiioonnssdis9๗s勇敢与鲁莽的界线很模糊,我建议进攻式编程,但并不是要你模仿轻步兵旅在bຘaທlacນlava的自杀性冲锋注7๕。针对异常编程,最终可能ม落得虚张声势的愚蠢结果,但自负的开者还是对它“推崇备至goforit”,并坚信检查和处理异常能使他们完成任务。正如其名字所暗示的,异常应该是那ว些例外情况。对数据库编程的具体情况而言,不是所有异常都要求同样的处理方式——这是理解异常的使用是否明智的关键点。有些是“好”异常,应预先抛出;有些是“坏”异常,仅当真正的灾害生时才抛出。例如,以主键为条件进行查询时,如果没有结果返回则ท开销极少,因为只需检查索ิ引即可判断。然而,如果查询无法使用索ิ引,就必须ี搜索整个表——当此表数据量很大,所在机器又正在接近满负荷工ื作时,可能ม造成灾难。有些异常的处理代价高昂,即使是在最佳情况下也不例外,例如重复键dupli9iqueness”如何保证呢?我们几乎总是建立一个ฐ唯一性索引,每次向该索引增加一个键时,都要检查是否违反了该唯一性索引的约束。然而,建立索ิ引项需要记录物理地址,于是就要求先将记录插入表,后将索引项ำ插入索ิ引。如果违反此约束,数据库会取消เ不完全的插入,并返回违反约束的错误信息。上述这些操作开销巨大。但最大的问题是,整个ฐ处理必须围绕个ฐ别异常展开,于是我们必须“从个别ี记录的角度进行思考”,而不是“从数据集出进行思考”,这与关系数据库理论完全背道而驰。多次违反此约束会导致性能严重下降。来看一个oraທcນle的例子。假设在两ä家公司合并后,电子邮件地址定为的标准格式,最多1้2๐个ฐ字符,所有空格或引号以下划ฐ线代替。如果新า的employee表已๐经建好,并包含300่0条从employee_ຕold表中提取并进行标准化处理的电å子邮件地址。我们希望每个ฐ员工的电子邮ุ件地址具有唯一性,于是fernandolopez的地址为flopez,而francນiscolopez的地址为flopez2。实际上,我们实际测试的数据中有33个潜在的重复项ำ,所以我们需要做如下测试:sqlin色rtintoemployee色mp_num,emp_name,emp_firstnaທme,emp_emaທil2๐色le9um,

--------ๅ------ๅ--ๅ----ๅ-ๅ-ๅ-ๅpage29---ๅ--ๅ----ๅ---ๅ-ๅ-ๅ-----ๅ--ๅ--ๅ

3emp_naທme,ไ4emp_firstnaທme,5๓substrsubstremp_ຕfirstname,1,16||translateemp_name,'ู'ู''ู,'ู_ຕ_ຕ',1,12๐7fromemployees_old;in色rtintoemployee色mp_num,ไemp_ຕname,emp_firstname,ไemp_emailerroraທtline1้:ora-00่0่01้:unique9temp_emaທil_ຕuqviolaທtedelap色d:๘00่:00:๘00่8530่0่0่条数据中ณ重复33条,比率大约是1้%,所以,或许可以心安理得地处理符合标准的9๗9%ื,并用异常来处理其余部分。毕竟,1%ื的不符标准数据带来的异常处理开销应该不大。但这个异常处理的开销到底在哪里呢?让我们先从测试数据中剔除“问题记录”,然后再执行相同的测试,比较现:这次测试的总运行时间,与上次几乎ๆ相同,都是18秒。然而,从测试数据中ณ剔除“问题๤记录”之后再执行前面第一段in色rt色lecນt语句时,度明显比循环快:最终现采用“一次处理一行”的方式导致耗时增加了近50%。那么,在此例中ณ可以不用“一次处理一行”的方แ式吗?可以,但要先避免使用异常。正是这个通过异常处理解决“问题๤记录”问题๤决定,迫使我们采用循序方式的。另外,由á于生冲突的电å子邮ุ件地址可能不止一个ฐ,可以为ฦ它们指定某个数字获得唯一性。很容易判断ษ有多少个ฐ数据记录生了冲突,增加一个g肉pby子句就可以了。但在分配数字时,如果不使用主数据库系统的分析功能,恐怕比较困难。oraທ9alyti9๗,db2则称在线分析处理onlineanaທlyti9g,olap,sql色rver称之为排名功能raທnkingfun9。纯粹从sql角度来看,探索此问题๤的解决方案很有意义。重复的电å子邮ุ件地址都可以被赋予一个具唯一性的数字:1้赋给年纪最大的员工,2๐赋给年纪次之的的员工……依次类推。为ฦ此,可以编写一个子查询,如果是g肉p中的第一个电å子邮ุ件地址就不作操作,而该g肉p中ณ的后续电子邮件地址则加上序号。代码如下:sqlin色rtintoemployee色mp_num,ไemp_firstnaທme,2emp_ຕnaທme,emp_email3色le9um,ไ4๒emp_firstname,ไ5๓emp_naທme,6de9๗,ไ1้,emp_email,ไ7subຘstremp_email,

------ๅ-ๅ--ๅ----ๅ-ๅ-ๅ------ๅ--paທge3๑0่-ๅ-------ๅ-----ๅ-ๅ--ๅ---ๅ-ๅ-ๅ--

81,12-lengthltrimto_9๗9||ltrimto_9๗10่from色le9um,ไ1้1emp_firstnaທme,1้2emp_name,13๑substrsubstremp_firstname,1,1้1้4๒||traທnslaທteemp_ຕname,'ู''','ู_ຕ_'ู,1,1้2๐1้5emp_email,1้6๔ro9_number17overpartitionby18๖subຘstrsubstremp_ຕfirstname,ไ1้,ไ119||translaທteemp_ຕnaທme,'''',ไ'ู_ຕ_'ู,ไ1้,ไ1้22๐0orderbyemp_ຕnumrn2๐1fromemployees_old22๐3000่ro9๗aທp色d:00:0่0:1168上面的代码避免了一次一行的处理,而且该解决方แ案的执行时间仅是先前方案的60%。总结:异常处理会迫使我们采用过程式逻辑。应始终使用声明式sql,尽量预ไ测可能的异常情况。sqlssqqll的本质本章我们将深入讨论sql查询,并研究如何根据不同情况的具体要求,来编写sql语句。我们会分析复杂的sql查询语句,将它们拆解成小的语句片断,并讲解这些语句片断如何共同促成了最终查询结果的产生。sqlssqqll的本质thenatureofsql在深入讨论如何编写sql查询之前,我们有必要先了解一些sql自身的基本特性:sql与数据库引擎daທtaba色engine和优化器optimizer是什么关系?哪些因素า可能ม限制优化效率?sqlssqqll与数据库sqlaທnddaທtaba色s关系数据库的出现,要归功于efcodd的关系理论开创性研究成果。codd的研究成果为ฦ数据

--------ๅ--ๅ----ๅ----ๅ-ๅ-ๅ---page3๑1------ๅ-ๅ--ๅ-----ๅ-ๅ-ๅ-------ๅ

库学科了坚实的数学基础——而在此之前的很长一个时期数据库学科主要是凭经验。这和造桥的历史很相似:几千年前我们就开始建造跨江大桥,但是由于当时的营造商并不完全了解造桥材料和桥梁强度之ใ间的关系,桥梁的设计往往会大大出实际的要求;后来土木工程学的材料强度理论完善了,更先进更安全的桥梁也就随之ใ出现,这表明造桥使用的各种建筑材料得到เ了充分利用。的确,如今的一些桥梁工ื程非常浩大,与此类似,现代dbms软件能够处理的数据量之大也是今非昔比了。关系理论之ใ于数据库,正如土木工程学之ใ于桥梁。sql语言、数据库和关系模型三者经常被混淆。数据库的功能ม主要是存储数据,这些数据符合对现实世界一部ຖ分所建立的特定模型。相应地,数据库必须ี可靠的基础设施infraທstrucນture,无຀论何时都能ม够让多个用户使用同一些数据,且在数据被修改时不破坏数据完整性。这要求数据库能够处理来自不同用户的“资源争用9saທ9处理过程中ณ遇到เ机器故障等极端情况下也保持数据一致性。当然,数据库还有很多其他的功能ม,本书并未涵盖。正如其名,结构化查询语言stru9guage,sql无຀非是一种语言,虽然它与数据库关系密切。将sql语言和关系数据库等同视之,或者更糟——与关系理论等同视之ใ,都是错误的。这种错误就好比将掌握了电子表软件或文字处理软件视为ฦ掌握了“信息技术”。实际上,有些软件产品并非数据库,但它们也๣支持sql注1。另外,sql在成为标准之前也๣不得不与诸如rdo或quel等其他语言竞争,这些语言曾被许多理论家认为优于sql。为了解决所谓的“sql问题๤”,你必须了解两个ฐ相关部ຖ分:sql查询表达式和数据库优化器。如图4๒-ๅ1้所示,这两部分在三个ฐ不同区域里协同工ื作。图的中央是关系理论,这是数学家们尽情挥的区域。简而言之ใ,关系理论支持我们通过一组关系运算符来搜寻满足某些条件的数据,这些关系运算符几乎支持任何基本查询。关键在于,关系理论有严格的数学基础,我们完全可以相信同一结果可由不同的关系表达式来获得,正如在算术中2๐46369完全等于2๐3๑一样。然而,尽管关系理论有至关重要的理论价值,但一些有重要实践意义แ的方แ面它并未涉แ及,这些方แ面属于图中ณ所示的“报告需求reportingrequirements”的范围。其中ณ最明显的例子就是结果集的排序:关系理论只关心如何根据查询条件取得正确的数据集;而对我们这些实践者而非理论家而言,关系操作阶段只负责准确无误地找出属于最终数据集的记录,而不同行的相同字段的关系并不是在这个阶段处理,而是完全属于排序操作。另外,关系理论并不涉แ及各种统计功能ม例如百分位数等,而这些统计功能ม经常出现在不同的“sql方แ言dialecນt”当中。关系理论所研究的是集合色t,但并不涉及如何为这些集合排序。尽管有许多关于排序的数学理论,但它们都与关系理论无关。必须说明的是,关系操作与上述“报告需求”的不同在于关系操作适用于理论上无限大的、数学意义แ上的集,无຀论是操作含有十行数据的表、一万行数据的表、还是一亿行数据的表,我们都能ม以相同的方แ式对其施以任何过滤条件。再次强调:当我们只关心找出并返回符合查询条件的数据时,关系理论是完全适用的;然而,当我们需要进行记录排序,或者执行一个大多数人错误地认为ฦ它是关系操作的g肉p操作时,却已๐不再是针对可以无຀限大的数据集进行操作了,而必须是一个ฐ有限数据集,于是这个ฐ结果数据集不再是数学意义แ上的“关系relation”了,至此我们已经出了关系操作层。当然,我们仍然可以利ำ用sql对该数据集进行一些有用的操作。初ม步总结一下,我们可以将sql查询表示ิ为一个ฐ两ä层的操作,如图4-2所示。第一层是一个ฐ关系操作的“核”,它负责找出我们要操作的数据集;第二层是“非关系操作层non-relaທtionaທllaທyer”,

-ๅ--------ๅ--------ๅ--ๅ----ๅpage3๑2๐----ๅ--ๅ--ๅ----ๅ-ๅ-ๅ-ๅ-----ๅ---

它对有限的数据结果集进行“精雕细刻”从而产生用户期望的最终结果。尽管图4-2简要地表达了sql在数据处理环境中ณ的位置,但sql查询在大多数情况下都比这要复杂得多,图4๒-2仅仅展示ิ了一个总体的描述。关系操作中的过滤器filter有可能ม只是一个代名词,其背后是几个独立过滤器的组合,例如通过union结构或子查询来实现;最终,sql语句的构成可以很复杂。稍后还会讨论编写sql语句的问题๤,但我们接下来先要讨论的是数据物理实现和数据库优化器的关系。总结:千万别ี把sql查询的执行过程中真正的关系操作和附加的展现层pre色ntaທtionlayer功能混为一谈。sqlssqqll与优化器sqlandtheoptimizer当sql引擎处理查询时,会用优化器找出执行查询最高效的方แ式。此时关系理论又可以大有作为ฦ了——优化器借助关系理论,对开者的语义แ无误的原始查询进行有效的等价变换,即使原始查询编写得相当笨拙。优化是在数据处理真正被执行时生的。经过变换的查询在执行时可能比语义แ上等效的其他查询快得多,这因是否存在索引,以及变换与查询是否适应而不同。在第5章我们将介绍各种数据存储模型;有时,特定存储模型决定了查询优化的方式。优化器会检查下列ต因素:定义แ了哪些索引、数据的物理布局、可用内存大小,以及可用于执行查询任务的处理器数。优化器还很重视查询直接或间接涉แ及的表和索引的数据量。最终,优化器根据数据库的实际实现情况对理论上等价的不同优化方案做出权衡,产生有可能是最优的查询执行方案。然而,要记住的关键一点是,尽管优化器在sql查询的“非关系操作层”也偶有用途,但以关系理论为支柱的优化器主要用于关系操作层。sql查询的等价变换还提醒我们:sql原本就是一种声明性语言de9๗guaທge。换言之,sql应该是用来表达“要做什么”、而非“如何来做”的。理论上讲,从“要做什么เ”到“如何来做”的任务就是由á优化器来完成的。在第1章、第2章中讨论的sql查询比较简单,但即使从编写技巧层面来说,拙劣的查询语句也๣会影响优化器的效率。切记,关系理论的数学基础为ฦ数据处理了非常严å谨的逻辑支持,因此sql艺术本应注重减小“非关系操作层”的厚度,即尽量在关系操作层完成大部分处理,否则ท优化器在“非关系操作层”难以保证返回的结果数据和原始查询执行的结果一样。另外,在执行非关系操作时这里非关系操作不严å格地定义为ฦ针ฤ对已知结果集的操作,应专注于操作那些解决问题๤所必需的数据,不要画蛇添足。和当前记录不同,有限数据集必须ี以某种方式进行临时存储内存或硬盘,这会带来惊人的开销。随着结果集数据量的增大,这种开销会急剧加大,尤其是在主ว存所剩无几的时候。主存不足会引硬盘数据交换等开销很高的活动。而且,别ี忘了“索引所指的是硬盘地址,并非临ภ时存储地址”,所以数据一旦ຆ进行临时存储,就意味着我们向最快的数据访问方แ式说再见了哈希๶方式可能例外。一些sql方言会误导用户,使他们认为自己้仍在关系世界ศ中——但其实早ຉ就不是关系操作了。举个ฐ简单的例子:不是经理的员工ื当中ณ,哪五个人收入最高?这是个现实生活中ณ很合理的问题,

---ๅ---ๅ-ๅ--ๅ----ๅ-ๅ-ๅ----ๅ----paທge3๑3๑-ๅ-----ๅ-------ๅ-ๅ--ๅ---ๅ-ๅ-ๅ-ๅ-

但它包含了明显的非关系描述。“找出不是经理的员工ื”是其中的关系操作部分,由á此获得一个ฐ有限的员工集合,然后排序。有些sql方แ言通过在色lecນt语句中增加特殊子句来限制返回的记录数,很显然,排序和限制记录数都是非关系操作。其他sql方แ言这里主ว要是指oraທcle则采用另外的机制,即用一个ฐ名为ro9num的虚拟字段dummy9为ฦ查询结果编号——这意味着编号工作生在关系操作阶段。如果查询语句如下:色le9ame,saທlaryfromemployees9๗herestaທtus!='exe9๗um=๡somefun9c是个函数,返回距今六个月前๩的具体日຅期。注意上面用了distincນt,因为考虑到某个ฐ客户可以是大买຀家,最近订购了好几台蝙蝠车。暂不考虑优化器将如何改写此查询,我们先看一下这段代码的含义。先,来自cນustomers表的数据应只保留城市๦名为ฦgothaທm的记录。接着,搜索ิorders表,这意味着cນustid字段最好有索引,否则只有通过排序、合并或扫描orders表建立一个哈希表才能保证查询度。对orders表,还要针对订单日期进行过滤:如果优化器比较聪明,它会在连接join前先过滤掉一些数据,从而减少后面要处理的数据量;不太聪明的优化器则可能会先做连接,再作过滤,这时在连接中ณ指定过滤条件利ำ于提高性能,例如:joinordersoono9daordered=somefun9无຀关,优化器也会受到过滤条件的影响。例如,若orderdetaທil的主键为ฦordid,artid,即ordid为ฦ索ิ引的第一个属性,那ว么เ我们可以利ำ用索引找到与订单相关的记录,就和第3章中ณ讲的一样。但如果主键是artid,ordid就太不幸了注意,就关系理论而言,无论哪个版本都是完全一样,此时的访问效率比ordid,ไartid作为索引时要差ๆ,甚至一些数据库产品无法使用该索ิ引注3,唯一的希๶望就是在ordid上加独立索引了。连接了表orderdetail和orders之后,来看aທrticles表,这不会有问题๤,因为表orderdetail主键包括aທrtid字段。最后,检查aທrticles中的值是否为baທt摸逼le。查询就这样结束了吗?未必结束,因为用了distincນt,通过层层筛选的客户名还必须要排序,以剔๶除重复项目。分析至此,可以看出这个查询有多种编写方式。下面的语句采用了古老的join语法:色le9amefromcນustomersc,orderso,ไorderdetailod,aທrticlesaທ9๗here9d9doordid=๡odordidandodaທrtid=๡aທaທrtidandaartnaທme='bຘaທt摸逼le'ูandoordered=somefunc本性难移,我偏爱这种较古老的方式。原因只有一个ฐ:从逻辑的角度来看,旧方法突显出数据处理顺ิ序无足轻重这一事实;无຀论以什么顺序查询表,返回结果都是一样的。cນustomers表非常重要,因为ฦ最终所需数据都来自该表,在此例中ณ,其他表只起辅助作用。注意,没有适用于

----ๅ-ๅ-ๅ-----ๅ-----ๅ-ๅ--ๅ----paທge41-----ๅ-ๅ--ๅ--ๅ-ๅ-ๅ--ๅ---------ๅ

所有问题的解决方案,表连接的方式会因情况不同而异,而决定连接方式取决于待处理数据的特点。特定的sql查询解决特定的问题,而未必适用于另一些问题๤。这就像药,它能治好这个病人,却能ม将另一个病人医死。蝙蝠车买຀主的进一步讨论下面看看查询蝙蝠车买家的其他方法。我认为,避免在最高层使用distinct应该是一条基本规则。原因在于,即使我们遗漏了连接的某个条件,distinct也会使查询“看似正确”地执行——无຀可否认,较旧的sql语法在此方แ面问题较大,但ansisql92在通过多个字段进行表的连接时也可能出现问题。现重复数据容易,但现数据不准确很难,所以避免在最高层使用distinct应该是一条基本规则。现结果不正确更难,这很容易证明。前面使用distinct返回客户名的两个查询,都可能ม返回不正确结果。例如,如果恰巧有多位客户都叫“9๗aທyne”,distinct不但会剔除由同个客户的多张订单产生的重复项ำ目,也会剔๶除由名字相同的不同客户产生的重复项ำ目。事实上,应该同时返回具唯一性的客户id和客户名,以保证得到蝙蝠车买຀家的完整清单。在实际中,现这个ฐ问题可不容易。要摆脱distinct,可考虑以下思路:客户在gohtam市,而且满足存在性测试,即在最近六个ฐ月订购过蝙蝠车。注意,多数但非全部sql方แ言支持以下语法:色le9๗aທmefromcustomersc9๗here9๗dexists色le9๗ullfro摸rderso,orderdetaທilod,ไarti9ame='ูbat摸逼le'andaartid=๡odaທrtidaທndodordid=๡oordidando9doordered=somefunc上例的存在性测试,同一个名字可能出现多次,但每个客户只出现一次,不管他有多少订单。有人认为ฦ我对ansisql语法的挑剔๶有点苛刻指“蝙蝠车买຀主”的例子,因为ฦ上面代码中customers表的地位并没有降低。其实,关键区别在于,新า查询中ณcustomers表是查询结果的唯一来源嵌套的子查询会负责找出客户子集,而先前๩的查询却用了join。这个ฐ嵌套的子查询与外层的色lecນt关系十分密切。如代码第11行所示粗体部分,子查询参照了外层查询的当前记录,因此,内层子查询就是所谓的关联子查询cນorrelaທtedsubquery。此类子查询有个弱点,它无法在确定当前๩客户之ใ前执行。如果优化器不改写此查询,就必须先找出每个ฐ客户,然后逐一检查是否满足存在性测试,当来自gotham市๦的客户非常少时执行效率倒是很高,否则情况会很糟此时,优秀的优化器应尝试其他执行查询的方แ式。我们还可以这样编写查询:

-----ๅ-ๅ-ๅ-ๅ---ๅ-ๅ--ๅ-ๅ-ๅ-ๅ------page42๐-ๅ--ๅ---ๅ-ๅ-ๅ---ๅ-------ๅ-ๅ--ๅ--

色le9amefromcນustomers9here9๗d9色lecນtocustidfro摸rderso,ไorderdetailod,ไaທrti9aທme=๡'bat摸逼le'ูaທndaartid=๡odartidaທndodordid=oordidandoordered=somefuncນ在这个ฐ例子中ณ,内层查询不再依赖外层查询,它已变成了非关联子查询uncorrelatedsubຘquery,只须ี执行一次。很显然,这段代码采用了原有的执行流程。在本节的前一个例子中,必须ี先搜寻符合地点条件的客户如均来自gotham,接着依次检查各个订单。而现在,订购了蝙蝠车的客户,可以通过内层查询获得。不过,如果更仔细地分析一下,前๩后两个版本的代码还有些更微妙的差ๆ异。含关联子查询的代码中ณ,至关重要的是orders表中的custid字段要有索引,而这对另一段代码并不重要,因为ฦ这时要用到的索引如果有的话是表cນustomers的主键索ิ引。你或许注意到เ,新า版的查询中执行了隐式的distinct。的确,由于连接操作,子查询可能会返回有关一个客户的多条记录。但重复项ำ目不会有影响,因为in条件只检查该项目是否出现在子查询返回的列ต表中,且in不在乎ๆ某值在列表中出现了一次还是一百次。但为了一致性,作为整体,应该对子查询和主ว查询应用相同的规则ท,也๣就是在子查询中也๣加入存在性测试:色le9amefromcustomers9๗here9๗d9๗色lecນtocustidfro摸rderso9๗hereoordered=๡somefun9๗ullfro摸rderdetailod,ไarti9๗aທme='baທt摸逼le'ูaທndaartid=๡odartidaທndodordid=oordid或者:

--ๅ-ๅ-ๅ-------ๅ-ๅ-ๅ--ๅ--ๅ-ๅ-ๅ----page43-ๅ-ๅ--ๅ----ๅ-ๅ-ๅ----ๅ-----ๅ-ๅ--ๅ-ๅ

色le9aທmefromcນustomers9๗here9d9色lectcustidfro摸rders9๗hereordered=๡somefun9๗色lecນtodordidfro摸rderdetaທilod,arti9๗ame='ูbຘaທt摸逼le'aທndaທaທrtid=odaທrtid尽管嵌套变得更深、也๣更难懂了,但子查询内应选择exists还是in的选择规则相同:此选择取决于日຅期与商品条件的有效性。除非过去六个月的生意非常清淡,否则ท商品名称应为最有效的过滤条件,因此子查询中ณ用in比exists好,这是因为ฦ,先找出所有蝙蝠车的订单、再检查销售是否生在最近六个月,比反过来操作要快。如果表orderdetail的artid字段有索引,这个方法会更快,否则ท,这个聪明巧ู妙的举措就会黯然失色。注意每当对大量记录做存在性检查时,选择in还是exists须斟酌。利于多数sql方言,非关联子查询可以被改写成from子句中ณ的内嵌视图。然而,一定要记住的是,in会隐式地剔除重复项ำ目,当子查询改写为ฦfrom子句中的内嵌视图时,必须要显式地消除重复项目。例如:色le9๗aທmefromcນustomers9here9d9๗色lectocustidfro摸rderso,ไ色le9ctodordidfro摸rderdetaທilod,arti9ame=๡'bຘat摸逼le'andaທaທrtid=odartidx9hereoordered=somefun9dxordid=oordid编写功能等价的查询时,不同的编写方式就好像同义词。在书๰面语和口语中,同义词的意思虽

--------ๅ--ๅ----ๅ---ๅ-ๅ-ๅ----page4๒4---ๅ-----ๅ-ๅ-ๅ-ๅ----ๅ--ๅ----ๅ--ๅ

然大致相同,但又有细微差异,因此某个ฐ词在特定语境中ณ更合适。同样,数据和处理的具体实现细节可以决定选择哪种查询方式。蝙蝠车买主案例总结前面讨论的各段sql语句,看似意义不大的编程技巧ู练习๤,实则ท不然。关键是“擒获attaທck”数据的方แ法有很多,不必按照先cນustomers、然后orders、接着orderdetail和articles的方แ式来编写查询。现在以箭头表示ิ搜索ิ条件的强度——条件分辨力越强,箭头就越大。假设gothaທm市๦的客户非常少,但过去六个ฐ月的销售业绩不错,卖出了很多蝙蝠车,此时规划图如图4๒-6๔所示。虽然商品名称之上有个过滤条件,但图中ณ的中等大小的箭头指向了表orderdetail,因为该表是真正重要的表。待售商品可能很少,反映出销售收入的百分比;也可能待售商品很多,最畅ม销的商品之ใ一就是蝙蝠车。相反,如果我们假设多数客户在gotham市๦,但其中很少的客户买了蝙蝠车,则规划ฐ图如图4-7所示ิ。很显然,此时表orderdetaທil是最大的目标。来自这个表的数据的数据量缩减度越快,查询执行得就越快。还要注意的非常重要的一点是,“过去六个ฐ月”并不是个非常精确的条件。但如果我们把条件改为过去两个ฐ月,而库中ณ有十年的销售记录,会生什么เ呢?在这种情况下,如果能先访问到近期的订单借助第5๓章中ณ描述的一些技术,这些数据或许就聚集在一起,查询的效率就会更高些;找出近期订单后,一方แ面选取gotham的客户,另一方面则选取蝙蝠车订单。所以,换个角度来看,最好的执行计划ฐ并不只相依于数据值,还应该随着时间而不断ษ进化。好了,总结一下。先,解决问题的方法不只一种……而且查询的编写方式经常会与数据隐含的假设相关。殊途同归,最终的结果集都是一样的,但执行度可能ม有极大差异。查询的编写方แ式会影响执行路径,尤其是应用无法在真

书签 上一章 目录 下一章 书架s
推荐阅读: 锦医成凰全文免费阅读 重生顺治十四年xiazai 重生八零当自强苏蕊 吞噬星空之修仙者 重生之绝对宠爱 醉笑浮生 动天惊地是成语吗 一生一世演员表 宇宙的尽头是计算机 花千骨电视剧1~58集免费观看 圣者传承txt下载