您好,登錄后才能下訂單哦!
從 SQL 到集算器的基本查詢語法遷移(一)單表操作
數據庫和數據分析領域,有一個強大的存在,大名 SQL,全名結構化查詢語言 (Structured Query Language)。從字面上看,這個語言的目標就是把數據“查詢”出來,而查詢這個詞給人的感覺并不是很難。但實際上,為了支持貌似簡單的查詢,需要進行大量的計算動作,甚至整個過程就是一個多步驟的計算,前后步驟之間還有很強的依賴關系,前面計算的結果要被后面使用,而后面的輸出有可能需要我們對前面的計算進行調整。
打個比方,這有點類似于去各類網點辦事,填表遞交后,相關辦事人員開始在窗口后忙忙碌碌,時不時還會甩回來幾個問題要求澄清,等到最后拿到回復,還有可能根本不是自己期望的結果!這時候,坐在辦事窗口外的我們,抓狂之余,往往會產生一個念頭,如果我們能夠看到,甚至參與到過程中,應該能夠大大地提高辦事效率。
沒錯,你應該能想到,下面要介紹的集算器,和 SQL 相比對于我們這些過程控來說,就是一個可以輕松把玩和控制的計算(不止是查詢)工具。
我們要做的,就是“照貓畫虎”地把習慣中的 SQL 操作遷移到集算器中,用小小的習慣改變,換來大大的效益提升。
首先,我們需要把數據從傳統的數據源中“搬遷”到集算器中,這樣后續的操作就可以完全在集算器中進行了。
我們最常用的數據源一般就是關系數據庫 RDB。這里使用的樣例數據,就是數據庫中的兩個數據表:
訂單信息表(order,主鍵 orderId),包括訂單編號orderId、客戶代碼customerId、雇員編號employeeId、訂單日期orderDate、發送日期sendDate以及金額money****:
orderId | customerId | employeeId | orderDate | sendDate | money |
---|---|---|---|---|---|
10248 | VINET | 2 | 2011-02-04 | 2011-02-16 | 440 |
10249 | TOMSP | 9 | 2011-02-05 | 2011-02-10 | 1863 |
10250 | HANAR | 9 | 2011-02-08 | 2011-02-12 | 1813 |
10251 | VICTE | 9 | 2011-02-08 | 2011-02-15 | 670 |
訂單明細表(orderDetail,主鍵 orderId,productId),包括訂單編號orderId、產品編號productId、價格price、數量amount、折扣discount:
orderId | productId | price | amount | discount |
---|---|---|---|---|
11059 | 17 | 39 | 12 | 0.85 |
11059 | 60 | 34 | 35 | 0.9 |
11060 | 60 | 34 | 4 | 1 |
11060 | 77 | 13 | 10 | 0.95 |
“搬遷”,或者說導入數據的過程非常簡單,如下圖所示:
集算器 | A |
---|---|
1 | =connect(“hsqlDB”) |
2 | =A1.query(“select * from order”) |
3 | >A1.close() |
首先建立數據庫連接(網格 A1),然后直接通過單表全量查詢的 SQL 語句從數據庫中讀取數據(網格 A2),最后清理現場,關閉數據庫連接(網格 A3)。
在執行了腳本后,我們可以選中網格 A2,在結果區中看看搬過來的數據,同時,order 表在集算器中也換了個身份,我們稱之為“序表”,用網格名稱 A2 代表。序表是集算器中一個非常重要的概念,現在我們可以簡單地把它理解成對應數據庫中的一張表:
其實,在集算器中,任何一個有計算結果的網格(一般是用等號“=”開始),都可以在執行腳本后,隨時選中并查看結果,并通過網格名稱 A7、B8 等隨時引用,從而滿足我們隨時監控的欲望……
接下來,我們以 SQL 中 select 語句的各個子句為線索,看看集算器中是如何操作的:
用來選擇需要查詢的字段,也可以通過表達式來對字段進行計算,或者重命名字段。與之對應的,集算器里有 new、derive、run 三個函數。
例如:只選出訂單表里的訂單編號、雇員編號、訂單日期以及金額字段,同時把金額乘以 100 后使它的單位從元變成分,把相應的結果字段重命名為 centMoney。
SQL 的做法如下:
SQL |
---|
SELECT orderId,employeeId,orderDate,money*100 centMoney FROM order |
集算器對應的做法是下表中的 A3:
集算器 | A |
---|---|
1 | =connect(“hsqlDB”) |
2 | =A1.query(“SELECT * FROM order”) |
3 | =A2.new(orderId,employeeId,orderDate,money*100:centMoney) |
4 | =A3.derive(year(orderDate):orderYear) |
5 | =A4.run(string(orderYear)+”年”:orderYear) |
6 | >A1.close() |
A3 利用 A2 的數據新建了一個序表,包含了要求的字段,包括把金額乘以 100 后用 centMoney 命名:
我們繼續看下 A4 的結果,在序表 A3 的原有字段后增加了新字段 orderYear,這也就是說 derive(新計算字段) 相當于 new(所有老字段, 新計算字段),可以看做是 new 函數的一種簡單的寫法,免去了把老字段再抄寫一遍。
A5 使用了 run 函數,直接作用是修改老字段 orderYear 的值,但是再看 A4 的結果,也變成和 A5 一樣了。這是因為 run 函數并沒有像 new、derive 函數那樣生成新的序表對象,而是在原來對象上直接做修改。
總結一下,在集算器中,new、derive、run 函數都會產生序表結果,但是 new、derive 函數會生成一個新的序表對象,像是把數據復制出來(這個行為有個專有名詞immutable),而 run 則直接修改被處理對象(行為屬于mutable)。
【延伸閱讀】之所以提出mutable這樣的行為,有兩個因素:首先是減少內存占用,從而提高性能;其次,有些實際業務的需求就需要改變原有對象,一系列的動作直接作用在一個對象上,到最后一步就得到正確結果了,而不是得到一堆中間結果,再做最后合并的動作。當然 immutable 也有它的適用場景,兩種行為本身沒有優劣之分。
用來對數據表按條件進行過濾。與之對應的,集算器通過 select 函數對一個序表的記錄進行過濾。效果如下圖所示:
針對前面的示例數據,我們希望查詢指定時段(2012 年 1 月期間)的訂單數據,可以對比一下 SQL 和集算器(網格 A3)的做法。
SQL |
---|
SELECT * FROM order |
WHERE orderDate>=’2012-01-01′ AND orderDate<‘2012-02-01’ |
集算器 | A |
---|---|
1 | =connect(“hsqlDB”) |
2 | =A1.query(“SELECT * FROM order”) |
3 | =A2.select(orderDate>=date(“2012-01-01”) && orderDate<date(“2012-02-01”)) |
4 | >A1.close() |
需要注意一下集算器表達式中有兩個細節:一是用了 date 函數把字符串轉換成日期類型,二是 AND/OR 在集算器里的寫法是 &&/||。
A3 的結果如下:
看起來和 A2 結構一致,只是數據量小了。但是我們可以做一個實驗,在網格 B3 中輸入“=A2.select(orderId=10555).run(money*10:money)”,修改 A2 序表中某些記錄的字段值,可以看到 A3 序表里這些對應記錄的值也跟著變化了。這就說明兩個序表里的記錄就是同一個東西(叫做對象會顯得更專業點),也就是說集算器里的 select 函數屬于我們前面所說的 mutable 行為。
GROUPY BY 經常和聚合函數 SUM、COUNT 等一起出現,用來將查詢結果按照某些字段進行歸類分組,并匯總統計。嚴格來說,這是兩個獨立的動作,但在 SQL 中總是一起出現,從而給人一種兩者必須同時使用的假象。事實上,這種組合是對分組操作的一種局限,或者說分組之后,能夠進行的計算遠不止 SQL 中的幾種聚合函數。在集算器中,與 GROUP BY 子句對應的是 group 函數,它可以只對數據純粹分組,每組形成一個小集合,在后面的計算中可以針對這些小集合進行簡單的聚合,也可以進行更加復雜的計算。下圖是 SQL 中利用 GROUP BY 進行分組求和的示意:
同樣還是前面的示例數據,我們希望計算 2012 年 1 月期間每個雇員的銷售總額,也就是按照雇員編號分組后求和。針對這個分組求和的計算,我們對比一下 SQL 和集算器的做法:
SQL |
---|
SELECT employeeId, sum(money) salesAmount FROM order WHERE orderDate>=’2012-01-01′ AND orderDate<‘2012-02-01’ GROUP BY 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 | >A1.close() |
A4 的結果如下:
集算器把查詢分解成了三步:
首先,是 A2 取出訂單表中的所有記錄;
然后,A3 過濾得到指定時段(2012 年 1 月期間)的訂單記錄
最后,A4 把過濾得到的記錄按照雇員編號進行分組(由函數參數中分號前的部分定義,可以同時指定多個字段),同時對每個分組(用“~”符號表示)進行求和(sum)計算,并把計算結果的字段命名為 salesAmount(分號后的部分)
看起來和 SQL 分組沒什么不用,這只是因為我們這個例子只演示了和 SQL 相同的分組查詢。實際上 A4 里 group 函數的后半部分不是必須的,也可能有的業務僅僅是想得到分組后的結果,而不在這里進行求和、計數運算;也可能針對特定值的分組有不同的聚合運算,那就針對分組后的小集合“~”寫更復雜的表達式就可以了。
同時,在其他教程中,我們還可以了解到,分組字段不局限于原始字段,可以是一個表達式,這個和 SQL 里一樣。
單純的分組屬于mutable行為,是把一個大集合的記錄拆分到多個小集合里。而加上聚合運算后,因為產生了新的數據結構,就不再是簡單的把原有記錄挪挪地方的操作了。
用來對 GROUP BY 后的聚合結果再進行過濾。在集算器里,就沒有專門對應 HAVING 的函數了,對任何序表按照條件過濾都用 select 函數,因為計算是分步的,操作指向的對象總是明確的。而 SQL 要求一句話把所有事情表達出來,遇到復雜查詢,就難免出現對同一性質的操作增加概念,以表達作用到不同的對象上。再深想一下,HAVING 概念在 SQL 里也不是必須的,它是對第一層子查詢的簡化寫法:
SELECT f1, sum(f2) f2 FROM t GROUP BY f1 HAVING sum(f2)>100
等價于
SELECT * FROM
(SELECT f1, sum(f2) f2sum FROM t GROUP BY f1) t2
WHERE f2sum >100
對更多層子查詢做同類簡化,估計會出現 HAVING2、HAVING3…類似的關鍵字,但 HAVING2 之后的簡化性價比不高,SQL 也就沒有提供了。這里又體現出分步計算的一個優勢,只需要描述計算本質需要的概念,HAVING、子查詢這些因為技術手段被迫產生的概念就可以棄用了。減少非必要概念是降低學習成本的重要手段。
我們具體看一下 SQL 和集算器的做法的對比,找到 2012 年 1 月期間銷售額超過 5000 的雇員編號和他的銷售總額:
SQL |
---|
SELECT employeeId, SUM(money) salesAmount FROM order WHERE orderDate>=’2012-01-01′ AND orderDate<‘2012-02-01’ GROUP BY employeeId HAVING SUM(money)>5000 |
集算器 | 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.close() |
A5 結果
隨著查詢復雜度逐步提升,集算器語句容易閱讀,步驟流程清晰的特點也就凸顯出來了。每一步都可以觀察結果,根據結果隨意控制計算流程,用最精簡的概念描述每個計算步驟。這還只是一個最簡單的單表查詢例子,下一篇我們會繼續了解在多表連接和聯合的情況下,集算器會有怎樣更加優秀的表現。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。