【摘要】
用實例、分步驟,詳細講解多維分析(OLAP)的實現。點擊瞭解多維分析後臺實踐 2:數據類型優化
實踐目標
本期目標是練習將數據庫讀出的數據,儘可能轉換爲有利於性能優化的數據類型,例如:小整數和浮點數。
實踐的步驟:
1、 準備基礎寬表:修改上期的代碼,完成數據類型優化存爲組表文件。
2、 訪問基礎寬表:修改上期的代碼,在傳入參數保持不變的前提下,查詢數據轉換之後的組表文件,結果集也要返回原有的數據顯示值。對於這個要求,SQL 是無法實現傳入參數和結果集的轉換的,所以訪問寬表的代碼以 SPL 爲例。
本期樣例寬表不變,依然爲 customer 表。從 Oracle 數據庫中取出寬表數據的 SQL 語句是 select * from customer。執行結果如下圖:
其中字段包括:
CUSTOMER_ID NUMBER(10,0), 客戶編號
FIRST_NAME VARCHAR2(20), 名
LAST_NAME VARCHAR2(25), 姓
PHONE_NUMBER VARCHAR2(20), 電話號碼
BEGIN_DATE DATE, 開戶日期
JOB_ID VARCHAR2(10), 職業編號
JOB_TITLE VARCHAR2(32), 職業名稱
BALANCE NUMBER(8,2), 餘額
EMPLOYEE_ID NUMBER(4,0), 開戶僱員編號
DEPARTMENT_ID NUMBER(4,0), 分支機構編號
DEPARTMENT_NAME VARCHAR2(32), 分支結構名稱
FLAG1 CHAR(1), 標記 1
FLAG2 CHAR(1), 標記 2
FLAG3 CHAR(1), 標記 3
FLAG4 CHAR(1), 標記 4
FLAG5 CHAR(1), 標記 5
FLAG6 CHAR(1), 標記 6
FLAG7 CHAR(1), 標記 7
FLAG8 CHAR(1), 標記 8
多維分析計算的目標也不變,用下面 Oracle 的 SQL 語句表示:
select department_id,job_id,to_char(begin_date,'yyyymm') begin_month ,sum(balance) sum,count(customer_id) count
from customer
where department_id in (10,20,50,60,70,80)
and job_id in ('AD_VP','FI_MGR','AC_MGR','SA_MAN','SA_REP')
and begin_date>=to_date('2002-01-01','yyyy-mm-dd')
and begin_date<=to_date('2020-12-31','yyyy-mm-dd')
and flag1='1' and flag8='1'
group by department_id,job_id,to_char(begin_date,'yyyymm')
準備寬表
一、數值整數化
在 customer 表中有些字段本身就是整數,比如:CUSTOMER_ID、EMPLOYEE_ID、DEPARTMENT_ID。
處理方法:
l 如果從數據庫中導出的是整型,就可以直接存儲到組表中。
l 如果從數據庫中導出的不是整型,要用類型轉換函數強制轉換爲整型。
l 要注意儘量讓整數值小於 65536,這樣性能最好。如果原字段值被人爲的轉換成較大的整數,例如:所有的數值都加上了一個 100000,變成 100001、100002…,就要去掉前面的 1。
二、字符串整數化
FLAG1 到 FLAG8 是字符串,但是存儲的依然是整型數據,可以用類型轉換函數轉爲整型。
JOB_ID 字段也是字符串,取值是 jobs 維表的主鍵,屬於枚舉類型。我們可以用 jobs 表中的序號代替 JOB_ID 字段,實現整數化。
jobs 表結構和樣例數據如下:
處理方法:
l 取出 jobs 中的 JOB_ID,排好序後構成一個序列 job。customer 寬表中增加 JOB_NUM 字段存儲 JOB_ID 在序列 job 中的序號。
三、日期整數化
大多數情況下,日期型數據只是用來比較,並不需要計算間隔,所以也可以用小整數來存儲。在多維分析計算中,按照年、月來計算的情況比較常見。小整數化之後的日期,要求能很方便的把年、月拆分出來。
處理方法:
l 我們可以計算出 BEGIN_DATE 字段值與一個日期起點的間隔月數,乘以 100 後加上 BEGIN_DATE 的日值,來代替日期型數據存入組表。起點日期根據日期數據的特徵來確定,值越大越好。
例如:我們發現所有的 BEGIN_DATE 都在 2000 年之後,則可以確定日期起點爲 2000-01-01。
確定日期起點後,就可以轉化 customer 寬表中的 BEGIN_DATE 字段值了。例如:BEGIN_DATE 爲 2010-11-20,先計算出和 2000-01-01 相差的整月數是 130,乘以 100 後加上日值 20 即可得到小整數 13020。
以 2000-01-01 爲日期起點,BEGIN_DATE 小於 2050 年時,整數化之後的值都小於 65536。可以看到,在業務數據允許的前提下,日期起點儘量晚,可以更大程度避免出現寬表中的日期超出小整數範圍的情況。
四、無法整數化的情況
必須用字符串表示的字段,如 FIRST_NAME、JOB_TITLE 等;
必須用浮點數表示的字段,如金額、折扣率等有小數部分的字段;
必須用字符串加整數一起表示的字段,如國際電話號碼等。
處理方法:
l 保持字段原值不動。
根據以上要求,改寫 etl.dfx,從數據庫中取出數據,類型轉化後,生成組表文件,存儲基礎寬表。代碼示例如下:
A1:連接預先配置好的數據庫 oracle,@l 是指取出字段名爲小寫。注意這裏是小寫字母L。
B1:建立數據庫遊標,準備取出 customer 表的數據。customer 是事實表,實際應用中一般都比較大,所以用遊標方式,避免內存溢出。遊標的 @d 選項是將 oracle 的 numeric 型數據轉換成 double 型數據,而非 decimal 型數據。decimal 型數據在 java 中的性能較差。
A2:從數據庫中讀 jobs 表,只讀取 JOB_ID 字段並排序。jobs 是維表,一般都比較小,所以直接讀入到內存中。
B2:將 A2 的數據存儲成集文件,待後面使用。
A3:將 A2 轉化爲序列。
B3:定義日期 2000-01-01。
A4:用 new 函數定義三種計算。
1、 CUSTOMER_ID 等確定是整數的數值,從 double 或者 string 轉換爲 int。方法是直接用 int 函數做類型轉換。注意 int 不能大於 2147483647,對於數據量超過這個數值的事實表,序號主鍵要用 long 型。
2、 將 JOB_ID 從字符串轉化爲整數,提高計算性能。方法是用 pos 函數找到 job_id 在 A3 中的序號,定義爲 JOB_NUM 字段。
3、 用 interval 計算 begin_date 和 2000-01-01 之間相差的整月數,乘以 100 加上 begin_date 的日值,用 int 轉換爲整數存儲爲新的 begin_date。
A5:定義列存組表文件。字段名和 A4 完全一致。
A6:邊計算遊標 A4,邊輸出到組表文件中。
B6:關閉組表文件和數據庫連接。
數據量爲一千萬,導出組表文件約 344MB。和第一期未做數據類型優化的文件比較如下:
從上表可以看出,完成數據類型優化之後,文件大小減少了 12%(49M)。文件變小,能減少磁盤讀取數據量,有效提高性能。
訪問寬表
如上所述,後臺組表的很多字段已經優化轉換,沒有辦法用原來的 SQL 進行查詢了。我們採用執行腳本的方式,提交過濾條件、分組字段等參數,後臺將參數值轉換成優化後的數據類型,再對組表進行計算。這樣做,可以保證通用多維分析前端傳入的參數保持不變。最後,計算結果也需要轉換爲對應的顯示值。
例如:傳入的參數 flag1='1',需要轉換爲 flag1=1;計算結果中的 job_num 和 begin_date,還要從整數轉換爲字符串 job_id 和日期。
爲了實現這個計算,要先在節點服務器主目錄中編寫 init.dfx 文件,預先加載全局變量 job,用於後續的轉換計算。
init.dfx 代碼如下:
A1:取出集文件中的數據,@i 表示只有一列時讀成序列。
B1:存入全局變量 job。
寫好的 init.dfx 要放入節點機主目錄,啓動或重啓節點機時會被自動調用。
按照數據類型優化要求改寫 olap-spl.dfx,用 SPL 代碼訪問寬表並進行過濾和分組彙總計算。
定義網格參數,將文件名、部門編號、工作編號、標誌位、日期範圍、分組字段、聚合表達式分別傳入。
參數設置窗口如下,和第一期完全一致:
參數值樣例:
filename="data/customer.ctx"
arg_department_id ="10,20,50,60,70,80"
arg_job_id="AD_VP,FI_MGR,AC_MGR,SA_MAN,SA_REP"
arg_begin_date_min = "2002-01-01"
arg_begin_date_max ="2020-12-31"
arg_flag ="flag1==\"1\"&& flag8==\"1\" "
group="department_id,job_id,begin_yearmonth"
aggregate="sum(balance):sum,count(customer_id):count"
說明:group 中如果是 begin_date 則按照日期分組,如果是 begin_yearmonth 則按照年月分組。對於多維分析前端來說,可以認爲有兩個字段。
SPL 代碼示例如下:
A1:打開組表對象。B1:定義起點日期 2000-01-01 用於參數和結果中的日期值轉換。
A2、B2:將傳入日期參數按照前面介紹的方法轉化爲整數。
A3:將傳入的逗號分隔字符串 job_id 轉換爲在全局變量 job 序列中的位置,也就是 job_num 整數序列。
B3:將傳入的逗號分隔字符串 department_id 用 int 函數轉換爲整數序列。
A4:將傳入的 flag 條件中的雙引號去掉,變爲整數條件。
B4:將傳入的分組字段中的 job_id 替換爲 job_num。
A5:將傳入的分組字段中的 begin_yearmonth,替換爲 begin_date\100。begin_date 的字段值除以 100 取整,就是實際日期和起點日期相差的月數。
A6:定義帶過濾條件的遊標。
A7:對遊標計算小結果集分組彙總。
A8:將 A7 結果的字段名形成序列。
B8: 字段名中如果有 job_num,就替換成轉換語句。語句的作用是:將分組結果中的 job_num 轉換爲 job_id。
A9:字段名中如果有 begin_yearmonth,替換爲轉換語句,作用是:將分組字段中的月差值 begin_yearmonth 從整數轉化爲 yyyymm。
A10:字段名中如果有 begin_date,替換爲轉換語句,作用是:將分組字段中的整數化日期值轉換爲日期型。
A11:將替換之後的 A8 重新用逗號連接成字符串,並對 A7 循環計算,完成字段類型和顯示值的轉換。
A12:返回 A11 結果集。
執行結果如下圖:
olap-spl.dfx 編寫好之後,可以在多維分析中作爲存儲過程調用,Java 代碼和第一期相同。如下:
public void testOlapServer(){
Connection con = null;
java.sql.PreparedStatement st;
try{
// 建立連接
Class.forName("com.esproc.jdbc.InternalDriver");
// 根據 url 獲取連接
con= DriverManager.getConnection("jdbc:esproc:local://?onlyServer=true&sqlfirst=plus");
// 調用存儲過程,其中 olap-spl 是 dfx 的文件名
st =con.prepareCall("call olap-spl(?,?,?,?,?,?,?,?)");
st.setObject(1, "data/customer.ctx");//arg_filename
st.setObject(2, "10,20,50,60,70,80");//arg_department_id
st.setObject(3, "AD_VP,FI_MGR,AC_MGR,SA_MAN,SA_REP");//arg_job_id
st.setObject(4, "2002-01-01");//arg_begin_date_min
st.setObject(5, "2020-12-31");//arg_begin_date_max
st.setObject(6, "flag1==\"1\"&& flag8==\"1\" ");//arg_flag
st.setObject(7, "department_id,job_id,begin_yearmonth");//arg_group
st.setObject(8, "sum(balance):sum,count(customer_id):count");//arg_aggregate
// 執行存儲過程
st.execute();
// 獲取結果集
ResultSet rs = st.getResultSet();
// 繼續處理結果集,將結果集展現出來
}
catch(Exception e){
out.println(e);
}
finally{
// 關閉連接
if (con!=null) {
try {con.close();}
catch(Exception e) {out.println(e); }
}
}
}
Java 代碼加上後臺計算返回結果總的執行時間,和第一期比較如下:
如上期所述,表中的執行時間硬件配置相關,其絕對數值並不重要。重要的是,通過上表的對比可以看出,數據類型優化有效提高了計算性能。