返回

SQL语言艺术

关灯
护眼
第4章(1 / 1)
强烈推荐: 武神至尊 三嫁弃心前妻 虎娘子 快穿女神的代价 母子初赴巫山 当科学遇见科学 女神完美攻略 冰山王子恋上冷公主 星空破晓

fun9g子句中,在订单完成之前。我承认先前处理订单的方法比较复杂,以及设置状态的时间。现在回过头来看客户与订单的例子,任何与聚合无关的条件都应放在9๗here子句中。因为ฦ在g肉p,最终,我们写出下列查询,该表的主要字段有:ordid订单id、staທtus、statusdate时间戳等,主ว键由ordid和staທtusdate组成。我们的需求是列ต出所有尚未标记为ฦ完成状态的订单假设所有交易都已终止的下列ต字段:订单号、客户名、订单的最后状态,从而减少为进行g肉pb。必须,这些都记录在表ord,滤掉已完成的订单,并找出订单当前状态:这个查询很合理,色le9๗ame,,但事实上,它让人非常担心。上面代,先,但它们嵌入的方式和前一个ฐ例子的方式不同,它们只是彼此间接相关的。最让人担心的是,这两个子查询访问相同的表,而且该表在外层已经被访问过。我们编写的过滤条件质量如何呢?因为ฦ只检查了订单是否完成,所以它不是非常精确。这个查询如何执行的呢?很显然,可以扫描orders表,检查每一条订单记录是否为ฦ已๐完成状态——注意,仅通过表orders即可找出所要信息似乎ๆ令人高兴,但实际情况并非如此,因为只有上述活动之后,才能检查最新状态的日຅期,即必须ี按照ั子查询编写的顺序来执行。上述两个ฐ子查询是关联子查询,这很不好。因为必须要扫描orders表,这意味着我们必须检查orders的每条订单记录状态是否为ฦ“plete”,虽然检查状态的子查询执行很快,但多次重复执行就不那ว么เ快了。而且,若第一个子查询没找到“plete”状态时,还必须ี执行第二个ฐ子查询。那么,何不试试非关联子查询呢?要编写非关联子查询,最简单的办法是在第二个子查询上做文章。事实上,在某些sql方言中,我们可以这么写:andoordid,osstaທtusdaທte=色lecນtordid,maxstaທtusdatefro摸rderstatusg肉pbyordid这个子查询会对orderestaທtus作“全扫描”,但未必是坏事,下面会对此加以解释。重写的子查询条件中ณ,等号左端的“字段对”有点别扭,因为ฦ这两ä个ฐ字段来自不同的表,其实不必这样。我们想让orders和orderstaທtus的订单id相等,但优化器能ม感知这一点吗?答案是不一

卡付款的处理中就涉及类似步骤。例如,检查所提交的客户身份和卡号是否有效,以及两ä者是否匹配;检查信用卡是否过期;最后,检查当前的支付额๩是否过了信用额度。如果通过了所有检查,支付操作才继续进行。为ฦ了完成上述功能,不熟ງ练的开者会写出下列ต语句,并检查其返回结果:色le9tfromcນustomers9herecustomer_id=๡provided_ຕid接下来,他会做类似的工ื作,并再一次检查错误代码:色le9๗um,ไexpiry_date,credit_limitfro毛unts9๗herecustomer_ຕid=provided_ຕid之后,他才会处理金融交易。相反,熟ງ练的开者更喜欢像下面这样编写代码假设today返当前日期:updaທteaounts色tbຘaທlan9๗9t9apur9๗dcredit_ຕlimit=pur9dexpiry_ຕdaທtetodaທyandcustomer_ຕid=provided_idaທnd9um=๡provided_9um接着,检查被更新า的行数。如果结果为ฦ0่,只需执行下面的一个操作即可判断出错原因:色le9๗um,aexpiry_ຕdaທte,ไa9cefromcustomers9๗tsaທonacustomer_ຕid=9daທ9๗um=provided_9๗um9hereustomer_id=provided_id如果此查询没有返回数据,则可断定customer_id的值是错的;如果9ull,则ท可断定卡号是错的;等等。其实,多数情况下此查询无需被执行。注意你是否注意到,上述第一段代码中使用了9๗t被误用于存在性检测的绝

本书๰假定你已๐精通sql语言。这里所说的“精通”不是指在你大学里学了sql10่1并拿来aທ+的成绩,当然也并非指你是国际公认的sql专家,而是指你必须具有使用sql开数据库应用的经验、必须考虑索ิ引、必须不把5๓0่00行的表当大表。本书๰的目标不是讲解连接、外连接、索引的基础知识,阅读本书过程中,如果你觉得某个sql结构还显神秘,并影响了整段代码的理解,可先阅读几本其他sql书๰。另外,我假定读者至少熟悉一种编程语言,并了解计算机程序设计的基本原则。性能已๐很差ๆ、用户已抱怨、你已在解决性能ม问题๤的前线,这就是本书的假定。本书๰内容我现sql和战争如此相像,以至于我几乎ๆ沿用了《孙子兵法》的大纲,并保持了大部分题目名称注2。本书分为12๐章,每一章包含许多原则ท或准则,并通过举ะ例的方式对原则ท进行解释说明,这些例子大多来自于实际案例。第1章,制定计划ฐ:为ฦ性能ม而设计讨论如何设计高性能ม数据库第2章,动战争:高效访问数据库解释如何进行程序设计才能高效访问数据库第3章,战术部ຖ署:建立索引揭示ิ为何建立索引,如何建立索引第4章,机动灵活:思考sql语句解释如何设计sql语句第5๓章,了如指掌:理解物理实现揭示ิ物理实现如何影响性能第6章,锦囊妙计:认识经典sql模式包括经典的sql模式、以及如何处理第7๕章,变换战术:处理层次结构说明如何处理层次数据第8๖章,孰优孰劣:认识困难,处理困难指出如何认识和处理比较棘手的情况第9章,多条战线:处理并讲解如何处理并第10่章,集中ณ兵力:应付大数据量讲解如何应付大数据量第1้1้章,精于计谋:挽救响应时间分享一些技巧ู,以挽救设计糟糕的数据库的性能ม第12章,明察秋毫:监控性能收尾,解释如何定义和监控性能本书๰约定本书๰使用了如下印刷็惯例:等宽courier表示sql及编程语言的关键字,或表示table、索引或字段的名称,抑或表示函数、代码及命令

--ๅ-ๅ--ๅ----ๅ-ๅ-ๅ------ๅ---ๅ-ๅ--ๅpaທge72----ๅ-----ๅ-ๅ--ๅ---ๅ-ๅ-ๅ---ๅ---

orderbytimestamprnfromhit_ຕ9๗+1andastatistic_id=๡bຘstatisticນ_ຕidorderbyaທtimestaທmp,ไastaທtistic_idoraທcle等dbms支持olap函数laທg9๗。该函数借助分区和排序,返回9๗个ฐ值。如果使用laທg函数,我们的查询甚至执行得更快——比先前的查询大约快2๐9ounter60timestamp-prev_timestampfrom色lecນttimestamp,statisti9๗ter,1overpaທrtitionbຘystatisticນ_ຕidorderbytimestaທmpprev_9ter,lagtimestamp,1overpartitionbystaທtistic_ຕidorderbຘytimestaທmpprev_timestampfromhit_ຕ9teraorderbyatimestaທmp,ไaທstatistic_id很多时候,我们的数据并不像航班案例中ณ那样具有对称性。通常,当需要查找和最小、最大、最早ຉ、或最近的值相关联的数据时,先必须找到เ这些值本身此为第一遍扫描,需比较记录,接下来的用这些值作为第二遍扫描的搜索条件。而以滑动窗口sliding9๗indo9ap函数,可以将两遍扫描合而为一至少表面上如此。基于时间戳或日຅期的数据查询,非常特殊也๣非常重要,本章在稍后的“基于日຅期的简单搜索或范围搜索”中专门讨论。总结:当多个ฐ选取条件用于同一个ฐ表的不同记录时,可以使用基于滑动窗口工ื作的函数。基于日期的简单搜索或范围搜索simpleorraທnge色ar9daທtes搜索条件有多种,其中日期和时间占有特殊地位。日期极为常见,而且比其他数据类型更可能ม成为范围搜索的条件,范围搜索可以是有界ศ的如“在某两天之ใ间”,也๣可以是部ຖ分有界“在某天之ใ前”。通常,为了获得这种结果集,查询需要使用当前๩日຅期如“前六个月”。上一节“通过聚合获得结果集”所举的例子,用到了sales_history表。当时,条件位于aທ摸unt上,其实对于sales_ຕhistory这种表更常见的是日຅期条件,尤其是读取特定日期的数据、或读取两个日຅期之ใ间的数据。在保存历史数据的表中查找特定日຅期或其对应值时,必须特别注意确定当前日຅期的方法,它可能ม成为ฦ聚合条件的基础。

----ๅ-ๅ-ๅ--ๅ-ๅ-ๅ-ๅ-ๅ--ๅ--ๅ----ๅ---paທge7๕3---ๅ--ๅ----ๅ-ๅ-ๅ-ๅ---ๅ------ๅ--ๅ

第1้章已指出,设计保存历史数据的表颇为困难,而且没有现成的简单解决方案。无论你对当前๩数据、还是历史数据感兴趣,设计历史数据的存储方แ案都要根据如何使用数据决定,同时还要看数据多快会过时。例如,零售系统中ณ价格的变动度比较慢除非正在经受严å重的通货膨胀,而网络流量或财务设备的价格改变度比较快,甚至快很多。从宏观角度来看,关键是各项历史数据的数量:是“少量数据项ำ、大量历史数据”,还是“大量数据项、少量历史数据”,或是介于两者之ใ间?其重点是:数据项的可选择性取决于数据项的总数、取样频率“每天一次”还是“每次改变时”、时间长短“永久”还是“一年”等。因此,本节将先讨论“大量数据项ำ、少量历史数据”的情况,接着讨论“少量数据项、大量历史数据”的情况,最后讨论当前๩值问题。大量数据项、少量历史数据maທnyitems,fe9historicalvaທlues既然没有为每个数据项保留แ大量历史数据,那ว么เ各项的id可选择性很高。说明要查询哪些项,限定参与查询的少数历史记录,就可确定特定日期当前๩日຅期或以前日期对应的值。这种情况需要我们再次处理聚合值aggregatevalue。除非建立了代理键本情况不需要代理键,否则主键通常是复合键,由áitem_id和record_date组成。为了查询特定日期的值,可采用两ä种方แ法:子查询和olap函数。使用子查询查找某数据项ำ在特定日期的值相对简单,但实际上,这种简单只是假象。通常你会遇到这样的代码:色lecນt9haທteverfromhist_dataທasouter9hereouteritem_id=๡somevalueaທndouterre9errecນord_ຕdaທtefromhist_daທtaທasinner9hereinneritem_id=outeritem_idandinnerre9ce_ຕdate考察这个ฐ查询的执行路径,我们现:先,内层查询与外层查询是有关联的correlated,因为ฦ内层查询参照了item_id的值,该值是由外层查询返回的当前记录一个ฐ字段。下面,先来分析外层查询。理论上,复合键中的字段顺序不会有太大影响,但实际上它们非常重要。如果我们误把主ว键定义为recນord_ຕdaທte,ไitem_id,而不是item_id,ไrecord_date,前例的内层查询就非常依赖item_ຕid字段的索ิ引,否则无法高效地向下访问树状结构索引。但我们知道,额外增加一个索ิ引的代价很高。外层查询找到了保存item_id历史的各条记录,接着使用当前๩item_ຕid值逐次执行子查询。注

--------ๅ-----ๅ-ๅ--ๅ-----ๅ-ๅ-ๅpage74--ๅ-ๅ--ๅ--ๅ-ๅ-ๅ--ๅ---------ๅ-ๅ--ๅ

意,内层查询只依赖item_ຕid,这与外层查询处理的记录相同,这意味着我们执行相同的查询、返回相同的结果。优化器会注意到查询总是返回相同的值吗?无຀法确定。所以最好不要冒这个ฐ险。在使用关联子查询时,如果它处理不同的记录后总是返回相同的值,就没有意义了。所以,应该改用无关联子查询:色lect9haທteverfromhist_daທtaasouter9hereouteritem_id=somevaທlueandouterre9errecord_ຕdatefromhist_ຕdataaທsinner9hereinneritem_ຕid=somevalueandinnerre9cນe_ຕdaທte现在子查询的执行不需要访问表,只需访问主键索ิ引就够了。个ฐ人习๤惯各有不同,但如果dbms支持将“子查询的输出”与多个字段进行比较这个特性不是所有产品都支持的,则应优先考虑基于主键比较:色lect9hateverfromhist_dataasouter9hereouteritem_ຕid,ไouterre9eritem_ຕid,ไmaxinnerrecord_ຕdatefromhist_dataaທsinner9๗hereinneritem_ຕid=somevalueandinnerre99eritem_id让子查询返回的字段,完全与复合主键的字段相符,有一定道理。如果必须返回“数据项ำ值的列ต表”例如是另一个查询的结果,则ท上述查询语句建议的执行路径非常合适。只要每个ฐ数据项的历史信息数量都较少,以in列ต表或子查询取代内层查询中ณ的somevalue,会使整个查询执行更高效。也可以用in子句取代“相等性条件”,在多数情况下没有什么不同;但偶有例外,例如,如果用户输错了item_id,采用in时会返回未现数据,而采用“相等性条件”时会返回错误数据。使用olaທp函数我们在自连接色lf-ๅjoin情况下,使用了诸如ro9๗ap函数,它们在查询“特定日຅期某数据项的值”时也๣同样有用甚至高效。但记住,olaທp函数会带来非关系的处理模式注5๓。注意olaທp函数属于sql的非关系层。这类函数的作用是:在查询中ณ做最后或几乎ๆ是最后处理。因为它们在过滤已完成后对结果集进行处理。运用ro9๗_numbຘer等函数,可以通过日期排序判ศ断数据的“新旧程度degreeoffreshness”也๣就是距离现在有多久:

-ๅ-ๅ--ๅ-------ๅ-ๅ--ๅ----ๅ-ๅ-ๅ---page7๕5๓-ๅ--ๅ-ๅ-ๅ-ๅ-ๅ--ๅ--ๅ----ๅ----ๅ-ๅ-ๅ--ๅ

色le9๗umberoverpartitionbyitem_ຕidorderbyre9ess,9haທteverfromhist_ຕdataທ9hereitem_id=somevalueaທndre9๗ce_date选取最新数据,只需保留freshness值为1้的记录:色le9sfrom色le9umbຘeroverpartitionbຘyitem_ຕidorderbຘyre9ess,ไ9hateverfromhist_ຕdaທta9hereitem_id=๡somevaທlueandre9ce_ຕdaທteasx9herexfreshness=1理论上,使用olap函数方แ法和子查询几乎ๆ没有差ๆ异。实际上,olaທp函数只访问一次表,即使需要为ฦ此而进行排序操作也不例外。olap函数对表不需要做额外的访问,甚至在使用主ว键快存取时也是如此。因此,采用olap函数度会比较快尽管只是快一点点。少量数据项ำ、大量历史数据maທnyhistoricalvaluesperitem当存在大量历史数据时,情况有所不同——例如,监控系统中ณ采集“度量值”的频率很高。这里的困难在于,必须根据对极大量的数据进行排序,才能ม找到เ特定日期或最接近特定日期的值。排序是代价很高的操作:如果我们应用第4๒章的原则,降低非关系层厚度的唯一方法,就是在关系层多做一些工作,增加过滤条件的数量。此时,针对所需数据更精确地归类日຅期或时间以缩小范围,便非常重要。如果我们只上限,就必须扫描并排序所有历史数据。所以如果数据的采集频率很高,下限是有必要的。如果我们成功地把记录的“工ื作集”控制在可管理的大小,就相当于回到了“少量历史记录”的情况。如果无຀法同时指定上限例如当前๩日期和下限,我们的唯一希望就是根据数据项ำ分区;我们只需在单一分区上操作,这比较接近“大结果集”的情况。结果集和别的数据存在与否有关result色tpredi9๗cນeofdata一个ฐ表中的哪些记录和另一个表中ณ的数据不匹配?这种“识别ี例外”的需求经常出现。人们最常想到的解决方แ案有两个:notin搭配非关联子查询,或者notexists

-ๅ-ๅ-ๅ----ๅ-----ๅ-ๅ--ๅ-ๅ-ๅ-ๅ-----ๅpage76-ๅ-ๅ--ๅ---ๅ-ๅ-ๅ-ๅ---------ๅ-ๅ--ๅ-

搭配关联子查询。一般认为ฦ应该使用notexists。在子查询出现在高效搜索ิ条件之后,使用notexists是对的,因为高效过滤条件已๐清除大量无关数据,关联子查询当然会很高效。但当子查询恰好是唯一条件时,使用notin比较好。查找在另一个表无຀对应数据的记录时,会碰到一些奇特的解决方案。以下为ฦ实际例子,显示哪些数据库查询代价最高。注意,问号是占位符pla9๗dvariaທble,它们的具体值在后续执行中传递给查询:in色rtintottmpoutcustcode,suistrcod,cນempdtcນod,bkgaທreaທcod,mgtaທreacod,ไrityp,riflg,usr,ไ色q,9g,sig色9cນtcustcode,?,?๣,?,mgtareaທcນod,?,ไ?,usr,色q,9๗g,sig色csuifromttmpouta9๗herea色q=?aທnd0่=色le9tfromttmpoutbຘ9herebຘsuistr9dbຘ9๗dbຘbຘkgarea9diflg=?๣aທndb色q=๡?๣此例并非暗示我们无条件地认可临时表的使用。另外,我怀疑这个ฐin色rt语句会被循环执行,通

---------ๅ--ๅ----ๅ--ๅ-ๅ-ๅ----paທge77๕----ๅ----ๅ-ๅ-ๅ--ๅ----ๅ--ๅ----ๅ-ๅ

过消เ除循环可以适当改善性能。例子中出现了自参照色lf-ๅreference很不常见的用法:对一个表的插入操作,是以同一个ฐ表上的色lect为基础的。当前存在哪些记录?要创น建的记录是否不存在?要插入的记录是由上述两个问题๤决定的。使用9๗t测试某些数据是否存在是个ฐ糟糕的主意:为ฦ此dbms必须搜索并找出所有相符的记录。其实,此时应该使用exists,它会在遇到第一个相符数据时就停止。当然,如果过滤条件是主ว键,使用9๗t或exists的差别不大,否则ท差异极大——无论如何,从语义角度讲,若想表达:aທndnotexists色le9๗d0่=色le9t时,优化器“可能”会进行合理的优化——但未必一定如此。记录的数量若通过独立步骤被计入某个变量,优化器肯定不会优化,因为ฦ优化器再聪明也无຀法猜测计数的用途:9๗t的结果可能是极重要的值,而且必须ี显示给最终用户!然而,当我们只想建立一条新记录,且新记录要从已存在于表中ณ的记录推导出来时,正确的做法是使用诸如ex9๗us这样的集合操作符色toperaທtor。in色rtintottmpoutcustcນode,suistrcນod,ไcນempdtcod,bkgareacod,ไmgtaທreacod,rityp,riflg,usr,ไ色q,ไ9g,ไsig色csui色lecນtcustcode,ไ?,?,?๣,mgtareaທcນod,?,?,ไusr,色q,9๗g,

-----ๅ-ๅ-ๅ-----ๅ--------ๅ--ๅ-page78-ๅ-ๅ-ๅ----ๅ--ๅ--ๅ----ๅ-ๅ-ๅ-ๅ-----ๅ

sig色csuifromttmpout9๗here色q=?excນept色lecນtcustcode,ไ?,ไ?๣,ไ?๣,ไmgtaທreacນod,?๣,ไ?๣,usr,色q,ไ9g,sig色csuifromttmpout9heresuistr9๗d9dbkgareaທ9driflg=๡?and色q=?集合操作符的重大优点是彻底打破了“子查询强加的时间限制ๆ”,无຀论子查询是关联子查询还是非关联子查询。打破“时间限制ๆ”是什么เ意思?当存在关联子查询时,就必须ี执行外层查询,接着对所有通过过滤条件的记录,执行内层查询。外层查询和内层查询相互依赖,因为外层查询会把数据传递给内层查询。使用非关联子查询时情况要好得多,但也不是完全乐观:必须ี先完成内层查询之ใ后,外层查询才能介入。即使优化器选择把整个查询作为哈希๶连接hashjoin执行——这是聪明的方法——也不例外,因为要进行哈希连接,sql引擎必须先进行表扫描以建立哈希数组hashaທrray。相比之ใ下,使用集合操作符union、inter色cນt或except时,查询中ณ的这些组成部分不会彼此依赖,从而不同部分的查询可以并行执行。当然,如果有个步骤非常慢,而其他步骤非常快,则ท并行化意义不大;另外,如果查询的两个部分工ื作完全相同,并行化就没有好处,因为不同进程的工作是重复的,而不是分工负责。一般而言,在最后步骤之前,让所有部ຖ分并行执行会很高效,最后步骤把不完整的结果集组合起来——这就是分而治之。集合操作符的使用有个额外的问题:各部ຖ分查询必须返回兼容的字段——字段的类型和数量都要相同。下例实际案例,来自账单程序通常不适合集合操作符:色lecນt9๗haທtever,sumdtaxfromi女oice_detaທild,ไ

------ๅ----ๅ-ๅ-ๅ------ๅ-----page79-ๅ-----ๅ-ๅ-ๅ-----ๅ---------ๅ-ๅ

i女oice_extraທcນtore9hereepgaທ_staທtus=0orerd_staທtus=๡0andsuitable_join_9ddtype_93๑,ไ7๕,2ordtype_ຕ9ddsubtype_9๗s_9s_ຕdes9s_ຕ9๗6๔,7g肉pby9haທt_is_requiredhavingsumdtaທx!=0最后一个ฐ条件有问题它使我想起了《绿野仙踪》里的黄砖路,甚至使我做起了“负税率”的白日:sumdtaທx!ำ=๡0如前所述,换成下列ต条件更加合理:aທnddtax0上述的例子中,使用集合操作符会相当笨拙,因为必须访问i女oicນe_detail表好几次——如你所料,那不是个轻量级的表。当然,还要看每个条件的可选择性,如果type_ຕcນode=4很少见,那么เ它就是个ฐ可选择性很高的条件,exists或许会比notin更适合。另外,如果trans_ຕdes9๗正好是个ฐ小型表或者相对较小,尝试通过单独操作测试存在性,并起不到เ改善性能ม的效果。另一个表达非存在性的方法很有趣——而且通常相当高效——是使用外连接outerjoin。外连接的主ว要目的是,返回来自一个ฐ表的所有信息及连接表中的对应信息。无对应信息的记录也需返回——查找另一个ฐ表中无对应信息的数据时,这些记录正好是我们的兴趣所在,可通过检查连接表的字段值是否为null找出它们。例如:色le9voice_detail9heretype_9๗dsubຘtype_ຕ9๗s_9s_des9s_ຕ9๗6,ไ7或重写为ฦ:色le9voi9trans_ຕdes9๗s_des9s_9๗s_des9๗voice_detailsubຘtype_ຕcນode

-------ๅ------ๅ--ๅ----ๅ----paທge80--ๅ---------ๅ-ๅ--ๅ-ๅ-ๅ-ๅ-ๅ--ๅ--ๅ-

9๗aptiontrans_ຕ9子句中加上traທns_ຕcategory的条件。有人认为ฦ它应该出现在9here子句中,实际上,在连接之前๩或在连接之后过滤都不影响结果当然,根据这个条件和连接条件本身的可选择性不同,会有不同的性能表现。然而,在使用空值上的条件时,我们别ี无຀选择,只有在连接后才能做检查。外连接有时需要加distin9๗非关联子查询,来检查数据是否存在的差异很小,因为ฦ连接所使用的字段,正好与比较子查询结果集的字段完全相同。不过,众所周知的是,sql语言的“查询表达式风格”对“执行模式”影响很大,尽管理论上不是这么เ说的。这取决于优化器的复杂程度,以及它是否会以类似方法处理这两类查询。换言之,sql不是真正的声明性语言sqlisnotatrulyde9๗guaທge,尽管优化器不断推陈出新า改善sql的可靠性relia逼lity。最后提醒一下,应密切注意null,这个舞会扫兴者paທrty-poopers经常出现。虽然在in子查询中,null与大量非空值连接不会对外层查询造成影响,但在使用notin子查询时,由内层查询返回的null会造成notin条件不成立。要确保子查询不会返回null并不需要太高的代价,而且这么เ做可以避免许多灾难。总结:数据集可以通过各种技巧进行比较,但一般而言,使用外连接和子集合操作符更高效。当前๩值9tvalues当我们只对最近或当前๩值感兴趣时,如何避免使用嵌套子查询或olap函数两ä者都引起排序而直接找到เ适当值,是非常吸引人的设计。如第1้章所述,解决该问题的方法之一,就是把每个值与某个“截止日຅期”相关联——就像麦片外盒上的“保质期bestbefore”一样——并让当前值的“截止日຅期”是遥远的未来例如公元299๗9年12๐月3๑1日຅。这种设计存在一些与实际相关的问题,下面讨论这些问题。使用“固定日຅期”,确定当前๩值变得非常容易。查询如下所示:色lect9hateverfromhist_data9hereitem_ຕid=somevalueaທndrecord_ຕdate=fixed_ຕdaທte_in_thefuture接着,通过主ว键找到正确的记录。当然,要参照ั的日期如果不是当前日期,就必须使用子查询或olap函数了。然而,这种方法有两个ฐ主ว要缺点。较明显的缺点:插入新า的历史数据之前,先要更新“当前值”例如今天,接着,将最新“当前๩值”和历史数据一起插入表中。这个过程导致工ื作量加倍。更糟的是,关系理论中ณ的主键用于识别记录,但具有唯一性的item_id,ไrecord_date却不能ม作为主键,因为我们会对它做“部ຖ分更新าpaທrtiaທllyupdate”。因此,必须有一个能ม让外键参照ั的代理键id字段或序列ต号,结果程序变得更加复杂。大型历史表的麻烦就是,通常它们也经历过高频๗率的数据插入,所以数据量才

-----ๅ-----ๅ-ๅ-ๅ-----ๅ------ๅpaທge81้-ๅ-ๅ-ๅ-ๅ--------ๅ--ๅ----ๅ---ๅ-ๅ-ๅ

会这么เ大。快查询的好处,能抵销缓慢插入的缺点吗?这很难说,但绝对是个值得考虑的问题๤。还有个微妙的缺点与优化器有关。优化器使用各种详细程度不同的统计数据,检查字段的最低值和最高值,尝试评估值的分布情况。假设历史表包含了自20่0่0่年1้月1日开始的历史数据。于是,我们的数据组成是“散布๧在几年间的9๗9๗9๗%的历史数据”加上“2๐99๗9年12月3๑1้日的0่1%的‘当前๩数据’”。因此,优化器会认为数据散布๧在一千年的范围内。优化器在数据范围上的偏差ๆ是由á于查询中出现的上限日期的误导即“aທndrecord_daທte=fixed_daທte_in_ຕthefuture”。此时的问题就是,如果你当查询的不是当前๩值例如,你要统计不同时段的数据变化,优化器可能错误地做出“使用索引”的决定——因为你访问的只是千年中的极小部ຖ分——但实际上需要的是对数据进行扫描。是优化器的评估偏差ๆ导致它做出完全错误的执行计划决定,这很难修正。总结:要理解优化器如何看待你的系统,就必须ี理解你的数据和数据分布方แ式。通过聚合获得结果集result色tobຘtainedbຘyaggregaທtion本节讨论一类极常见的情况:对一个ฐ或多个主ว表maintable中的详细数据进行汇总,动态计算出结果集。换言之,我们面临数据聚合aggregationofdata的问题。此时,结果集大小取决于g肉pby的字段的基数,而不是查询条件的精确性。正如第一节“小结果集,直接条件”中ณ所述,对表进行一趟asinglepass处理获得的并非真正聚合的结果否则就需要自连接和多次处理,但此时聚合函数或聚合也๣相当有用。实际上,最让人感兴趣的sql聚合使用技巧,不是明显需要sum或avg的情况,而是如何将过程性处理转化为ฦ以聚合为基础的纯sql替代方แ案。如第2章所强调的,编写高效sql代码的关键,第一是“勇往直前”,即不要预ไ先检查,而是查询完成后测试是否成功——毕竟,蹑手蹑脚地用脚趾试水赢不了游泳比赛。第二是尽量把更多“动作”放到เsql查询中ณ,此时聚合函数特别ี有用。优秀sql编程的困难,多半在于解决问题的方แ式:不要将“一个问题”转换成对数据库的“一系列查询”,而是要转换成“少数查询”。程序用大量中间变量保存从数据库读出的值,然后根据变量进行简单判ศ断ษ,最后再把它们作为ฦ其他查询的输入……这样做是错误的。糟糕的sql编程有个显着特点,就是在sql查询之ใ外存在大量代码,以循环的方式对返回数据进行些加、减、乘、除之类的处理。这样做毫无຀价值、效率低下,这里工作应该交给sql的聚合函数。注意:聚合函数非常有用,可以解决不少sql问题๤第11้章会再次讨论。然而,我现开者通常只使用最平常的聚合函数9๗t,它对大多数程序是否真的有用值得怀疑。第2章说明了使用9๗t判ศ定是否要更新记录插入新า记录是很浪费的。你可能在报表中误

--------ๅ--ๅ----ๅ-----ๅ-ๅ-ๅ--page8๖2------ๅ-ๅ--ๅ-ๅ-ๅ-ๅ---ๅ--------

用了9t。测试存在性有时会以模仿布๧尔值的方式实现:9๗'el色'y'end对于上述实现,只要存在与条件相符的记录,就会读取其中ณ每条记录。其实,只需找到เ一条记录就足以判ศ断要显示ิy还是n,通过测试存在性或限制返回记录数可以写出更高效的语句,一旦现条件相符就停止处理即可。当要解决的问题与最多、最少、最大、第一、最后有关时,聚合函数可能ม会当成olap函数使用很可能是最佳选择。也๣就是说,不要认为聚合函数仅支持9、avg等功能,否则就说明你还没有充分理解聚合函数。有趣的是,聚合函数在作用范围上非常狭窄。除了计算最大值和最小值,它们唯一能做的就是简单的算术运算:9๗t每遇到的一行加1;aທvg一方面将字段值累加,另一方面不断ษ加1้计数,最后进行除法运算。聚合函数有时可取得令人吃惊的效果,比如通过sum就可以做很多事情。喜欢数学的朋友知道,通过对数和次方函数,要在sum和乘๖积producນt之间转换有多简单。喜欢逻辑的朋友也会知道or很依赖sum,而and很依赖乘积。下面通过简单的例子说明聚合的强大作用。假设要进行装ณ运shipment处理,一次装ณ运由一些不同的订单组成,每张订单都必须ี分别做准备;只有装运涉及的每张订单都完成时装运才准备就绪。问题就是,如何判断ษ装ณ运涉แ及的所有订单都已完成。这样的情况常会生,有多种方法可以判定装ณ运是否就绪。最糟的方แ法是逐一判断每批装ณ运,而每批装ณ运内部进行第二个ฐ循环,查看有多少张订单的order_plete字段值为ฦ“n”,并返回计数为0的装ณ运id。更好的解决方案是理解“‘n’值的不存在性测试”的意图,并用子查询无论是关系还是非关系完成:色le9๗t_idfromshipments9๗herenotexists色le9ullfro摸rders9hereorder_plete=๡'n'ูandordersshipment_id=shipmentsshipment_id如果表shipments上没有其他条件了,则上述方法很糟糕,当shipments表数据量大时而且未完成订单占少数,换成以下查询会更高效:色le9๗t_idfromshipments9๗hereshipment_ຕidnotin色le9๗t_ຕidfro摸rders9hereorder_plete=๡'ูn'ู上述查询也可以稍作变形,优化器比较喜欢这个变形,但要求orders表的shipment_ຕid字段上有

--------ๅ--ๅ----ๅ-ๅ-ๅ-ๅ------page8๖3---ๅ---ๅ-ๅ-ๅ---ๅ----ๅ--ๅ----ๅ--

索ิ引:色le9t_idfromshipmentsleftouterjoinordersonordersshipment_id=๡shipmentsshipment_idandordersorder_ຕplete='n'ู9๗hereordersshipment_ຕidisnull另一个替代方แ案是借助集合操作,该集合操作会使用shipments主键索ิ引,且对orders表进行全表扫描:色le9t_ຕidfromshipmentsex9t_idfro摸rders9๗hereorder_ຕplete=๡'ูn'注意,并非所有dbຘms都实现了ex9us。还有一种方法。主要是对装ณ运中所有订单执行逻辑and操作,将order_plete为true的订单的id返回。这类操作在现实中很常见。如前所述,aທnd和乘๖法、or和加法之间关系密切。关键是把诸如“y”和“n”的flag值转换为ฦ0和1้,使用caທ色结构即可。要把order_plete转成0或1้的值可以这样写:色le9๗t_id,9order_ຕplete='y'then1el色0่endflaທgfro摸rders到目前๩为ฦ止,一切顺利ำ。如果每批装运包含的订单数固定的话,则很容易对适当字段进行sum后检查是否为预期订单数。然而,实际上希望每批装运的flag值相乘๖,并检查结果是0或是1。这个方แ法是可行的,因为只要有一张以0表示的未完成订单,乘๖法的最后结果就是0。乘๖法运算可由对数运行协助完成虽然在以对数处理时,0่不是最简单的值,但我们这个ฐ例子要做的甚至更简单。我们想要的是“第一张订单已完成、且第二张订单已完成……且第n张订单已完成”。德摩根定律laທ9sofdeman注4告诉我们,这等价于“第一张订单未完成、或第二张订单未完成……或第n张订单未完成”的情况“不成立”。由于使用聚合时,or比aທnd更容易处理。检查由or连结的一连串条件是否不成立,比检查由and连结的一连串ธ条件是否成立,要容易得多。我们要考虑的真正“谓词predicນate”是“订单未完成”,并对order_plete标志作转换,如果是n就转换为1,如果是y就转换为ฦ0。之ใ后,通过加总flaທg值,就可检查是否所有订单的flaທg值都是0都已๐完成——如果总和是0,所有订单都已完成。因此,查询可写成:色le9t_id

---------ๅ--ๅ----ๅ---ๅ-ๅ-ๅ---paທge84๒----ๅ-----ๅ-ๅ-ๅ-ๅ----ๅ--ๅ----ๅ-

from色lepbyshipment_ຕidhavingsumflag=0่甚至可以写得更简洁:色le9t_idfro摸rdersg肉pbຘyshipment_idhaທvingsum9'ูthen1้el色0่end=0还有更简单的方แ法。使用另一个聚合函数,而不必转换任何的flaທg值。注意,从字母的顺ิ序来看,“y”大于“n”,如果所有的值都是“y”,则ท最小值就是“y”。于是:色le9๗t_ຕidfro摸rdersg肉pbyshipment_ຕidhavingminorder_ຕplete='y'这个方แ法利用了“y”大于“n”,而没有考虑标志转换为数值。本方แ法更高效。上例使用了g肉pby,并以order_ຕplete值最小作为查询条件,那么,其中ณ不同的子查询或作为ฦ子查询替代品的聚集函数之间是如何比较的呢?如果先做sum操作而后检查总和是否为ฦ0่,必然导致整个ฐorders表排序。而上例中ณ使用了不太常见的聚合函数min,一般比其他查询快,其他查询因访问两个ฐ表shipments和orders而度较慢。先前๩的例子大量使用了haທving子句。如第4章所述,“粗心的sql语句”往往和在聚合语句中ณ使用having子句有关。下面这个查询oracນle就是一例,它要查询过去一个月内每个产品的每周销售情况:色le9cນsaທle_date,'9๗eek'ู,ไsumsold_qtyfromsales_historyg肉pbyprodu9csale_date,'9๗eek'havingtruncນsale_daທte,'9eek'ู=add_ຕ摸nthsysdate,-1这里的错误在于,having子句中的条件没有使用聚合。于是,dbms必须处理saທles_history中的每条记录,进行排序操作、进行聚合操作……然后过滤掉过时的数值,最后返回结果。这类错误并不引人注意,直到เsales_history表数据量变得非常大为止。当然,正确的方法是把条件放在9here子句中ณ,确保过滤会生在早期阶段,而之ใ后要处理的数据集已大为减小。

-------ๅ----ๅ--ๅ----ๅ---ๅ-ๅ-ๅ-ๅpage85๓--ๅ----ๅ-----ๅ-ๅ-ๅ-----ๅ-----

必须ี指出:对视图即聚合的结果应用条件时,如果优化器不够聪明,没有在聚合前๩再次注入过滤条件,我们就会遇到完全相同的问题๤。有些过滤条件生效太晚,应该提前๩,可做如下修改:色lectcustomer_ຕidfro摸rders9hereorder_ຕdate0在这个查询中ณ,以下having的条件乍看起来相当合理:haທvingsuma摸unt0่然而,如果aທ摸unt只能ม是正数或零,这种haທving用法就不合理。最好改为:9hereaທ摸unt0此例中,g肉pby的使用分两ä种情况。先:色lectcustomer_idfro摸rders9hereorder_daທte0่g肉pbycນustomer_id我们注意到เ,g肉pby对聚合计算是不必要的,可以用distinct取代它,并执行相同的排序和消除重复项目的工ื作:色le9๗cນtcustomer_ຕidfro摸rders9hereorder_date0่把条件放在9๗here子句中,能让多余的记录尽早被过滤掉,因而更高效。总结:聚合操作的数据应尽量少。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

书签 上一章 目录 书尾页 书架s
推荐阅读: 致十六岁的我 做一个合格的万人迷快穿 百度网盘 千里姻缘一线牵下联 明星助理上位记在磨铁 红闺秘筪 猎人无处不在的龙套生活381 快穿炮灰逆袭花样攻略txt 我爱你比永远多一天婀娜TXT下载 第二张脸小说 欠下的总是要还的图片