跨行引用
早期SQL不直接支持跨行引用,要生成序號後再JOIN,極其繁瑣困難。引入窗口函數後的SQL能夠較方便地引用其它行數據,但寫法仍不簡潔,有多個跨行引用項時代碼會很長。而且如前所述,窗口函數在其它運算結果集基礎上再實施,對窗口函數計算值的再引用就要寫成子查詢的形式,仍然繁瑣。
MySQL不支持窗口函數,但支持在SQL中使用變量,可以引用到前面的行,但無法引用到後面的行。
集算器提供了方便自然的跨行引用語法。
各產品月銷售表結構為:產品、月份、銷量;現要找出銷量比上月多10%的記錄。
A
1
=db.query("select * from 銷售表 order by 產品,月份")
2
=A1.select(if(產品==產品[-1],銷量/銷量[-1])>1.1)
排序後可以簡單用[-1]就可以引用前一月的數據,且可以直接基於跨行計算值過濾。使用SQL窗口函數則要用子查詢,MySQL則要定義兩個臨時變量。
再計算上表中各月前後一個月的銷量移動平均值:
A
1
=db.query("select * from 銷售表 order by 產品,月份")
2
=A1.derive(if(產品==產品[-1]&&產品==產品[1],銷量{-1:1}.avg()):移動平均)
計算移動平均涉及到向後引用和集合引用,用[1]可引用下一行數據,{-1:1}可引用從上一行到下一行的字段值集合。類似地,SQL窗口函數也需要子查詢先把相應行計算出來再做移動平均;而MySQL的變量不能後向引用,就很難直接計算了。
再看一例,簡化的事件表結構為:序號,時刻,…;時刻應當和序號同步遞增,但可能有錯誤,需要找出時刻沒有和序號同步遞增的記錄。
A
1
=db.query("select * from 事件表 order by 序號")
2
=A1.select(時刻!=max(時刻{:0})||時刻!=min(時刻{0:}))
和前後所有記錄對比
取集合時還可以從頭取後或取到尾。SQL窗口函數也支持類似的寫法,但兩次比較要做兩個不同方向的排序,當然了必須要用子查詢。
有序分組
SQL只提供與次序無關的等值分組,但有時分組的鍵值並不能在每條記錄中找到,而是和記錄的次序有關,這種情況,用SQL又需要使用窗口函數(或其它更麻煩的手段)制造出序號才能實現。
集算器提供了與次序相關的分組機制,方便用於與連續區間相關的計算。
收支表結構為:月份、收入、支出;找出連續虧損達三月或以上的那些月份的記錄。
A
1
=db.query("select * from 收支表 order by 月份")
2
=A1.group@o(收入>支出).select(~.收入<~.支出 && ~.len()>=3).conj()
group@o表示在分組時只比較相鄰記錄,如果相鄰值發生變化則會分出一個新組。這樣就可以根據收入支出的比較把收支記錄分成贏利、虧損、贏利、…這樣的組,然後取出其中虧損且成員不少於3的組再合並起來。
還是這個表,希望計算收入最長連續增長了幾個月。可以設計這樣的分組機制:收入增長時和上月分作一個組,收入下降時則分出一個新組,最後統計組成員的最大值。
A
1
=db.query("select * from 收支表 order by 月份")
2
=A1.group@i(收入<收入[-1]).max(~.len())
group@i將在條件變化時分出一個新組,即收入降低時。
在窗口函數的支持下,SQL也能實現本例和上例的思路,但寫法非常難懂。
區間合並也是常見的有序分組運算。設有事件發生區間表T有字段:S(開始時刻)、E(結束時刻);現在要將這些區間中重疊部分去除後再計算該事件實際發生的總時長。
A
1
$select S,E from T order by S
2
=A1.select(E>max(E{:-1}))
去除被包含的條目
3
=A2.run(max(S,E[-1]):S)
去除重疊時間段
4
=A2.sum(interval@s(max(S,E[-1]),E))
計算總時長
5
=A2.run(if(S<E[-1],S[-1],S):S).group@o(S;~.m(-1).E:E)
合並有重疊的時間段
這裡給了多種目標的處理方法,充分利用了跨行運算和有序分組的特點。SQL要實現這種運算簡單用窗口函數已經做不到了,需要用到很難理解的遞歸查詢。
位置訪問
對於有序的集合,有時我們需要直接用序號訪問成員。SQL延用了數學上的無序集合概念,要生成序號再用條件過濾才能訪問指定位置的成員,這對許多運算造成很大的麻煩。
集算器采用了有序集合機制,允許直接用序號訪問成員,這類運算要方便得多。
比如經濟統計中常用到的在眾多價格中找出中位數:
A
1
=db.query@i("select 價格 from T order by 價格")
2
=A1([(A1.len()+1)\2,A1.len()\2+1]).avg()
位置還可以用於分組。事件表結構為:序號、時刻、動作,動作有開始、結束兩種,現在要統計事件持續的總時長,即每一對開始和結束之間的時間之和。
A
1
=db.query@i("select 時刻 from 事件表 order by 時刻")
2
=A1.group((#-1)\2).sum(interval@s(~(1),~(2))
#表示記錄序號,group((#-1)\2)即將數據每兩個分成一組,然後針對每組計算時長再合計即可。
根據位置還能進行相鄰跨行引用。設有股價表結構為:交易日、收盤價;現列出計算出股價超過100元的交易日及當日漲幅。
A
1
=db.query("select * from 股價表 order by 交易日")
2
=A1.pselect@a(收盤價>100).select(~>1)
3
=A2.new(A1(~).交易日:交易日,A1(~).收盤價-A1(~-1).收盤價:漲幅)
pselect函數將返回滿足條件的成員位置,使用這些位置就可以方便地計算漲幅,而不必象使用窗口函數時事先計算出所有漲幅再過濾。
---恢復內容結束---