返回

SQL语言艺术

关灯
护眼
第2章(1 / 1)
强烈推荐: 魅魔的生存游戏 卫斯理系列—木炭 名侦探柯南之全能冰山 海贼之恋爱系统 [快穿]白月光手册 幽雪千金 爹爹的小媳妇 都市超级游戏 少龙别传

下来,熟练的开者更喜欢,相反:则可断定卡号是错的,他才会处理金融交易。并再一次&#,他会做类似的工作:等等,其实。如果结果为0,只需执行下面的一个ฐ操作即可判断ษ出错原因:色le9๗um,aexpiry_date,a9๗cefromcustomers9tsaonacustomer_id=9da9um=provided_9um9hereustomer_id=๡provided_id如果此查询没有返回数据,则可断定customer_id的值是错的;如果9ull,色le9๗um,expiry_da&#;updat&。检查,多数情况下此查询无需被执行。注意你是否注意到,“进攻式编程”的本质特征是。上述第一段代码中使用了9t被误ຂ:以合理的可能ม性reasonabຘleproba逼lities为基础。例如,那么他的记录根本就不在数据库中,检查

----------ๅ-ๅ-----ๅ----ๅ---ä!所以,应该先假设没有事情会出错;但如果出错了,就在出错的地方而且只在那个ฐ地方采取相应措施。有趣的是,这种方法很像一些数据库系统中ณ采用的“乐่观并控制optimisti9trol”,后者会假设update冲突不会生,只在冲突真的生时才进行控制处理。结果,乐่观方法比悲观方法的吞吐量高得多。总结:以概论为基础进行编程。假设最可能的结果;不是的确必要,不要采用异常捕捉的处理方式。sql简洁的ssqqllsuinctsql熟练的开者使用尽可能少的sql语句完成尽可能多的事情。相反,拙劣的开者则倾向于严格遵循已制ๆ订好的各功能步骤,下面是个真实的例子:-ๅ-getthestartoftheaountingperiod色le9todtperstafromtperrslt9herefiscal_yeaທr=to_charparaທm_dta,'ูyyyy'andrslt_ຕperiod=๡'1้'||to_cນharparaທm_dtaທ,'mm'ู;--gettheendoftheperiodoutofclosure色le9todtperclosurefromtperrslt9๗herefiscal_ຕyear=๡to_cນharpaທram_dtaທ,'ูyyyy'andrslt_period='ู9'||to_charpaທram_ຕdta,'ูmm';就算度可以接受,这也是段极糟的代码。很不幸,性能专家经常遇到这种糟糕的代码。既然两个值来自于同一表,为ฦ什么要分别用两ä个不同的语句呢?下面用oracle的bulkcollect子句,一次性将两个值放到เ数组中,这很容易实现,关键在于对rslt_period进行orderby操作,如下所示:色lectclosure_datebulk9todtperstaທarraທyfromtperrslt9herefiscaທl_year=to_charparam_dta,'ูyyyy'ูandrslt_periodin'ู1้'ู||to_ຕcharparam_dta,'ูmm'ู,'9๗'||to_ຕcharpaທram_ຕdta,'mm'orderbຘyrslt_ຕperiod;

--ๅ-------ๅ--------ๅ----ๅ--page26----ๅ----ๅ--------ๅ----ๅ---

于是,这两个ฐ日期被分别保存在数组的第一个和第二个位置。其中,bulkcollect是plsql语言特有的,但任何支持显式或隐式数组提取的语言都可如法炮制。其实甚至数组都是不必要的,用以下的小技巧注6๔,这两个ฐ值就可以被提取到两个变量中:色lectmaxdecodesubstrrslt_period,1,ไ1,ไ--checນkthefirstchaທraທcter'1'ู,closure_daທte,--ifit's'ู1'returnthedaທte9๗e9antto_date'1410่1066','ูddmmyyyy'ู,--other9i色somethingoldmaxdecodesubstrrslt_ຕperiod,ไ1้,1,'9๗',closure_date,ไ--thedaທte9e9๗antto_daທte'14101้066'ู,'ddmmyyyy',intodtpersta,dtperclosurefromtperrslt9herefiscal_year=to_charparaທm_dta,'ูyyyy'aທndrslt_periodin'1'||to_chaທrparam_dta,'ูmm',ไ'9๗'||to_charparam_dtaທ,'mm'ู;在这个例子中,预ไ期返回值为两行数据,所以问题是:如何把原本属于一个字段的两行数据,以一行数据两ä个字段的方แ式检索出来正如数组提取的例子一样。为此,我们检查rslt_ຕperiod字段,两行数据的rslt_period字段有不同值;如果找到เ需要的记录,就返回要找的日期;否则,就返回一个在任何情况下都远比我们所需日期要早的日期此处选了哈斯丁之役battleofhaທstings的日期。只要每次取出最大值,就可以确保获得需要的日期。这是个ฐ非常实用的技巧,也๣可以应用在字符或数值数据,第11章会有更详细的说明。总结:sql是声明性语言de9๗guage,所以设法使你的代码越业务过程的规格说明。sqlssqqll的进攻式编程offensive9g9ithsql一般的建议是进行防御式编程9๗sively,在开始处理之前先检查所有参数的合法性。但实际上,对数据库编程而言,尽量同时做几件事情的进攻式编程有切实的优势。有个很好的例子:进行一连串ธ检查,每当其中一个ฐ检查所要求的条件不符时就产生异常。信用

--ๅ-ๅ--ๅ-ๅ-----ๅ----ๅ-------ๅ-page27---------ๅ----ๅ----ๅ-----ๅ-

卡付款的处理中就涉及类似步骤。例如,检查所提交的客户身份和卡号是否有效,以及两者是否匹配;检查信用卡是否过期;最后,检查当前๩的支付额是否过了信用额度。如果通过了所有检查,支付操作才继续进行。为ฦ了完成上述功能,不熟练的开者会写出下列语句,并检查其返回结果:色le9tfromcustomers9herecustomer_ຕid=provided_id接下来,他会做类似的工ื作,并再一次检查错误代码:色le9um,ไexpiry_daທte,ไcredit_limitfro毛unts9herecustomer_id=provided_id之后,他才会处理金融交易。相反,熟练的开者更喜欢像下面这样编写代码假设todaທy返当前日຅期:updaທteaounts色tbalan9๗9t9apur9dcນredit_limit=๡pur9dexpiry_ຕdaທtetodaທyandcນustomer_id=provided_idand9um=provided_9um接着,检查被更新的行数。如果结果为ฦ0,只需执行下面的一个操作即可判断出错原因:色le9um,ไaexpiry_date,a9๗cນefromcustomers9๗tsaonaທcustomer_ຕid=9daທ9um=provided_9um9hereustomer_id=๡provided_id如果此查询没有返回数据,则可断定cນustomer_id的值是错的;如果9ull,则ท可断ษ定卡号是错的;等等。其实,多数情况下此查询无຀需被执行。注意你是否注意到,上述第一段代码中使用了9t被误用于存在性检测的绝

--------ๅ--------ๅ----ๅ---ๅpage-----ๅ-ๅ--ๅ-----ๅ----ๅ---ๅ---

佳例子。“进攻式编程”的本质特征是:以合理的可能性reaທsonableprobaທ逼lities为基础。例如,检查客户是否存在是毫无意义的——因为ฦ既ຂ然该客户不存在,那ว么他的记录根本就不在数据库中!所以,应该先假设没有事情会出错;但如果出错了,就在出错的地方而且只在那个地方采取相应措施ๅ。有趣的是,这种方法很像一些数据库系统中ณ采用的“乐观并控制ๆoptimisti9trol”,后者会假设update冲突不会生,只在冲突真的生时才进行控制处理。结果,乐观方法比悲观方法的吞吐量高得多。总结:以概ฐ论为ฦ基础进行编程。假设最可能的结果;不是的确必要,不要采用异常捕捉的处理方แ式。ex9s精明地使用异常eexxeeppttiioonnssdis9s勇敢与鲁莽的界ศ线很模糊,我建议进攻式编程,但并不是要你模仿轻步兵旅在balaclava的自杀性冲锋注7。针ฤ对异常编程,最终可能落得虚张声势的愚蠢结果,但自负的开者还是对它“推崇备至goforit”,并坚信检查和处理异常能使他们完成任务。正如其名字所暗示的,异常应该是那ว些例外情况。对数据库编程的具体情况而言,不是所有异常都要求同样的处理方แ式——这是理解异常的使用是否明智的关键点。有些是“好”异常,应预先抛出;有些是“坏”异常,仅当真正的灾害生时才抛出。例如,以主键为条件进行查询时,如果没有结果返回则ท开销极少,因为只需检查索ิ引即可判ศ断。然而,如果查询无法使用索引,就必须搜索整个表——当此表数据量很大,所在机器又正在接近满负荷工作时,可能造成灾难。有些异常的处理代价高昂,即使是在最佳情况下也๣不例外,例如重复键dupli9iqueness”如何保证呢?我们几乎总是建立一个ฐ唯一性索引,每次向该索引增加一个键时,都要检查是否违反了该唯一性索引的约束。然而,建立索ิ引项需要记录物理地址,于是就要求先将记录插入表,后将索引项插入索引。如果违反此约束,数据库会取消不完全的插入,并返回违反约束的错误信息。上述这些操作开销巨大。但最大的问题是,整个处理必须ี围绕个ฐ别异常展开,于是我们必须ี“从个ฐ别记录的角度进行思考”,而不是“从数据集出进行思考”,这与关系数据库理论完全背道而驰。多次违反此约束会导致性能严重下降。来看一个oracle的例子。假设在两ä家公司合并后,电子邮件地址定为ฦ的标准格式,最多12个字符,所有空格或引号以下划ฐ线代替。如果新的employee表已经建好,并包含300่0条从employee_old表中提取并进行标准化处理的电å子邮件地址。我们希望每个员工的电子邮ุ件地址具有唯一性,于是fernandolopez的地址为flopez,而fraທncນiscolopez的地址为ฦflopez2。实际上,我们实际测试的数据中有3๑3个潜在的重复项,所以我们需要做如下测试:sqlin色rtintoemployee色mp_num,emp_name,emp_firstname,ไemp_email2色le9um,

-ๅ----ๅ---ๅ----ๅ--ๅ---ๅ---ๅ-ๅ--ๅpage2๐9๗----------ๅ-----ๅ--ๅ----ๅ--

3emp_name,4emp_firstname,5๓substrsubstremp_firstname,1,16||translateemp_name,''ู''ู,'__ຕ',ไ1้,127fromemployees_old;in色rtintoemployee色mp_num,ไemp_naທme,emp_firstname,emp_ຕemailerroraທtline1้:oraທ-00่001:unique9temp_email_ຕuqviolatedelap色d:๘00:๘00:๘00่853000条数据中ณ重复33๑条,比率大约是1้%,所以,或许可以心安理得地处理符合标准的99%ื,并用异常来处理其余部分。毕竟,1%的不符标准数据带来的异常处理开销应该不大。但这个异常处理的开销到底在哪里呢?让我们先从测试数据中剔除“问题记录”,然后再执行相同的测试,比较现:这次测试的总运行时间,与上次几乎相同,都是1้8秒。然而,从测试数据中剔๶除“问题记录”之后再执行前面第一段in色rt色lect语句时,度明显比循环快:最终现采用“一次处理一行”的方式导致耗时增加了近50%ื。那么เ,在此例中可以不用“一次处理一行”的方แ式吗?可以,但要先避免使用异常。正是这个通过异常处理解决“问题๤记录”问题๤决定,迫使我们采用循序方式的。另外,由á于生冲突的电子邮ุ件地址可能不止一个ฐ,可以为它们指定某个数字获得唯一性。很容易判断有多少个ฐ数据记录生了冲突,增加一个g肉pby子句就可以了。但在分配数字时,如果不使用主数据库系统的分析功能,恐怕比较困难。oraທ9aທlyti9,db2则ท称在线分析处理onlineanalyti9g,olap,sql色rver称之ใ为排名功能rankingfun9๗。纯粹从sql角度来看,探索此问题๤的解决方แ案很有意义。重复的电子邮件地址都可以被赋予一个具唯一性的数字:1赋给年纪最大的员工ื,2赋给年纪次之的的员工……依次类推。为此,可以编写一个ฐ子查询,如果是g肉p中的第一个ฐ电子邮件地址就不作操作,而该g肉p中ณ的后续电å子邮件地址则加上序号。代码如下:sqlin色rtintoemployee色mp_num,ไemp_firstnaທme,2๐emp_name,emp_email3色le9um,4๒emp_firstname,5emp_naທme,6๔de9,ไ1้,emp_ຕemail,7subຘstremp_email,

-----ๅ----ๅ---ๅ-------ๅ--ๅ--ๅpage30่----ๅ---ๅ-----ๅ----ๅ---ๅ---ๅ-

81,12-lengthltrimto_ຕ99||ltrimto_9๗10from色le9๗um,11emp_firstname,1้2๐emp_name,13๑subຘstrsubstremp_firstname,1,ไ114||translateemp_naທme,'''','ู__'ู,1,1้21้5emp_email,ไ16ro9_number17overpartitionbຘy18substrsubstremp_ຕfirstname,1,ไ11้9||traທnslateemp_naທme,'''',ไ'__'ู,1,ไ1220orderbyemp_numrn2๐1fromemployees_old223000่ro9๗ap色d:00:0่0:1168上面的代码避免了一次一行的处理,而且该解决方案的执行时间仅是先前方案的60%。总结:异常处理会迫使我们采用过程式逻辑。应始终使用声明式sql,尽量预ไ测可能的异常情况。sqlssqqll的本质本章我们将深入讨论sql查询,并研究如何根据不同情况的具体要求,来编写sql语句。我们会分析复杂的sql查询语句,将它们拆解成小的语句片断,并讲解这些语句片断ษ如何共同促成了最终查询结果的产生。sqlssqqll的本质thenatureofsql在深入讨论如何编写sql查询之前,我们有必要先了解一些sql自身的基本特性:sql与数据库引擎daທtabaທ色engine和优化器optimizer是什么关系?哪些因素า可能限制优化效率?sqlssqqll与数据库sqlanddaທtabaທ色s关系数据库的出现,要归功于efcodd的关系理论开创น性研究成果。cນodd的研究成果为ฦ数据

--------ๅ----ๅ----ๅ-ๅ-----ๅ-page31---ๅ-ๅ--ๅ-ๅ----ๅ-ๅ---ๅ--------

库学科了坚实的数学基础——而在此之前๩的很长一个ฐ时期数据库学科主要是凭经验。这和造桥的历史很相似:几千年前我们就开始建造跨江大桥,但是由于当时的营造商并不完全了解造桥材料和桥梁强度之间的关系,桥梁的设计往往会大大出实际的要求;后来土木工程学的材料é强度理论完善了,更先进更安全的桥梁也就随之ใ出现,这表明造桥使用的各种建筑材料得到了充分利ำ用。的确,如今的一些桥梁工ื程非常浩大,与此类似,现代dbms软件能ม够处理的数据量之大也๣是今非昔比了。关系理论之于数据库,正如土木工程学之于桥梁。sql语言、数据库和关系模型三者经常被混淆。数据库的功能ม主要是存储数据,这些数据符合对现实世界一部分所建立的特定模型。相应地,数据库必须可靠的基础设施infrastructure,无຀论何时都能ม够让多个ฐ用户使用同一些数据,且在数据被修改时不破坏数据完整性。这要求数据库能够处理来自不同用户的“资源争用9sa9处理过程中遇到เ机器故障等极端情况下也保持数据一致性。当然,数据库还有很多其他的功能,本书并未涵盖。正如其名,结构化查询语言stru9guage,sql无຀非是一种语言,虽然它与数据库关系密切。将sql语言和关系数据库等同视之,或者更糟——与关系理论等同视之ใ,都是错误的。这种错误就好比将掌握了电子表软件或文字处理软件视为掌握了“信息技术”。实际上,有些软件产品并非数据库,但它们也支持sql注1。另外,sql在成为标准之前也๣不得不与诸如rdo或quel等其他语言竞争,这些语言曾被许多理论家认为优于sql。为了解决所谓的“sql问题”,你必须ี了解两ä个ฐ相关部分:sql查询表达式和数据库优化器。如图4๒-1所示,这两部分在三个ฐ不同区域里协同工作。图的中ณ央是关系理论,这是数学家们尽情挥的区域。简而言之ใ,关系理论支持我们通过一组关系运算符来搜寻满足某些条件的数据,这些关系运算符几乎支持任何基本查询。关键在于,关系理论有严å格的数学基础,我们完全可以相信同一结果可由不同的关系表达式来获得,正如在算术中ณ24636๔9完全等于23一样。然而,尽管关系理论有至关重要的理论价值,但一些有重要实践意义แ的方แ面它并未涉及,这些方面属于图中所示的“报告需求reportingrequirements”的范围。其中ณ最明显的例子就是结果集的排序:关系理论只关心如何根据查询条件取得正确的数据集;而对我们这些实践者而非理论家而言,关系操作阶段只负责准确无误地找出属于最终数据集的记录,而不同行的相同字段的关系并不是在这个阶段处理,而是完全属于排序操作。另外,关系理论并不涉แ及各种统计功能例如百分位数等,而这些统计功能经常出现在不同的“sql方言diaທlect”当中。关系理论所研究的是集合色t,但并不涉及如何为这些集合排序。尽管有许多关于排序的数学理论,但它们都与关系理论无຀关。必须ี说明的是,关系操作与上述“报告需求”的不同在于关系操作适用于理论上无限大的、数学意义上的集,无论是操作含有十行数据的表、一万行数据的表、还是一亿行数据的表,我们都能以相同的方式对其施以任何过滤条件。再次强调:当我们只关心找出并返回符合查询条件的数据时,关系理论是完全适用的;然而,当我们需要进行记录排序,或者执行一个大多数人错误地认为ฦ它是关系操作的g肉p操作时,却已不再是针对可以无຀限大的数据集进行操作了,而必须ี是一个ฐ有限数据集,于是这个结果数据集不再是数学意义แ上的“关系relation”了,至此我们已经出了关系操作层。当然,我们仍然可以利用sql对该数据集进行一些有用的操作。初ม步总结一下,我们可以将sql查询表示为一个两层的操作,如图4-2๐所示。第一层是一个关系操作的“核”,它负责找出我们要操作的数据集;第二层是“非关系操作层non-ๅrelaທtionaທllayer”,

-----ๅ--ๅ-ๅ-ๅ----ๅ-ๅ---ๅ------page32๐---ๅ---ๅ------ๅ----ๅ--ๅ---ๅ--

它对有限的数据结果集进行“精雕细刻๑”从而产生用户期望的最终结果。尽管图4-2๐简要地表达了sql在数据处理环境中ณ的位置,但sql查询在大多数情况下都比这要复杂得多,图4๒-2仅仅展示ิ了一个总体的描述。关系操作中的过滤器filter有可能ม只是一个代名词,其背后是几个独立过滤器的组合,例如通过union结构或子查询来实现;最终,sql语句的构成可以很复杂。稍后还会讨论编写sql语句的问题,但我们接下来先要讨论的是数据物理实现和数据库优化器的关系。总结:千万别ี把sql查询的执行过程中ณ真正的关系操作和附加的展现层pre色ntationlaທyer功能ม混为一谈。sqlssqqll与优化器sqlandtheoptimizer当sql引擎处理查询时,会用优化器找出执行查询最高效的方แ式。此时关系理论又可以大有作为了——优化器借助关系理论,对开者的语义无误的原始查询进行有效的等价变换,即使原始查询编写得相当笨拙。优化是在数据处理真正被执行时生的。经过变换的查询在执行时可能比语义แ上等效的其他查询快得多,这因是否存在索引,以及变换与查询是否适应而不同。在第5章我们将介绍各种数据存储模型;有时,特定存储模型决定了查询优化的方式。优化器会检查下列ต因素:定义了哪些索ิ引、数据的物理布局、可用内存大小,以及可用于执行查询任务的处理器数。优化器还很重视查询直接或间接涉及的表和索引的数据量。最终,优化器根据数据库的实际实现情况对理论上等价的不同优化方案做出权衡,产生有可能是最优的查询执行方案。然而,要记住的关键一点是,尽管优化器在sql查询的“非关系操作层”也偶有用途,但以关系理论为支柱的优化器主ว要用于关系操作层。sql查询的等价变换还提醒我们:sql原本就是一种声明性语言de9๗guage。换言之,sql应该是用来表达“要做什么”、而非“如何来做”的。理论上讲,从“要做什么เ”到“如何来做”的任务就是由á优化器来完成的。在第1章、第2章中ณ讨论的sql查询比较简单,但即使从编写技巧层面来说,拙劣的查询语句也会影响优化器的效率。切记,关系理论的数学基础为数据处理了非常严谨的逻辑支持,因此sql艺术本应注重减小“非关系操作层”的厚度,即尽量在关系操作层完成大部ຖ分处理,否则优化器在“非关系操作层”难以保证返回的结果数据和原始查询执行的结果一样。另外,在执行非关系操作时这里非关系操作不严格地定义แ为针对已๐知结果集的操作,应专注于操作那些解决问题所必需的数据,不要画蛇添足。和当前记录不同,有限数据集必须ี以某种方式进行临ภ时存储内存或硬盘,这会带来惊人的开销。随着结果集数据量的增大,这种开销会急剧加大,尤其是在主存所剩ທ无几的时候。主存不足会引硬盘数据交换等开销很高的活动。而且,别忘了“索引所指的是硬盘地址,并非临时存储地址”,所以数据一旦进行临时存储,就意味着我们向最快的数据访问方式说再见了哈希๶方式可能例外。一些sql方言会误导用户,使他们认为自己้仍在关系世界中——但其实早就不是关系操作了。举个ฐ简单的例子:不是经理的员工当中,哪五个人收入最高?这是个现实生活中ณ很合理的问题๤,

---ๅ----ๅ--ๅ-----ๅ-ๅ---ๅ-----page33--ๅ--ๅ---ๅ---ๅ----ๅ--------ๅ-

但它包含了明显的非关系描述。“找出不是经理的员工ื”是其中的关系操作部分,由á此获得一个ฐ有限的员工集合,然后排序。有些sql方แ言通过在色lect语句中增加特殊子句来限制返回的记录数,很显然,排序和限制记录数都是非关系操作。其他sql方言这里主要是指oraທcle则ท采用另外的机制,即用一个ฐ名为ro9num的虚拟字段dummy9为查询结果编号——这意味着编号工作生在关系操作阶段。如果查询语句如下:色le9ame,salaryfromemployees9herestatus!=๡'exe9๗um=somefun9cນ是个函数,返回距今六个月前的具体日຅期。注意上面用了distinct,因为考虑到某个客户可以是大买家,最近订购了好几台蝙蝠车。暂不考虑优化器将如何改写此查询,我们先看一下这段代码的含义。先,来自customers表的数据应只保留城市名为gotham的记录。接着,搜索orders表,这意味着custid字段最好有索引,否则只有通过排序、合并或扫描orders表建立一个哈希表才能保证查询度。对orders表,还要针对订单日期进行过滤:如果优化器比较聪明,它会在连接join前๩先过滤掉一些数据,从而减少后面要处理的数据量;不太聪明的优化器则可能会先做连接,再作过滤,这时在连接中指定过滤条件利ำ于提高性能,例如:joinordersoono9daordered=somefun9๗无关,优化器也会受到เ过滤条件的影响。例如,若orderdetail的主键为ฦordid,ไartid,即ordid为索引的第一个属性,那么我们可以利用索引找到与订单相关的记录,就和第3章中讲的一样。但如果主键是artid,ordid就太不幸了注意,就关系理论而言,无຀论哪个版本都是完全一样,此时的访问效率比ordid,ไartid作为ฦ索ิ引时要差,甚至一些数据库产品无法使用该索引注3,唯一的希๶望就是在ordid上加独立索引了。连接了表orderdetail和orders之后,来看articles表,这不会有问题,因为表orderdetail主键包括artid字段。最后,检查aທrticles中的值是否为baທt摸逼le。查询就这样结束了吗?未必结束,因为用了distincນt,通过层层筛选的客户名还必须要排序,以剔除重复项目。分析至此,可以看出这个ฐ查询有多种编写方式。下面的语句采用了古老的join语法:色le9amefromcustomersc,orderso,ไorderdetailod,articlesa9๗here9d9doordid=odordidandodaທrtid=aartidandaaທrtnaທme=๡'ูbat摸逼le'ูandoordered=๡somefuncນ本性难移,我偏爱这种较古老的方式。原因只有一个:从逻辑的角度来看,旧方法突显出数据处理顺ิ序无足轻重这一事实;无论以什么顺ิ序查询表,返回结果都是一样的。cນustomers表非常重要,因为最终所需数据都来自该表,在此例中,其他表只起辅助作用。注意,没有适用于

-------ๅ--ๅ-------ๅ---ๅ----ๅpaທge41-ๅ-ๅ---ๅ---------ๅ--------ๅ-

所有问题๤的解决方案,表连接的方แ式会因情况不同而异,而决定连接方แ式取决于待处理数据的特点。特定的sql查询解决特定的问题,而未必适用于另一些问题。这就像药,它能治好这个病人,却能将另一个病人医死。蝙蝠车买主的进一步讨论下面看看查询蝙蝠车买家的其他方法。我认为,避免在最高层使用distinct应该是一条基本规则。原因在于,即使我们遗漏了连接的某个条件,distinct也会使查询“看似正确”地执行——无可否认,较旧的sql语法在此方面问题较大,但aທnsisql92在通过多个字段进行表的连接时也可能出现问题。现重复数据容易,但现数据不准确很难,所以避免在最高层使用distinct应该是一条基本规则。现结果不正确更难,这很容易证明。前面使用distinct返回客户名的两个查询,都可能返回不正确结果。例如,如果恰巧有多位客户都叫“9ayne”,distinct不但会剔除由á同个客户的多张订单产生的重复项ำ目,也๣会剔๶除由名字相同的不同客户产生的重复项目。事实上,应该同时返回具唯一性的客户id和客户名,以保证得到蝙蝠车买຀家的完整清单。在实际中,现这个问题可不容易。要摆脱๳distinct,可考虑以下思路:客户在gohtam市๦,而且满足存在性测试,即在最近六个月订购过蝙蝠车。注意,多数但非全部sql方言支持以下语法:色le9amefromcustomersc9๗here9dexists色le9๗ullfro摸rderso,orderdetailod,ไarti9ame='bat摸逼le'aທndaartid=odartidaທndodordid=oordidando9doordered=somefunc上例的存在性测试,同一个名字可能出现多次,但每个客户只出现一次,不管他有多少订单。有人认为我对ansisql语法的挑剔有点苛刻指“蝙蝠车买主”的例子,因为ฦ上面代码中ณcນustomers表的地位并没有降低。其实,关键区别在于,新查询中cນustomers表是查询结果的唯一来源嵌套的子查询会负责找出客户子集,而先前的查询却用了join。这个嵌套的子查询与外层的色lect关系十分密切。如代码第1้1行所示粗体部分,子查询参照了外层查询的当前记录,因此,内层子查询就是所谓的关联子查询correlaທtedsubquery。此类子查询有个弱点,它无法在确定当前客户之ใ前执行。如果优化器不改写此查询,就必须先找出每个客户,然后逐一检查是否满足存在性测试,当来自gotham市的客户非常少时执行效率倒是很高,否则情况会很糟此时,优秀的优化器应尝试其他执行查询的方แ式。我们还可以这样编写查询:

--ๅ-----------ๅ-------ๅ---paທge42๐---ๅ----ๅ--ๅ--------ๅ------

色le9amefromcustomers9here9๗d9色lectocustidfro摸rderso,orderdetailod,aທrti9ame=๡'bat摸逼le'andaartid=odartidaທndodordid=oordidaທndoordered=somefuncນ在这个例子中ณ,内层查询不再依赖外层查询,它已变成了非关联子查询uncorrelatedsubquery,只须ี执行一次。很显然,这段代码采用了原有的执行流程。在本节的前一个例子中,必须先搜寻符合地点条件的客户如均来自gotham,接着依次检查各个订单。而现在,订购了蝙蝠车的客户,可以通过内层查询获得。不过,如果更仔细地分析一下,前๩后两个ฐ版本的代码还有些更微妙的差异。含关联子查询的代码中,至关重要的是orders表中的custid字段要有索引,而这对另一段代码并不重要,因为这时要用到的索引如果有的话是表customers的主键索引。你或许注意到เ,新版的查询中执行了隐式的distinct。的确,由于连接操作,子查询可能会返回有关一个ฐ客户的多条记录。但重复项目不会有影响,因为in条件只检查该项目是否出现在子查询返回的列ต表中,且in不在乎某值在列表中出现了一次还是一百次。但为了一致性,作为整体,应该对子查询和主查询应用相同的规则,也๣就是在子查询中也๣加入存在性测试:色le9aທmefromcustomers9here9d9๗色lectocustidfro摸rderso9hereoordered=somefun9ullfro摸rderdetailod,arti9ame=๡'baທt摸逼le'andaartid=odartidaທndodordid=oordid或者:

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

色le9aທmefromcນustomers9here9d9色lectcustidfro摸rders9๗hereordered=somefun9๗色lecນtodordidfro摸rderdetailod,aທrti9ame='ูbat摸逼le'aທndaaທrtid=odartid尽管嵌套变得更深、也๣更难懂了,但子查询内应选择exists还是in的选择规则相同:此选择取决于日期与商品条件的有效性。除非过去六个ฐ月的生意非常清淡,否则商品名称应为最有效的过滤条件,因此子查询中用in比exists好,这是因为,先找出所有蝙蝠车的订单、再检查销售是否生在最近六个月,比反过来操作要快。如果表orderdetail的artid字段有索引,这个方法会更快,否则,这个聪明巧ู妙的举措就会黯然失色。注意每当对大量记录做存在性检查时,选择in还是exists须斟酌。利于多数sql方言,非关联子查询可以被改写成from子句中的内嵌视图。然而,一定要记住的是,in会隐式地剔除重复项ำ目,当子查询改写为ฦfrom子句中的内嵌视图时,必须ี要显式地消除重复项目。例如:色le9๗amefromcustomers9here9d9๗色lectocustidfro摸rderso,色le9ctodordidfro摸rderdetaທilod,ไarti9ame='bat摸逼le'andaartid=odaທrtidx9๗hereoordered=somefun9dxordid=๡oordid编写功能等价的查询时,不同的编写方式就好像同义词。在书๰面语和口语中ณ,同义词的意思虽

--ๅ--------ๅ----------ๅ--ๅ-page44---ๅ----ๅ--ๅ-----ๅ-ๅ---ๅ-----

然大致相同,但又有细微差异,因此某个词在特定语境中更合适。同样,数据和处理的具体实现细节可以决定选择哪种查询方แ式。蝙蝠车买主ว案例总结前面讨论的各段sql语句,看似意义不大的编程技巧练习,实则不然。关键是“擒获attack”数据的方法有很多,不必按照先customers、然后orders、接着orderdetail和articles的方式来编写查询。现在以箭头表示搜索条件的强度——条件分辨力越强,箭头就越大。假设gotham市的客户非常少,但过去六个月的销售业绩不错,卖出了很多蝙蝠车,此时规划图如图4-6所示。虽然商品名称之ใ上有个过滤条件,但图中的中等大小的箭头指向了表orderdetaທil,因为该表是真正重要的表。待售商品可能很少,反映出销售收入的百分比;也可能ม待售商品很多,最畅销的商品之一就是蝙蝠车。相反,如果我们假设多数客户在gotham市,但其中很少的客户买了蝙蝠车,则规划ฐ图如图4-7所示。很显然,此时表orderdetail是最大的目标。来自这个表的数据的数据量缩减度越快,查询执行得就越快。还要注意的非常重要的一点是,“过去六个月”并不是个非常精确的条件。但如果我们把条件改为过去两个ฐ月,而库中有十年的销售记录,会生什么呢?在这种情况下,如果能先访问到近期的订单借助第5章中描述的一些技术,这些数据或许就聚集在一起,查询的效率就会更高些;找出近期订单后,一方แ面选取gotham的客户,另一方面则选取蝙蝠车订单。所以,换个角度来看,最好的执行计划并不只相依于数据值,还应该随着时间而不断进化。好了,总结一下。先,解决问题๤的方法不只一种……而且查询的编写方式经常会与数据隐含的假设相关。殊途同归,最终的结果集都是一样的,但执行度可能有极大差异。查询的编写方แ式会影响执行路径,尤其是应用无法在真

书签 上一章 目录 下一章 书架s
推荐阅读: 至纯生命 正妻(VIP完结+番外) 魔者佛心 花丛炼心那一年出的书 异界之刺客纵横网盘全文 肉欲 我心飞翔 重生之好命几个女主 难续前缘图片 蚀骨柔情慕冉冉免费阅读