在數據庫應用開發中,我們經常需要面對複雜的SQL式計算,比如多層分組中的關聯計算。在SQL中,分組必須同時進行彙總計算,並且不能進行對象式關聯訪問,因此處理這類問題會比較複雜,只能用窗口函數嵌套多層子查詢這類高級技巧來實現。而本文要介紹的SPL能夠支持真正的分組,進行直觀的對象式關聯訪問,從而解決這類問題更加容易。
分組關聯在實際業務中遇到的很多,下面以實際業務爲藍本設計一個比較通用的例子,以此說明SPL實現分組關聯的具體過程:
計算目標:查詢出缺貨的DVD分店,即現存的DVD拷貝不到4類的分店。
數據結構:
l Branch表,存儲DVD分店信息;
l DVD表,存儲DVD的標題及分類信息,DVD是虛擬的數據,比如“變形金剛4”是一個DVD,但它不是一張可見的光盤
l DVDCopy表,存儲DVD的多張拷貝,DVD拷貝是真正的光盤,以實體形式存放於各個分店。注意:DVDCopy表以BranchID字段和Branch表關聯,以DVDID字段和DVD表關聯。
下面是部分數據示例:
Branch表:
BID | Street | City |
B001 | street1 | New York |
B002 | street2 | Houston |
B003 | street3 | LA |
B004 | street4 | Lincoln |
DVD表:
DVDID | Category | Title |
D001 | science fiction | Transformers IV |
D002 | science fiction | Transformers II |
D003 | science fiction | Guardians of the Galaxy |
D004 | act | The Expendables III |
D005 | sport | Need for Speed |
D006 | feature | Grace of Monaco |
DVDCopy表:
CopyID | DVDID | BID | Status | LastDateRented | LastDateReturned | MemberID |
C000 | D001 | B001 | 7/10/2014 | 7/13/2014 | M001 | |
C001 | D004 | B001 | 7/10/2014 | 7/13/2014 | M001 | |
C002 | D001 | B001 | 7/10/2014 | M001 | ||
C003 | D005 | B001 | 7/10/2014 | 7/13/2014 | M003 | |
C004 | D006 | B001 | 7/10/2014 | 7/13/2014 | M003 | |
C005 | D005 | B002 | 7/10/2014 | 7/13/2014 | M003 | |
C006 | D002 | B002 | 7/10/2014 | 7/13/2014 | M006 | |
C007 | D002 | B002 | 7/10/2014 | 7/13/2014 | M007 | |
C008 | D001 | B002 | 7/10/2014 | 7/13/2014 | M008 | |
C009 | D004 | B002 | 7/10/2014 | 7/13/2014 | M009 | |
C010 | D005 | B002 | 7/10/2014 | 7/13/2014 | M010 | |
C011 | D006 | B002 | Miss | 7/10/2014 | 7/13/2014 | M010 |
C000 | D001 | B003 | 7/10/2014 | 7/13/2014 | M001 | |
C001 | D004 | B003 | 7/10/2014 | 7/13/2014 | M001 | |
C002 | D001 | B003 | Miss | 7/10/2014 | M001 | |
C003 | D005 | B003 | 7/10/2014 | 7/13/2014 | M003 |
說明:
1. 計算結果應當是Branch表中的某些記錄。
2. DVDCopy表中的Status字段如果是“Miss”,則說明光盤丟失。LastDateReturned字段如果爲空,則說明光盤借出尚未歸還。顯然,丟失或未歸還的光盤不在計算範圍內,應當過濾掉。
3. 應當考慮某些分店可能在DVDCopy表中不存在記錄,雖然這種情況比較罕見。
解題思路:
1. 從DVDCopy表過濾出店裏現存的DVD拷貝(沒有丟失或借出)。
2. 按照BID對DVDCopy表分組,每組就是一個門店所有的DVD拷貝。
3. 找到每個門店的DVD拷貝對應的DVD,再計算出這些DVD的分類數量。
4. 查詢出現存的DVD分類數量小於4的門店,這樣的門店符合要求。
5. 找到DVDCopy表中沒出現過的門店,這樣的門店也符合要求。
6. 將兩類符合要求的門店合併。
SPL代碼:
A | |
1 | =Branch=db.query("select * from Branch") |
2 | =DVD=db.query("select * from DVD") |
3 | =DVDCopy=db.query("select * from DVDCopy") |
4 | =DVDCopy.switch(DVDID,DVD:DVDID; BID,Branch:BID) |
5 | =DVDCopy.select(STATUS!="Miss" && LASTDATERETURNED!=null) |
6 | =A5.group(BID) |
7 | =A6.new(~.BID:BonList, ~.(DVDID).id(CATEGORY).count():CatCount) |
8 | =A7.select(CatCount<4) |
9 | =A8.(BonList) | (Branch \ A7.(BonList)) |
10 | >file("shortage.xlsx").xlsexport@t(A9) |
A1-A3:從數據庫中檢索數據,分別命名爲變量Branch、DVD、DVDCopy。計算結果如下:
A4:=DVDCopy.switch(DVDID,DVD:DVDID; BID,Branch:BID)
使用函數switch,將DVDCopy表中的DVDID字段切換成DVD表中對應的記錄,將BID字段切換成Branch表中對應的記錄。這一步是對象式關聯訪問的基礎,計算後DVDCopy的結果如下:
淺藍色字體表示該字段對應爲某條記錄,點擊後可查看,如下圖:
此時,只需用操作符“.”就可以進行對象式關聯訪問,比如DVDCopy.(DVDID). (CATEGORY)表示每個DVD拷貝對應的DVD分類。DVDCopy.(BID)則可以取得每個DVD拷貝對應的分店詳情(完整記錄)。
A5:=DVDCopy.select(STATUS!="Miss" && LASTDATERETURNED!=null)
這句代碼用來過濾數據,即:丟失的,未歸還的DVD拷貝不在計算範圍內,過濾後A5的值如下:
A6:=A5.group(BID)
上述代碼用來對A5中的數據按照BID分組,每行代表一個門店的所有DVD拷貝,如下:
點擊淺藍色字體,可以看到組內成員:
可以看到,函數group只對數據進行分組,並不會同時進行彙總計算,這一點和SQL中的分組函數不同。當我們需要對分組後的數據進行較深入加工,而不是簡單彙總時,用SPL的group函數會更方便,比如A7中的代碼。
A7:=A6.new(~.BID:BonList, ~.(DVDID).id(CATEGORY).count():CatCount)
上述代碼用來計算每個門店對應的DVD拷貝各有幾類。函數new可以根據A6中的數據生成新的對象A7,A7有兩個列:BonList和CatCount,BonList直接來自A6中組內數據的BID列,CatCount來自於組內數據的DVDID列。CatCount的算法分爲三部分:~.(DVDID)找到每個門店所有的DVD拷貝對應的DVD記錄;id(CATEGORY)去除這些DVD記錄中重複的Category;count()用來計算Category的數量。計算結果如下:
即:B002門店有3類DVD拷貝,B003門店有3類,B001門店有4類。
A8:A7.select(CatCount<4)
上述代碼執行查詢,求出CatCount小於4的門店,結果如下:
上述缺貨的門店是根據DVDCopy表計算出的。但有些嚴重缺貨的門店也許不會出現在DVDCopy表,比如該門店所有的DVD拷貝都借出去了,或者該門店完全沒有DVD拷貝,因此要把這部分門店合並進來,代碼如下:
A9:=A8.(BonList) | (Branch \ A7.(BonList))
上述代碼中,運算符“|”表示將兩個數據集進行並集計算(可用union函數代替),運算符“\”表示差集計算(可用函數diff代替)。A8.(BonList)、Branch、A7.(BonList)分別代表:DVDCopy表中缺貨的門店、所有的門店、DVDCopy表中出現過的門店,其值分別爲:
A9就是本案例最終的計算結果,其值爲:
A10:>file("shortage.xlsx").xlsexport@t(A9)
最後將結果導出到excel文件shortage.xlsx,打開文件查看結果如下:
通過這個例子我們可以看到,SQL缺乏顯式集合,不能用A8或Branch這樣的變量來代表數據集,因此上述簡短的SPL代碼必須用幾個冗長的SQL才能實現。
另外,SPL可被報表工具或java程序調用,調用的方法也和普通數據庫相似,使用它提供的JDBC接口即可向java主程序返回ResultSet形式的計算結果,具體方法可參考相關文檔。【Java如何調用SPL腳本】