第26期:再談有序分組
細(xì)心的讀者可能會發(fā)現(xiàn),我們在討論有序分組時只研究了待分組集合的成員次序?qū)Ψ纸M運算可能的影響,但即然要考慮集合的有序性,那么結(jié)果集的成員次序是不是也有業(yè)務(wù)意義呢?
確實有意義,不過重要程度不如原集有序性。
一
分組結(jié)果集的有序性有兩個方面,一是這些分組子集以什么次序返回,二是分組子集的成員是什么次序構(gòu)成的。
在考慮有序集合的等值分組運算時,我們認(rèn)為在缺省狀態(tài)下分組子集保持原序最為合理,即每個分組子集成員***次在原集中出現(xiàn)的次序。這個原因在于:其它次序(比如按分組字段值)可以針對結(jié)果集再排序而獲得,而原序很可能在分組完成后就丟失了,或者至少再獲得會比較困難。
比如我們要統(tǒng)計一本教科書中單詞的重復(fù)次數(shù),這是個簡單的等值分組運算,缺省的結(jié)果集應(yīng)當(dāng)是以新單詞在書中出現(xiàn)的先后次序為序的,這個次序是有業(yè)務(wù)意義的,向?qū)W生講授這本書時可以按該次序讓學(xué)生預(yù)習(xí)生詞。而這個次序如果不是在分組運算后返回,就會很難獲得了。需要給每個單詞人為增加一個在書中出現(xiàn)的次序號,分組時同時把次序號的最小值也統(tǒng)計出來,然后再按這個值排序,***又丟棄這個值。運算過程繁瑣且效率低。
對位分組和枚舉分組的結(jié)果集次序,顯然應(yīng)當(dāng)與基準(zhǔn)集合一致。而有序分組的結(jié)果集次序,則顯然按每個分組產(chǎn)生的次序最為合理。
二
基于無序集合的SQL沒有約定分組結(jié)果集的次序,返回結(jié)果集就不能保證原序了。在實踐上,數(shù)據(jù)庫一般是采用HASH方法來實現(xiàn)分組的,這時結(jié)果集的次序常常是HASH值的次序,而HASH值次序毫無業(yè)務(wù)意義,在關(guān)心次序時就還需要再排序,而為了獲得排序依據(jù)就要象前面例子中說的那樣在原集中新增序號信息,并參與到分組運算中,麻煩且低效。還有的數(shù)據(jù)庫是用排序來做分組,結(jié)果集的次序就是分組字段(表達式)的次序,這個次序有一定業(yè)務(wù)意義了,但想還原出原序也不容易。
我們前面說過,SQL中用LEFT JOIN的方法可以實現(xiàn)出對位和枚舉分組的效果,但無論是HASH方法還是排序方法,結(jié)果集都會喪失基準(zhǔn)集合的次序。而對位和枚舉分組的結(jié)果集次序又是非常必要的,想通過再排序來獲得這個次序,需要在基準(zhǔn)集合中就要維護個次序號,這會使得本來簡單的單值成員集合變成多字段的記錄集合,而且當(dāng)基準(zhǔn)集合需要插入/刪除成員時還要繼續(xù)維護序號會是個很麻煩的事情,被改動成員后面的成員序號都要調(diào)整。所以SQL實現(xiàn)對位和枚舉分組是個很繁瑣的事情。
三
至于子集成員的次序,原則上也應(yīng)當(dāng)缺省保持原序,也就是在原集合中的次序。不過,它是否有意義取決于后續(xù)要執(zhí)行的動作。
比如SQL就完全不關(guān)心這個次序,SQL在分組后會強制聚合,而且只有SUMM/COUNT這些運算結(jié)果與執(zhí)行次序無關(guān)的常規(guī)聚合運算,分組子集的成員次序就沒有意義了。
但有些非常規(guī)聚合運算可能和執(zhí)行次序有關(guān),比如用登錄日志(日志缺省都是按事件發(fā)生時刻有序的)列出每個用戶的***兩次登錄的時間間隔,就是按用戶分組后取出分組子集的***兩條記錄計算時間差,這時就會關(guān)心子集成員的次序了。不過,這已經(jīng)是聚合運算的范疇了,我們將在后面的文章中再詳細(xì)討論。