本文来自:http://www.ituring.com.cn/article/details/472
为了以有趣的方式更好地帮助你形成面向集合的思维方式,我将给出自己最喜欢的游戏之一——集合。你可以在线玩这个游戏,网址是www.setgame.com/puzzle/set.htm,每天都会贴出一个新的集合谜题。集合游戏是一个每张卡片上有4个特征的谜题:颜色、符号、底纹以及符号的个数。颜色有红、绿和紫色。符号有花体、方块以及椭圆。底纹可以是实的、斑点的及外轮廓线。至于符号个数,每张卡上可能有一个、两个或三个符号。要成为集合只有一条规则:一个集合只需要3张卡上的每一种单独的特征要么都一样要么全不相同。因此,一个有效的集合的例子就是3张卡上有相同的符号(例如方块),3张卡上都有两个符号,3张卡的颜色各不相同,并且3张卡都是实心图案。所有使得3张卡上具有相同特征或者完全不同特征的组合都能构成集合。游戏的目标就是在12张卡中寻找集合。当找到一个集合后,这3张卡片就将被去掉,并加入3张新卡片。在在线版本中,12张卡片总会有6个集合,你的目标就是要找出所有这6个集合。
图4-1给出了12张卡片,从中你应该可以找出6个集合。每张卡片左上角的字母表示颜色(R=红色,G=绿色,P=紫色)。试试吧。
答案在本章的最后(可别作弊哦!)。我首先给出第一个集合:第1行第2列(实心绿色花体),第3行第2列(实心红色花体)和第3行第4列(实心紫色花体)。这个游戏迫使你按集合来思考,除此以外别无他法。如果你觉得找出集合很困难,我打赌你在写SQL语句的时候就会觉得以面向集合的方式来思考更困难。SQL语句的书写工作与这个游戏是具有同样的前提的(必须要有面向集合的思维方式!),只不过是另外一个不同的游戏罢了。既然你已经在面向集合的思考方面热过身了,让我们来看看从面向过程的思维方式转变到面向集合的思维方式的几种方法。
从面向过程转变为基于集合的思维方式
你首先需要做的是停止那些一次处理一行数据的过程化步骤思维。如果你一次只想处理一行,实现你的想法将会使用短语如“for each row do x”或者 “while value is y do x”。试着把思路转移到使用类似于“for all”的短语上来。有关于此的一个简单的例子就是加数字。当你按过程化来考虑的时候,你就会想把一行的数值与另一行的数值加起来直到把所有行加到一起。对所有行求和的思维与此不同。正如我所说的,这是个非常简单的例子,但类似的思维方式的转变同样适用于更复杂的不是那么明显的情况下。
例如,如果我让你生成一个所有在公司里每个工作岗位上干了同样年数的员工列表,你会怎么做?如果你按照过程化的思维方式来进行,你可能需要去查看每个工作岗位,计算出在这个岗位上的工作年限,然后与在其他各个岗位的工作年限比较。如果年数不匹配,那么你就不会把这个员工放到结果列表中。这种方法将会通过如下的一个自联结的查询来进行:
相反,如果你使用面向集合的观点来看待这个问题,你就会写出对表只进行一次访问的查询,按照员工进行分组,然后筛选出那些在某个岗位上工作的最短年数与某个岗位上工作的最长年数相一致的员工。
代码清单4-1分别列出了这两种选择的执行过程。你可以看到基于集合的方法使用了较少的逻辑读取并拥有更简洁的计划。
代码清单4-1 过程化与基于集合的方法的对比
关键是要开始以完成后的结果的形式(而不是以处理步骤的形式)来思考。要找集合的特征而不是单独的步骤或行为。在基于集合的思维方式中,所有事物都以应用于集合的筛选条件或约束所定义的状态存在。你不再按照过程步骤来思考而是要按照集合的状态来思考。图4-2给出了处理步骤图与嵌套集合图之间的一个比较用来说明我的观点。
处理流程图表明结果集(A)是通过一系列以其他步骤为基础的处理步骤来产生的最终答案。B通过遍历C和D得出,然后A通过遍历B和E得出。但是,嵌套集合图中将A看做是不同集合的组合的结果。
另一种常见的但却是错误的思考方式就是将表看做是排过序的行的集合。想想你所看到的典型的表的内容。它们是在一个表格或者类似于工作表的视图中展示出来的。但是,一张表代表一个集合,集合是无序的。通过表明一定顺序的方法来展示表可能会引起混淆。回忆一下在第2章中ORDER BY子句是在一个SQL语句执行的最后来实现的。SQL是基于集合理论的,正因为集合中的行没有预先确定的顺序,排序就必须在符合查询条件的数据行都被从集合中抽取出来之后再单独进行。图4-3给出了一种更恰当的方法来说明表中的内容是无序的。
看上去区分这些你在思考问题方式上的细小差别并不是那么重要,但是这些细小的转变是正确理解SQL的基础。让我们来看一个例子,通过面向过程的思维方式和基于集合的思维方式分别来写一个SQL语句,以帮助你弄清楚二者之间的区别。
面向过程vs.基于集合的思维方式:一个例子
在这个例子中,任务是要计算出一个顾客在各个订单之间的平均天数。代码清单4-2给出了通过面向过程的思维方式的实现方法。为了让例子的输出较短,我将只计算一个顾客的,但是由此可以很容易地转变为计算所有顾客的。
代码清单4-2 面向过程的思维方式
这看上去相当优雅,不是吗?在这个例子中,我依次执行了一系列查询来展示我是如何思考的,并按照逐步进行的过程方法来书写查询语句。如果你对分析函数LAG的使用方法还不是很熟悉,不必担心,分析函数将在第8章进行讲解。简单来说,我所做的事情就是按照orderdate的顺序读取102号顾客的每一行订单信息,然后使用LAG函数,回过头再看前一行的订单数据以获得该行的orderdate。当得到这两个order_date(当前订单行的日期以及前一行订单的日期)以后,利用这两个日期相减得出中间相差的天数就非常简单了。最后,我使用求平均值聚合函数来得到最终的答案。
你可能会指出这个查询是按照非常过程化的方式建立起来的。理解这种方式最好的办法就是依次来看几个不同的查询以展示如何建立最终结果集的。在这个过程中我可以看到相关详细信息。当以基于集合的思维方式进行思考的时候,你会发现并不需要去关心每一个单独的元素。代码清单4-3给出了一个按照基于集合的思维方式来写的查询例子。
代码清单4-3 基于集合的思维方式
这样怎么样?我根本不需要任何花哨的技巧来解决这个问题。我用来计算订单之间的平均天数所要做的事情就是计算出第一笔和最后一笔订单之间的天数以及总的订单数。我不需要一步一步地来考虑问题,正如我也不会写一个一行一行读取数据然后计算出结果的程序。我所需要的就是把我考虑问题的思维方式转变到将集合数据作为一个整体来考虑。
我并不是完全无视过程化方法。可能有的时候你不得不采用这样的方法来完成工作。然而,我想鼓励你进行思维方式的转变:首先寻找基于集合的方式,只有在需要的时候才采用更大程度的过程化方法。通过这样做,你可能会发现自己可以得到更简单、直接的,通常性能也更好的解决方案。