某些數據庫 SQL 缺失必要的能力,通常要編寫大段的代碼,才能間接實現類似的功能,有些情況甚至要改用存儲過程,連結構都變了。常見的比如:生成時間序列、保持分組子集、動態行列轉換、自然序號、相對位置、按序列和集合生成多條記錄、累積計算、條件分組、跨庫計算、集合計算、序列計算、自關聯結構、遞歸計算、對齊式關聯等。用下面幾個例子快速感受一下。
生成時間序列:某庫表的 Time 字段是時間,時間的間隔有時大于 1 分鐘。
Time | Value |
10:10:05 | 3 |
10:11:06 | 4 |
10:13:13 | 5 |
10:13:19 | 9 |
10:13:32 | 8 |
10:14:35 | 2 |
現在要將數據每分鐘分成一個窗口,補上缺失的窗口,對每個窗口統計 4 個值:前一個窗口的最后一條 start_value;本窗口的最后一條;本窗口的最小值;本窗口的最大值。第一分鐘的 start_value 用本窗口的第一條記錄;如果缺少某窗口的數據,則用前一個窗口的最后一條代替。
start | end | start_value | end_value | min | max |
10:10:00 | 10:11:00 | 3 | 3 | 3 | 3 |
10:11:00 | 10:12:00 | 3 | 4 | 4 | 4 |
10:12:00 | 10:13:00 | 4 | 4 | 4 | 4 |
10:13:00 | 10:14:00 | 4 | 8 | 5 | 9 |
10:14:00 | 10:15:00 | 8 | 2 | 2 | 2 |
很多數據庫的 SQL 沒有方便的方法生成月份序列,很多數據庫要用多層嵌套查詢 + 多個窗口函數才能間接實現。
保持分組子集:某表存儲多個賬號在多個日期發生的事件。
Row | Account Number | Date |
1 | 1001 | 2011-01-10 |
2 | 1001 | 2011-02-01 |
3 | 1001 | 2011-02-20 |
4 | 1001 | 2011-02-22 |
5 | 2001 | 2011-04-11 |
6 | 2001 | 2012-01-01 |
7 | 2001 | 2012-01-30 |
8 | 2001 | 2012-02-09 |
現在要找出每個賬號下符合條件的一對事件,分別是:日期最早的事件 a、距 a 事件 30 天以上的事件中日期最早的事件 b。
Row | Account Number | Date |
1 | 1001 | 2011-01-10 |
3 | 1001 | 2011-02-20 |
5 | 2001 | 2011-04-11 |
6 | 2001 | 2012-01-01 |
SQL 分組后必須立刻匯總,很難按條件 b 篩選記錄,只能用 join 語句配合多個 CTE 子句間接實現。
動態行列轉換:某庫表記錄了不同產品每個月的銷售額,其中產品的值未知。
product | month | amount |
AA | 1 | 100 |
AA | 1 | 150 |
AA | 2 | 200 |
AA | 2 | 120 |
BB | 2 | 180 |
BB | 2 | 220 |
CC | 3 | 80 |
現在要對產品、月份分組,對銷售額求和,再將產品由行轉列。
month | AA | BB | CC |
1 | 250 | ||
2 | 320 | 400 | |
3 | 80 |
某些數據庫的 SQL 缺失動態行列轉換能力,行轉列時必須寫出列名,很多數據庫只能改用存儲過程。
esProc SPL 內置豐富的計算類庫,可以補充這些數據庫 SQL 缺失的能力,比如上面 3 個例子:
"https://c.raqsoft.com.cn/article/1742353802112"
“@從 SQL 到 SPL:計算組內符合條件的一對最小值 - 乾學院”
“從 SQL 到 SPL:Create columns from distinct values of a column - 乾學院”
下面,我們就來嘗試一下如何將 esProc 集成到應用中。
先下載 esProc,推薦標準版:集算器 產品下載 | 報表加速器下載-半結構化計算軟件下載
下載并安裝相應的版本。
安完后,試一下 esProc IDE 是否可以正常訪問數據庫。先把數據庫的 JDBC Driver 放到目錄 "[安裝目錄]\common\jdbc",這是 esProc 的類路徑之一。比如 mySQL 的 JDBC:
打開 esProc IDE,找到菜單 "Tool->Connect to Data Source",新建 JDBC 數據源,填入具體數據庫的連接信息。下面是一個 mySQL 的數據源:
返回到數據源界面,試著連接數據源,跨庫運算時可以同時連接多個數據源。如果數據源名變成粉色,說明配置成功。
在 IDE 中新建腳本,寫 SPL 語句,連接 mysql 數據庫,加載第 1 個例子的數據:
=connect("mysql").query@x("select * from main")
按 ctrl-F9 執行,可以在 IDE 右邊看到執行結果,以數據表的形式呈現,這對調試 SPL 代碼很方便。
加載數據正常后,就可以寫正式的 SPL 代碼了,第 1 個例子:
A | |
1 | =connect("mysql").query@x("select * from main where time>? and time<=?",arg1,arg2) |
2 | =A1.run(Time=time@m(Time)) |
3 | =list=periods@s(A2.min(Time),A2.max(Time),60) |
4 | =A2.align@a(list,Time) |
5 | =A4.new(list(#):start, elapse@s(start,60):end, sv=ifn(end_value[-1],~.Value):start_value, ifn(~.m(-1).Value, sv):end_value, ifn(~.min(Value),sv):min, ifn(~.max(Value),sv):max) |
先用參數過濾;再將時間改為整分鐘數;生成連續分鐘的時間序列;將數據按時間序列對齊,每組數據對應一分鐘的窗口;按要求用每組數據生成一條新記錄。
把上面腳本保存在某個目錄中,比如 D:\data\procMain.splx,運行后可以看到結果:
第 2 個例子:
A | |
1 | =connect("mysql").query@x("select * from ventas") |
2 | =A1.group(#2) |
3 | =A2.conj(~1 | ~.select@1((#3 - A2.~1.#3)>30)) |
加載數據;按 2 個字段分組,但不匯總;取每組第 1 條,再篩選出距第 1 條 30 天以上的記錄,也取第 1 條;合并這 2 條記錄,最后合并各組的處理結果。
保存成 D:\data\proc2.splx,執行后看結果:
第 3 個例子:
=connect("mysql").query@x("select * from ventas").pivot@s(month;product,sum(amount))
加載數據后動態轉置,執行結果:
在 IDE 中調試無誤后,就可以把 esProc 集成到 Java 環境中了。
從目錄 "[安裝目錄]\esProc\lib" 下找到 esProc JDBC 相關的 jar 包:esproc-bin-xxxx.jar、icu4j_60.3.jar。
將這兩個 jar 包部署到 Java 開發環境的類路徑下。
再從目錄 "[安裝目錄]\esProc\config" 下找到 esProc 配置文件 raqsoftConfig.xml,同樣部署到 Java 開發環境的類路徑下。
配置文件中要改的配置項是 mainPath,這表示腳本等文件的默認路徑。注意數據源的信息也在配置文件中。
接下來,就可以編寫 Java 代碼,通過 esProc JDBC 調用 SPL 腳本了,例子 1:
Class.forName("com.esproc.jdbc.InternalDriver");
Connection con= DriverManager.getConnection("jdbc:esproc:local://");
PreparedStatement st = con.prepareCall("call procMain(?,?)");
st.setTime(1,Time.valueOf("10:00:00"));
st.setTime(2,Time.valueOf("11:00:00"));
ResultSet rs = st.executeQuery();
可以看到,調用 SPL 腳本的過程和調用存儲過程是一樣的。計算后結果如下:
腳本文件不是必須的,可以把 SPL 腳本轉為 SPL 代碼,像 SQL 那樣嵌入 Java。先在 IDE 中打開例子 2 的腳本文件,選中有代碼的單元格 A1-A3,再點擊菜單 "Edit->Copy->Code copy",這樣就把多行多列的網格代碼轉成了單行的 SPL 代碼,暫存在粘貼板里。
將轉換后的 SPL 代碼復制到 Java 代碼中
Connection con= DriverManager.getConnection("jdbc:esproc:local://");
PreparedStatement st = con.prepareStatement("==connect(\"mysql\").query@x(\"select * from ventas\")\n=A1.group(#2) \n=A2.conj(~1 | ~.select@1((#3 - A2.~1.#3)>30))");
ResultSet rs = st.executeQuery();
可以看到,Java調用SPL代碼的過程和調用SQL代碼一樣。運行后可以看到結果:
可以看到,Java調用SPL代碼的過程和調用SQL代碼一樣。運行后可以看到結果:
有些 SPL 代碼比較簡單,沒必要用 esProc IDE 編寫調試,那就可以直接寫在 Java 里。比如例子 3,直接在 Java 中嵌入 SPL 代碼:
Connection con= DriverManager.getConnection("jdbc:esproc:local://");
PreparedStatement st = con.prepareStatement("=connect(\"mysql\").query@x(\"select * from ventas\").pivot@s(month;product,sum(amount))");
ResultSet rs = st.executeQuery();
執行結果像下面這樣:
乾學院上還有很多補充 SQL 缺失能力的例子,開發遇到問題時可以去找找解決辦法。