第22期:有序遍歷語法

接前一期 【數(shù)據(jù)蔣堂】第21期:常規(guī)遍歷語法
我們繼續(xù)討論遍歷運算的語法規(guī)則。
5. 序號的引用
SQL延用了數(shù)學上的無序集合概念,遍歷時也不關注次序。但計算機只能一步步地執(zhí)行(暫先不考慮并行計算的情況),遍歷集合時總會有個次序,充分利用這個次序就可以方便地表達更豐富的計算需求。
比如我們想從一個集合取出半數(shù)成員構成新集合。這看起來象是過濾運算,但過濾條件和集合成員本身并沒有關系,而是由遍歷成員時的次序號決定的。
只有~寫法無法方便地描述出這種運算,這時候還需有個符號(標識符)來表示遍歷的次序號。
事實上,大部分高級語言在寫循環(huán)語句時都會有個循環(huán)變量來表示次序號,就起到了這個作用。但許多集合化語言中并沒有提供這個機制,碰到這種運算就只能再寫循環(huán)才能完成,就顯得很繁瑣。SQL也沒有表示遍歷次序后的方案,只能先用子查詢?nèi)藶橹圃煲粋€序號出來再針對這個序號進行過濾。
我們用#來表示遍歷的次序號,那么這個運算就很容易寫了:
- A.select(#<=A.len()/2) 取前一半成員
- A.select(#%2==0) 取偶數(shù)位置的成員
對應地,在過濾運算中我們總是返回滿足條件的成員,但有時候我們并不關心具體成員而只關心成員的次序號,那么我們還有必要設計返回次序號的過濾函數(shù):
- A.pselect( ~>5 ) 返回大于5的成員的次序號
類似地,還可能有:
- A.pmax() 返回***值的次序號
- ...
6. 相鄰成員和集合的引用
考慮到遍歷的次序時,我們還可以進一步豐富計算的描述能力。
比如有12個月的銷售額數(shù)據(jù)已經(jīng)按次序準備好,要計算哪些月份的增長率超過了5%。
SQL很難寫這種跨行計算,需要用JOIN語句或窗口函數(shù)把上月數(shù)據(jù)和本月數(shù)據(jù)對齊,然后再來計算增長率,這不可避免地用到子查詢。
如果我們提供了相鄰成員的引用語法,就可以很容易描述這個計算了。
比如用[i]表示和當前成員距離為i的成員,再結合前述的#寫法,上面的計算就可以寫成:
- A.(if(~/~[-1]>1.05,#,0)).select(~>0)
~[-1]表示前一個成員,也就是上月銷售額。找出把增長率超過5%的月份(也就是#),其它月份清0,***選出這些非0的月份。
如果用上述的返回次序號的過濾函數(shù),還可以寫成更簡單的形式:
- A.pselect(~/~[-1]>1.05)
除了相鄰成員外,還可能有相鄰集合的引用,比如還是上面的集合,我們希望計算前后各一個月的銷售額移動平均值。
把[i]表達式擴展成[a,b]寫法來表示相鄰成員構成的集合,這個運算就很容易描述了:
- A.(~[-1,1].avg())
相鄰集合還可能有更復雜的情況,比如計算到當月的累積銷售額。
允許[a,b]寫法中a缺省表示從***個成員開始(對等地,b缺省可以理解為***一個成員),這個運算可以寫成
- A.(~[,0].sum())
同樣的,面向結構化數(shù)據(jù)計算也還可以直接使用字段名,比如如果例子中的集合是由“月份”和“銷售額”的兩個字段構成的表,則上述的運算可以分別寫成:
- A.select(銷售額/銷售額[-1]>1.05) 這里結果集中已有月份字段,不再需要用#了
- A.derive(銷售額[-1,1].avg:移動平均值) 增加一個字段表示移動平均
- A.derive(銷售額[,0].sum():累計銷售額)
考慮到有序遍歷時,其語法規(guī)則就比常規(guī)遍歷要復雜許多,而這些有序遍歷也是實際計算中經(jīng)常發(fā)生的,如果遍歷語法不支持,會導致這些計算難以描述,程序員就要再編寫多行循環(huán)語句,繁瑣且影響可讀性。
SQL沒有提供有序遍歷的語法,經(jīng)常需要使用子查詢和窗口函數(shù)來生成序號,某些復雜些的有序遍歷運算甚至寫不出來,也要用存儲過程手段轉換成多行循環(huán)語句才可以。從這個意義講,SQL雖然是集合化語言,但集合化不夠徹底。