淺解比 SQL 更好用的 SPL(二)
從 SQL 到SPL基本查詢語法遷移 之多表操作
上一篇我們針對單表的情形瞭解瞭如何把資料計算從 SQL 查詢遷移到集算器,或者更準確地說,遷移到集算器所使用的SPL集算語言。這個遷移過程,既有相同的概念,也有不同的思路。
接下來,我們一起針對多表的情況看一下集算器和SPL語言是如何發揮更大的優勢的。
JOIN 連線兩個記錄
在前面的例子中,我們得到了每個僱員的銷售額,如果進一步還想知道每個僱員給出的最小折扣,那就又複雜了些。因為折扣資訊在另一張訂單明細表裡。急性子的朋友可能上來就要 JOIN 兩個表,然後再聚合兩個測度欄位——這樣就算錯了!正確的做法應該是每個測度欄位先按分組聚合出結果,之後再 JOIN。這是因為先 JOIN 的話可能導致結果中出現重複記錄。
按照這個思路寫出 SQL,並和集算器中的SPL程式碼進行比較:
SQL
select t1.employeeId, salesAmount, lowestDiscount from (
select employeeId, sum(money) salesAmount from order
where orderDate>=’2012-01-01′ and orderDate<‘2012-02-01’
group by employeeId
having sum(money)>5000) t1
left join (
select employeeId, min(discount) lowestDiscount from order
join orderDetail on order.orderId=orderDetail.orderId
where orderDate>=’2012-01-01′ and orderDate<‘2012-02-01’
group by employeeId) t2
on t1.employeeId=t2.employeeId
集算器 | A |
---|---|
1 | =connect(“hsqlDB”) |
2 | =A1.query(“select * from order”) |
3 | =A2.select(orderDate>=date(“2012-01-01”) && orderDate<date(“2012-02-01”)) |
4 | =A3.group(employeeId;~.sum(money):salesAmount) |
5 | =A4.select(salesAmount>5000) |
6 | =A1.query(“select * from orderDetail”) |
7 | =join(A3:order,orderId;A6:orderDetail,orderId) |
8 | =A7.group(A7.order.employeeId:employeeId;~.min(orderDetail.discount):lowestDiscount) |
9 | =join(A5:r1,employeeId;A8:r2,employeeId) |
10 | =A9.new(r1.employeeId:employeeId,r1.salesAmount:salesAmount,r2.lowestDiscount:lowestDiscount) |
11 | >A1.close() |
A5 之前是我們做過的聚合符合條件的按僱員分組的銷售額;
A6 查出訂單明細表的所有資料;
A7 中的SPL用到了對應 SQL 的多表連線的新函式 join。join 函式把兩個表連線起來,這裡它的引數的含義是:將 A3 序表起一個別名 order,A6 序表起一個別名 orderDetail,然後用兩個表各自的 orderId 作為關聯欄位,觀察 A7 的結果如下:
可以看到,join 後形成的序表有 order、orderDetail 兩個欄位,兩個欄位的值分別直接指向了兩個原始序表的記錄,這就是說欄位的值可以是複雜資料型別(包括序表、序列型別),也可以巢狀多層結構的資料,這是 SQL 語法裡不允許的,也是SPL的特色和優勢之一。
下圖比較形象地說明了這種結構(長方形表示序表、橢圓形表示記錄、三角形表示關聯欄位):
A8 直接用SPL中的 group 函式對這個複雜結構的序表進行分組聚合運算,獲得各個僱員曾經給出的最小折扣。
再需要注意的一點是 A3 序表被後面兩個不同的動作(A4、A7)都使用了一次,達到了中間結果複用的效果。這種方式對於越是複雜的計算,往往作用越大,可以起到類似“資料模組化”的作用。
在 A9 中通過SPL的 join 函式把 A5(銷售額超過 5000)、A8(最小折扣)兩個序表連線起來;
最後,A10 使用 new 函式生成新的序表結構,而資料來自 A9 複雜結構記錄的不同層次位置。
A10 最終的結果如下:
簡單回顧一下,對於最後這個比較複雜的 SQL,如果你是個 SQL 高手,可能會看出第二個子查詢裡的 where 並不是必須的,之所以保留它,是因為有可能會縮小處理資料的範圍,從而提升一些效能。這也是 SQL 的另一個特點,一方面需要不喘氣的一句話把整個查詢表達出來,另一方面還需要同時考慮效能優化因素,甚至即便考慮到了優化方案,也不一定能輕鬆、自然地描述出來。
在上面的例子中,不難體會出集算器以及SPL對分步過程的重視,每一個步驟的結果都是可以隨時觀察的,而且前面步驟的結果也可以重複利用,同時執行步驟也可以被程式設計師自由定製。這些特點最直接的好處是降低了學習和編碼的難度,而更本質的是符合人的自然思維,為描述複雜計算奠定了基礎。
UNION 等合併兩個集合
SQL 中還有一類針對多個集合(表)的運算,就是常說的並、合、交、差(UNION、UNION ALL、INTERSECTION、MINUS),與之對應的,在SPL中表示為 &、|、^、\。
集合運算時常需要判斷一條記錄是否重複,在這個細節上,SQL 和SPL有一點區別。SPL裡因為序表、記錄,以及欄位的值都被看成是一個物件,所以在進行並集運算時,可以比對元素是否為同一個物件,而 SQL 的記錄是抽象的,不是一個實體物件,所以只能通過逐個比對兩條記錄的欄位值來判斷是否重複。
我們來看一下實際的例子:
集算器 | A | B | |
---|---|---|---|
1 | =connect(“hsqlDB”) | ||
2 | =A1.query(“select * from order”) | ||
3 | =A2.select(orderId>10251 && orderId<10256) | =A2.select(orderId>10254 && orderId<10258) | |
4 | =A3&B3 | =A3 | B3 |
5 | =A3^B3 | =A3\B3 | |
6 | >A1.close() |
A3 序表的結果:
B3 序表的結果:
A3&B3,去掉重複合併到一起,並集運算後得到 A4 序表的結果:
A3|B3,保留重複合併到一起,合集運算後得到 B4 序表的結果:
A3^B3,取 A3 和 B3 裡相同的記錄,交集運算後得到 A5 序表的結果:
A3\B3,A3 裡去掉 B3 中存在的記錄,差集運算後得到 A5 序表的結果:
其它常用語法
最後,再看兩個常用的 SQL 函式語法,我們來對比看一下SPL的實現。
CASE WHEN …
SQL 中的 case when … then … else … end,在SPL中用 if 函式,語法是if(條件, 真值, 假值),下面的例子是把 employeeId<5 的分為一組,剩餘的分到另一組:
SQL |
---|
select (case when employeeId<5 then 1 else 2 end) groupId, orderId from order |
集算器 | A |
---|---|
1 | =connect(“hsqlDB”) |
2 | =A1.query(“select * from order”) |
3 | =A2.new(if(employeeId<5,1,2):groupId,orderId) |
4 | >A1.close() |
COALESCE(exp1,exp2…expn)
SPL中仍然用 if 函式,if(條件, 真值, 假值),下面這個例子把 1000 這個特殊的值賦值給為 null 的 employeeId。
SQL |
---|
select coalesce(employeeId,1000) employeeId,orderId from order |
集算器 | A |
---|---|
1 | =connect(“hsqlDB”) |
2 | =A1.query(“select * from order”) |
3 | =A2.new(if(employeeId==null,1000,employeeId):employeeId,orderId) |
4 | >A1.close() |
集算器(SPL)≠SQL
看完上面這些例子,會給人一個感覺:集算器,或者其中的SPL程式碼只是 SQL 的替代品,而常用的關係資料庫系統(RDBMS)本身已經是儲存能力(資料表)+ 計算能力 (SQL) 的綜合體了,還要這麼一個外來的計算體系幹嘛?
針對這個疑問,有兩個層面的答案。首先是資料層面,並非所有要計算的資料都在資料庫裡。當你手邊有個文字、excel 或從一個網路服務臨時得到一些資料想要計算,或者從大量終端採集上來的資料馬上要處理出結果……如果這些都不得不倒騰到資料庫裡然後再計算,還真是有點太繞了,更何況還可能涉及到資料庫的安裝部署,或者許可權控制等瑣碎而現實的問題。
其次是計算層面,對於很多複雜的計算過程,SQL 因為天生缺陷無法做到開發高效、維護高效、執行效高。這也是我們這篇文章最想說明的地方,SQL 的計算主要還是面向查詢,而這些對“高效”的追求,面向的是更廣泛的資料計算,這正是集算器和SPL語言所要重點改善的。
正因為有了這兩個層面的原因,集算器和SPL語言已經可以獨立於各種資料儲存形式,在“儲存”和“應用”之間,作為一個功能完整、形式靈活、部署方便的“資料計算中介軟體”而存在。這方面的內容,我們會在後面的章節做更多的介紹和探討。
這裡,我們只是先簡單看一下集算器和SPL語言如何基於文字、Excel 等資料來源進行計算。事實上,前面這些例子中的計算步驟完全不用改寫,只需要改變載入資料那一句就可以了。也就是把:
=A1.query(“select * from order”)
改為下列任意方式之一:
=file(“d:/data/order.txt”)[email protected]()// 從文字檔案載入資料表
=file(“d:/data/orderDetail.xls”)[email protected]()// 從 excel 載入資料表
=httpfile(“ http:/ /127.0.0.1/service ”,” param1=value1¶m2=value2″) // 從 http server 載入資料表
……
很明顯,通過這種簡單明瞭的資料載入方式,可以將各種資料來源的資料形成統一的“序表”,從而在一個計算過程裡可以輕鬆混合使用。換句話說,不必為了計算能力而要求必須統一儲存方式。同樣的,在集算器和SPL的環境中,計算結果的儲存,也和載入資料一樣方便,也是多樣化的,可以由程式設計師自由選擇。