1. 程式人生 > >如何優化多資料集關聯報表

如何優化多資料集關聯報表

多資料集關聯報表是很常見的報表形式,它允許開發者分別從不同的來源(表或資料庫)分別準備資料形成不同的資料集,在報表端(模板)通過表示式描述資料集間的關係完成關聯。這樣可以避免在資料準備時寫過於複雜的 SQL/ 儲存過程,降低維護難度。尤其當報表資料來源於多個數據庫時,多資料集的優勢更加明顯。

凡事都有兩面性,多資料集為開發帶來方便的同時卻對效能造成了極大的影響。在報表端進行多資料集關聯時要計算關聯表示式(舉例:ds2.select(name,,id==A1))時,報表引擎一般會採用順序遍歷的方式進行,先拿一個數據集的第一條記錄去第二個資料集中遍歷查詢符合條件的記錄,然後是第二條,第三條…。因此兩個資料集關聯的時間複雜度是 O(n²),資料量不大時感受還不明顯,資料量稍大一些就會很慢,隨著資料集數量的增多報表效能也會呈指數下降。

因此在實際報表業務中,當多資料集關聯導致報表效能降低時可以考慮將多個數據集 SQL 合併成一句,利用資料庫的關聯計算能力提升效能。但這種方式又會導致 SQL 過於複雜,很難維護,而太複雜的 SQL 很可能被資料庫搞錯優化路徑,結果效能仍不可控。並且合併 SQL 的方式有適用場景的限制(如無法完成跨異構庫關聯、文字關聯等)。

下面介紹採用集算器的優化方法,寫法簡單且效能高,能夠普遍適用於各種場景:

  1. 單資料庫,多個數據集 SQL 比較複雜,很難寫成一句

  2. 單資料庫,多資料集中使用了儲存過程,無法整合成一句 SQL

  3. 單資料庫,多資料集合併成一句 SQL 後效能仍不如人意

  4. 多資料庫,多資料集來源多個數據庫,無法通過一句 SQL 進行查詢

  5. 涉及檔案資料,多資料集中部分資料來自檔案,無法使用 SQL 進行統一查詢

不同於 SQL(關係代數)採用笛卡爾積再過濾的方式看待 JOIN,基於離散資料集模型的集算器將關聯運算做了區分(只考慮等值 JOIN):多對一的主外來鍵表採用外來鍵屬性化方式關聯、一對一的同維表採用同維表等同化方式關聯、一對多的主子表採用主子表一體化關聯,針對不同的表間關係採用不同演算法進行運算,可以獲得更簡單的寫法和更高的效能以及更廣泛的適用範圍。

我們將通過一些示例來說明面向各種情況時,如何使用集算器獲得最優的實現和效率。需要說明的是,為了描述方便我們使用抽象後最簡單的情況說明各種關聯運算,實際業務會複雜得多,每個資料集 SQL 也會複雜得多,但是不管怎樣多資料集關聯關係也逃不出多對一、一對一和一對多的情況,所以拿原子操作來說明問題,以期大家遇到問題時可以採用最合適的方式處理。

報表整合

這裡假定讀者已經瞭解集算器與報表的關係,集算器僅為報表提供資料準備,將原來的多資料集通過集算器完成關聯計算,將計算以結果以單 / 多資料集的方式提供給報表進行呈現。

集算器指令碼可以直接被潤乾報表 5.0 及以上版本直接引用(集算器資料集);如果是其他報表工具,集算器提供了標準 JDBC 和 ODBC 介面,可以採用類似呼叫儲存過程的方式呼叫集算器指令碼,詳細可以參考教程《應用整合 - 被 JAVA 呼叫》章節,以及《集算器與 BIRT 整合》或《集算器與 JasperReport 整合》。

因此下面大部分例子將省略報表製作部分,主要說明集算器處理多資料集關聯計算的過程。

外來鍵表(多對一)

表 A 的某些欄位與表 B 的主鍵關聯。A 表稱為事實表,B 表稱為維表。A 表中與 B 表主鍵關聯的欄位稱為 A 指向 B 的外來鍵,B 也稱為 A 的外來鍵表。外來鍵表是多對一的關係,且只有 JOIN 和 LEFT JOIN,一般不會用到 FULL JOIN。如:訂單表和客戶表

Orders 表和 Customer 表的主鍵都是其中的 id 欄位,Orders 表的 customerID 欄位是指向 Customer 表的外來鍵。

這裡說的主鍵是指邏輯上的主鍵(下同),也就是在表中取值唯一的欄位(組),一個表上可能有多個欄位(組)都取值唯一(並不常見),可以認為都是主鍵。不是一定是在物理表上建立的那個主鍵。

單外來鍵舉例

報表中有兩個資料集,資料分別來自訂單資訊表(Orders)和客戶表(Customer)(實際業務中可能是兩條複雜 SQL),訂單表的客戶 ID 指向客戶表的主鍵客戶 ID,屬於典型的主外來鍵關係。

【計算目標】 查詢某時間段內訂單和客戶詳單

集算器資料準備

單庫情況

當兩個資料集來源於單個數據庫,資料集 SQL 比較複雜不易合併時,通過集算器實現多對一關聯計算,指令碼如下:


A B
1 =connect(“db”) / 建立資料庫連線
2 =A1.query(“select * from 訂單 where 訂購日期 >=? and 訂購日期 <=?”,begin,end) / 查詢訂單資料
3 [email protected](“select * from 客戶”) / 查詢客戶資料
4 >A2.switch(客戶 ID,A3: 客戶 ID) / 關聯,在 A2 訂單表客戶 ID 欄位上建立指向客戶表的指標
5 =A2.new(客戶 ID. 公司名稱: 客戶名稱, 訂單 ID, 訂購日期, 運貨費, 訂單金額) / 通過外來鍵屬性化的方式,將外來鍵表字段作為客戶 ID 屬性使用

指令碼解析:

1、前 3 行連線資料庫後分別取訂單和客戶資料作為兩個獨立資料集(事實上 A2 和 A3 的 SQL 可以任意複雜,取數階段無需將兩條 SQL 合併,分別查詢即可);這裡為了說明指標與記錄,將兩個表所有欄位都選出,實際業務中應該用哪些欄位取哪些。

2、A2 中使用了指令碼引數 begin 和 end 來接收起止時間範圍

3、注意 A3 的 query 函式使用了 @x 選項,代表查詢後關閉連線,使用完資料庫連線一定要及時關閉(也可以通過 Aclose() 顯示關閉資料庫連線)

4、A4 中通過 switch 函式在 A2 訂單表的客戶 ID 欄位上建立指向客戶表記錄的指標實現關聯

5、A5 利用建立關聯關係通過“外來鍵欄位. 維表字段”的方式進行引用,如“客戶 ID: 客戶名稱”,將維表記錄看做外來鍵的的屬性,這便是外來鍵屬性化的由來;

6、A5 為報表返回關聯後結果集

關於 switch 函式

在 SQL 的概念體系中並不區分外來鍵表和主子表,多對一和一對多從 SQL 的觀點看來只是關聯方向不同,本質上是一回事。比如,訂單也可以理解成訂單明細的外來鍵表。但是,集算器把它們區分開,在簡化語法和效能優化時使用不同的手段。

switch 是集算器中實現多對一關聯的函式,通過建立事實表和維表之間的外來鍵指標實現連線。其原理是通過 HASH 演算法在外來鍵欄位上建立指向維表記錄的指標,這樣在建立關聯的時間與資料庫中最快的關聯方式 HASH JOIN 一樣,但接下來使用連線結果時就不需要再查詢 HASH TABLE,直接通過指標定位到記憶體中的維表記錄。

建立外來鍵指標後外來鍵欄位的原值不再儲存,而被轉化為指向維表記錄的指標,所有維表字段都可以通過“外來鍵欄位. 維表字段”方式引用,因此 switch 函式只適合做單外來鍵的關聯(原外來鍵欄位值變了),多外來鍵關聯時需要使用 A.join 函式(後面會說明多外來鍵情況)。

指標式連線的意義在於一次建立多次使用,重複使用時由於無需再建立連線效能高效得多。如上述例子中,除了獲取訂單和客戶詳單,還想針對客戶所在區域彙總訂單數量,那麼可以寫成這樣(B5 格):


A B
1 =connect(“db”)
2 =A1.query(“select * from 訂單 where 訂購日期 >=begin and 訂購日期 <=end”)
3 [email protected](“select * from 客戶”)
4 >A2.switch(客戶 ID,A3: 客戶 ID)
5 =A2.new(客戶 ID. 公司名稱: 客戶名稱, 訂單 ID, 訂購日期, 運貨費, 訂單金額) =A2.groups(客戶 ID. 所在區域;count(訂單 ID):num)

B5 的計算繼續使用了在 A2 客戶 ID 欄位上建立的指標,而無需重新建立關聯。實際應用中,指標式關聯建立後,重複使用次數越多效能優勢越明顯。

在報表中複用連線,計算不同的結果集多用於分片報表,分片報表在報表業務中並非很常見,但也不算罕見,不過對應業務都比較複雜,不大合適舉例,這裡就不細說了。當遇到報表分片且有相同關聯情況時可以考慮使用集算器進行連線複用。

多庫情況

前面提到多庫尤其是異構多庫情況下無法利用 SQL 做關聯計算,在報表中計算效能又低,這時非常適合使用集算器來做。下面假設訂單和客戶表分別來源兩個不同資料庫 db1 和 db2,計算目標仍然是:查詢某時間段內訂單和客戶詳單,來看集算器的具體寫法。


A B
1 =connect(“db1”) =connect(“db2”)
2 [email protected](“select * from 訂單 where 訂購日期 >=? and 訂購日期 <=?”,begin,end)
3 [email protected](“select * from 客戶”)
4 >A2.switch(客戶 ID,A3: 客戶 ID)
5 =A2.new(客戶 ID. 公司名稱: 客戶名稱, 訂單 ID, 訂購日期, 運貨費, 訂單金額)

注意到和單庫情況的區別了嗎?

多庫情況只需要在指令碼中建立多庫的連線(A1 和 B1)分別執行 SQL 查詢(A2 和 A3),剩下的運算和單庫完全一致,輕鬆實現基於多庫的關聯計算。

事實上,集算器(指令碼)還非常利於應用移植和資料庫擴充套件,當底層資料庫發生變化或者由單庫拆分成多庫時,只需更改資料庫連線,主要的計算邏輯完全不用改。更進一步,如果連線資訊也維護在配置中,則可以寫出更加通用的指令碼做到系統擴充套件時指令碼無縫移植。

涉及文字

集算器作為開放計算引擎提供了多資料來源支援,除了關係資料庫外,本地檔案(Excel、TXT、CSV、JSON/XML)、NoSQL、Hadoop 等也可以直接作為資料來源參與運算。因此如果報表中有資料來源於文字、Excel 等檔案,可以通過集算器直接處理(SQL 就無能為力了)。

沿用上面的例子,假設客戶資訊來源於 TXT,計算目標仍然是:查詢某時間段內訂單和客戶詳單。來看集算器的寫法。


A B
1 =connect(“db1”)
2 [email protected](“select * from 訂單 where 訂購日期 >=? and 訂購日期 <=?”,begin,end)
3 =file(“/usr/ 客戶.txt”)[email protected]() / 讀入檔案資料
4 >A2.switch(客戶 ID,A3: 客戶 ID)
5 =A2.new(客戶 ID. 公司名稱, 訂單 ID, 訂購日期, 運貨費, 訂單金額)

涉及到文字有什麼變化嗎?只將 A3 改為讀取檔案資料即可,核心計算邏輯仍然沒有變化。

上面我們通過多對一的兩個表對單庫、多庫和檔案三種情況進行說明,報表遇到相應問題可以使用集算器處理。實際業務中還可能涉及多層外來鍵情況,即多表外來鍵關聯。

多層外來鍵關聯舉例

報表中有三個資料集,資料分別來自訂單資訊表(Orders)、客戶表(Customer)和地區表(Area),訂單表的客戶 ID 指向客戶表的主鍵客戶 ID,客戶表的所在區域指向區域表的主鍵區域 ID。

【計算目標】 查詢某時間段內訂單及其客戶與所在區域詳細資訊

集算器資料準備


A B
1 =connect(“db”)
2 =A1.query(“select * from 訂單 where 訂購日期 >=? and 訂購日期 <=?”,begin,end)
3 =A1.query(“select 客戶 ID, 公司名稱 from 客戶”) [email protected](“select 區域 ID, 區域名稱 from 地區”)
4 >A3.switch(所在區域,B3: 區域 ID) >A2.switch(客戶 ID,A3: 客戶 ID)
5 =A2.new(客戶 ID. 所在區域. 區域名稱: 區域, 客戶 ID. 公司名稱: 客戶, 訂單 ID, 訂購日期, 運貨費, 訂單金額)

指令碼解析:

1、A2-B4 分別查詢訂單、客戶和地區資料

2、A4 中通過 switch 函式在 A3 所在區域上建立指向地區記錄的指標實現關聯

3、同理,B4 在 A2 訂單表的客戶 ID 欄位上建立指向客戶表記錄的指標實現關聯,這裡得到了一個三層結果的集合

4、A5 通過外來鍵屬性化的方式引用區域和客戶資訊,可以看到無論有多少層外來鍵都可以通 過 [點](.)的方式作為外來鍵屬性引用

在實際業務中很常見的星型結構還會涉及到同一個事實表和多個維表進行關聯,不同於傳統的 HASH 分段 JOIN 方案,集算器無需兩兩消除、多次遍歷,通過遍歷一次事實表即可完成與多個維表的關聯,非常高效,適合多資料庫表關聯效能低下需要改善的場景。

下面以一個事實表與兩個維表關聯說明多維表情況下集算器處理方式。

關聯多個維表舉例

訂單資訊表(Orders)與客戶表(Customer)、僱員表(Employee),訂單表的客戶 ID 指向客戶表的主鍵客戶 ID;銷售 ID 指向僱員表的員工 ID

【計算目標】 按客戶所在區域和銷售人員彙總訂單金額

集算器資料準備

集算器實現指令碼:


A B
1 =connect(“db”)
2 =A1.query(“select * from 訂單”)
3 =A1.query(“select * from 客戶”)
4 [email protected](“select * from 僱員”)
5 >A2.switch(客戶 ID,A3: 客戶 ID; 僱員 ID,A4: 僱員 ID) / 同時關聯兩個維表
6 =A2.groups(客戶 ID. 地區: 地區, 僱員 ID. 姓名: 姓名;sum( 訂單金額):amount) / 外來鍵屬性化方式訪問維表字段,彙總指標

在 A5 中通過 switch 將訂單資訊同時與客戶表和僱員表關聯,客戶 ID 和僱員 ID 分別指向對應維表的記錄

這裡可以看到,通過遍歷一次訂單表就關聯了客戶和僱員,當外來鍵關聯較多時使用 switch 更加簡單高效。相反,在寫 SQL 關聯多個表時,偶爾會出現漏寫 join 條件導致資料庫被跑死的情況,而集算器則完全避免了這種情況。

多外來鍵情況舉例

單外來鍵下無論是資料來源資料庫或是檔案均可使用 switch 進行處理,實際業務中還可能存在多外來鍵的情況。報表中有兩個資料集分別來自學生表(Students)和班級表(Classes),學生表的專業號和班級號為外來鍵欄位,分別指向班級表的聯合主鍵(專業號,班級號)。

【計算目標】 查詢所有學生的學號,姓名,專業,班級,班主任

集算器資料準備


A B
1 =connect(“db”)
2 =A1.query(“select * from 學生”)
3 [email protected](“select * from 班級”).keys(專業號, 班級號)
4 =A2.join(專業號: 班級號,A3, 班主任) / 雙主鍵關聯

指令碼解析:

1、A3 查詢班級資料,並通過 keys 設定班級的主鍵為專業號和班級號;

2、A4 使用 A.join() 函式進行雙主鍵關聯,將班主任資訊新增到學生資訊中,形成目標結果集

與 switch 處理單外來鍵關聯不同,當出現多外來鍵的情況下需要使用 A.join 完成關聯。

同維表(一對一)

表 A 的主鍵與表 B 的主鍵關聯,A 和 B 互稱為同維表。同維表是一對一的關係,JOIN、LEFT JOIN 和 FULL JOIN 的情況都會有,如:員工表和經理表。

兩個表的主鍵都是 id,經理也是員工,兩表共用同樣的員工編號,經理會比普通員多一些屬性,另用一個經理表來儲存。

單主鍵舉例

報表中有三個資料集,分別來自回款表(OrderPayment)、客戶表(Customer)和訂單表(Orders),回款表的客戶 ID 指向客戶表主鍵客戶 ID,訂單表的客戶 ID 指向客戶表主鍵客戶 ID。

【計算目標】 按客戶(所有)檢視某時間段訂單總額和回款總額

這是很常見的一類報表,按照某個維度(如地區、日期、人員)彙總多個指標(如訂單額、回款額),但我們發現報表的三個資料集之間並不是像銷售表和員工表(主鍵都是人員 ID)那樣互為同維表,不過結合計算目標分析一下,由於一個客戶會有多筆訂單和回款記錄,因此需要對兩個表分別按照客戶 ID 分組後(結果集以客戶 ID 為主鍵)向客戶表主鍵客戶 ID 對齊,顯然三個集合是一組以客戶 ID 為主鍵的同維表。

集算器資料準備


A B
1 =connect(“db”)
2 =A1.query(“select 客戶 ID,sum( 訂單金額) 訂單金額 from 訂單 where 訂購日期 >=? and 訂購日期 <? group by 客戶 ID”)
3 =A1.query(“select 客戶 ID,sum( 回款金額) 回款金額 from 回款 where 回款日期 >=begin and 回款日期 <=end group by 客戶 ID “)
4 [email protected](“select 客戶 ID, 公司名稱 from 客戶”)
5 [email protected](A4: 客戶, 客戶 ID;A2: 訂單, 客戶 ID;A3: 回款, 客戶 ID)
6 =A5.new(客戶. 公司名稱: 客戶名稱, 訂單. 訂單金額: 訂單金額, 回款. 回款金額: 回款金額)

指令碼解析:

1、A2 和 A3 針對訂單和回款資料分別按照客戶 ID 進行分組彙總;

2、A5 按照客戶表左關聯(@1 選項代表左連線)訂單和回款資料

3、A6 獲得關聯結果返回報表資料集

這裡關注一下 join 函式(上述例子 A5=[email protected](A4: 客戶, 客戶 ID; A2: 訂單, 客戶 ID;A3: 回款, 客戶 ID)),可以看到 join 的各個表之間看起來似乎是無關的,在集算器中關聯時無需關注表間關係,只需要同時向某一個維度(如客戶維度)對齊即可,這樣在關聯表增多或減少時修改非常方便。如果是 SQL 的寫法必須指定兩個表的關聯條件,關聯的表數量太多時就容易漏寫一兩個條件導致出現叉乘算錯的情況,如果漏寫條件的表比較大,還容易把資料庫跑死;集算器的 join 則避免了這種情況。

另外,從上述例子來看當涉及多個事實表同時向維表對齊彙總時,一定要先 group 再 join,如果先 join 再 group 就會算錯,寫成 SQL 應該是維表和有兩個 group by 的子查詢 join。

多主鍵情況舉例

與多外來鍵情況類似,當同維表採用聯合主鍵時就會存在多主鍵同維表關聯的情況。報表中有兩個資料集,分別來自回款表(OrderPayment)和訂單表(Orders),兩個表沒有關聯關係。

【計算目標】 按客戶和年份彙總回款金額和訂單金額

這兩個表直接並沒有關聯關係,但經過同樣兩個維度分組彙總後,就形成了兩個以客戶和日期為主鍵的同維表

按照計算目標,要同時獲得回款金額和訂單金額,需要將兩個表進行關聯計算。

集算器資料準備


A B
1 =connect(“db”)
2 =A1.query(“select 客戶 ID,year( 訂購日期) 年份,sum(訂單金額) 訂單金額 from 訂單 where 訂購日期 >=? and 訂購日期 <=? group by 客戶 ID, 年份”,begin,end)
3 =A1.query(“select 客戶 ID,year( 回款日期) 年份,sum(回款金額) 回款金額 from 回款 where 回款日期 >=? and 回款日期 <=? group by 客戶 ID, 年份 “,begin,end)
4 [email protected](B2: 訂單, 客戶 ID, 年份;B3: 回款, 客戶 ID, 年份)
5 =A4.new(訂單. 客戶 ID: 客戶, 訂單. 年份: 年份, 訂單. 訂單金額: 訂單金額, 回款. 回款金額: 回款金額)

指令碼解析:

1、A2 和 A3 分別查詢訂單和回款資料,並按客戶和年份彙總訂單額和回款額;

2、A4 通過全連線對齊帶有兩個主鍵(客戶 ID, 年份)的結果集

3、A5 根據關聯結果返回報表資料集

同維表與外來鍵表混合

在實際業務中還經常能見到同維表和外來鍵表混合使用的情況,集算器處理起來仍然簡單高效。

舉例

沿用上述單主鍵同維表的例子,現在還有一張地區表(Area),客戶表外來鍵欄位所在區域指向區域表主鍵區域 ID。

【計算目標】 按客戶所在區域和客戶檢視某時間段訂單總額和回款總額

分析後仍然得到下面的同維表,只不過客戶表外來鍵欄位所在區域又指向了地區表,出現了同維表和外來鍵表混合的情況。

集算器資料準備


A B
1 =connect(“db”)
2 =A1.query(“select 客戶 ID,sum( 訂單金額) 訂單金額 from 訂單 where 訂購日期 >=? and 訂購日期 <=? group by 客戶 ID”,begin,end)
3 =A1.query(“select 客戶 ID,sum( 回款金額) 回款金額 from 回款 where 回款日期 >=?  and 回款日期 <=? group by 客戶 ID “,begin,end)
4 =A1.query(“select 客戶 ID, 公司名稱, 所在區域 from 客戶”)
5 [email protected](“select 區域 ID, 區域名稱 from 區域”)
6 >A4.switch(所在區域,A5: 區域 ID)
7 [email protected](A4: 客戶, 客戶 ID;A2: 訂單, 客戶 ID;A3: 回款, 客戶 ID)
8 =A7.new(客戶. 所在區域. 區域名稱: 區域, 客戶. 公司名稱: 客戶名稱, 訂單. 訂單金額: 訂單金額, 回款. 回款金額: 回款金額)

指令碼解析:

1、A6 在 A4 客戶資訊中建立外來鍵關聯

2、A7 關聯後結果可以看到集算器的結果集可以是任意多層結構

3、A8 通過外來鍵屬性化方式引用區域名稱,為報表返回結果集

主子表(一對多)

表 A 的主鍵與表 B 的部分主鍵關聯,A 稱為主表,B 稱為子表。主子表是一對多的關係,只有 JOIN 和 LEFT JOIN,不會有 FULL JOIN,如:訂單和訂單明細。

Orders 表的主鍵是 id,OrderDetail 表中的主鍵是 (id,no),前者的主鍵是後者的一部分,訂單表是主表,訂單明細表子表。從子表去看主表,與前述提到的外來鍵表非常類似,只是外來鍵表不要求外來鍵欄位是主鍵,因此從子表角度觀察表間關係可以將主子表看做外來鍵表的特殊情況,所以有時也可以採用外來鍵表處理關聯的方法(switch)。

主子表關聯計算在報表中並不常見,即使有,多數情況下可以轉換成將主表作為外來鍵表關聯,或者子表 group 後變成同維表處理。當主表作為外來鍵表處理時,除了可以用到外來鍵表(多對一)switch 的處理方式,還可以通過 join 實現。除了 join 可以關聯多外來鍵情況,當關聯的兩個結果集按照關聯欄位有序時還可以使用歸併演算法,效能比 switch 更高(資料量不大時優勢並不明顯)。

子表關聯主表將主表作為外來鍵表可參考前述外來鍵表中 < 單外來鍵舉例 >,子表 group 後變成同維表關聯的例子可參考前述同維表中 < 單主鍵舉例 >。

下面介紹一種有序歸併實施關聯計算的方法,讀者在對主子表(包括同維表)進行關聯計算時也可選用,以獲得更高效能。

主子表有序歸併舉例

報表有兩個資料集,分別來自訂單表(Orders)和訂單明細表(OrderDetails)

訂單表主鍵與訂單明細表部分主鍵關聯,訂單表是主表,訂單明細表是子表。現在兩個表都按照訂單 ID 有序

【計算目標】 查詢某時間段內客戶訂單明細

集算器資料準備


A B
1 =connect(“db”)
2 =A1.query(“select 訂單 ID, 客戶 ID from 訂單 order by 訂單 ID where 訂購日期 >=? and 訂購日期 <=?”,begin,end)
3 [email protected](“select 訂單 ID, 訂購產品, 價格, 數量 from 訂單明細 order by 訂單 ID “)
4 [email protected](A2:o, 訂單 ID;A3:od, 訂單 ID)
5 =A4.new(o. 客戶 ID: 客戶,o. 訂單 ID: 訂單,od. 訂購產品: 產品,od. 價格: 價格,od. 數量: 數量)

指令碼解析:

1、A2-A3 分別查詢訂單和訂單明細資料,結果集按訂單 ID 有序

2、A4 通過有序歸併演算法(@m 選項)對兩個集合按照訂單 ID 關聯

3、A5 獲得關聯結果為報表返回結果集

有序歸併可以極大提高關聯效率,下面簡單解釋一下。

設兩個關聯表的規模(記錄數)分別是 N 和 M,則 HASH 分段技術的計算複雜度(關聯欄位的比較次數)大概是 SUM(Ni*Mi),其中 Ni 和 Mi 分別是 HASH 值為 i 的兩表記錄數,滿足 N=SUM(Ni) 和 M=SUM(Mi),這大概率會比完全遍歷時的複雜度 N*M 要小很多(運氣較好的時候會小 K 倍,K 是 HASH 值的取值範圍)。

如果這兩個錶針對關聯鍵都有序,那麼我們就可以使用歸併演算法來處理關聯,這時的複雜度是 N+M;在 N 和 M 都較大的時候(一般都會遠大於 K),這個數會遠小於 SUM(Ni*Mi),這就是有序歸併的好處。

潤乾報表層次資料集

通過這些例子,集算器為報表準備資料時最終返回的均為標準 ResutSet,這就經常需要將集算器的分層結構(如 switch 和 join 後結果集)轉換成標準的二維表,雖然轉換工作不復雜,但如果能直接使用分層結果集會更加簡單高效。

潤乾報表 5.0 及以上版本就支援直接使用帶有層次的資料集進行資料呈現。沿用訂單和訂單明細的主子表結構。

【報表展現目標】

報表每個單元上面是訂單資訊(單條),下面是明細資訊(多條),屬於典型的主子報表。

集算器資料準備


A B
1 =connect(“demo”)
2 =A1.query(“select 訂單 ID, 客戶 ID from 訂單 order by 訂單 ID where 訂購日期 >=? and 訂購日期 <=?”,begin,end)
3 [email protected](“select 訂單 ID, 訂購產品, 價格, 數量 from 訂單明細”) =A3.group(訂單 ID)
4 [email protected](A2:o, 訂單 ID;B3:od, 訂單 ID)
5 =A4.new(o. 客戶 ID: 客戶,o. 訂單 ID: 訂單,od)

指令碼解析:

1、B3 將訂單明細按照訂單 ID 分組,得到分組子集(保留分組成員)

2、A4 訂單關聯訂單明細,一條訂單資訊對應多條明細

3、A5 生成報表可以接收的多層結果併為報表返回資料集

潤乾報表設計

設定引數

開啟報表設計器,新建報表後設置查詢引數

設定資料集

設定報表資料集,選擇集算器資料集型別,新增上述準備好的集算器指令碼檔案,並設定報表引數與集算器指令碼引數對應

資料集設定後,在報表設計器右下角的資料集視窗中即輸出層次結構如下:

編寫報表表示式

直接使用集算器提供的層次結果集設定報表表示式,其中設定 B4、B5、B6、B7、B8 左主格為 C4(按照訂單擴充套件)

通過以上步驟即可完成基於層次資料集的報表設計,目前只有潤乾報表提供了層次資料集支援,在製作主子表、分組明細報表時就可以在資料準備(資料集)階段將資料準備好,然後為報表返回帶有層次的資料集,報表直接引用無需再次關聯或分組,可以帶來更高的報表效能。

子表有序計算舉例

區分主子表後,如果從主表觀察子表常常會涉及分組子集和有序運算,這時用集算器處理就非常方便了。舉一個並不十分常見的例子,讀者可以感受一下。

報表有三個資料集,分別來自回款表(OrderPayment)、訂單表(Orders)和訂單明細表(OrderDetails)。

訂單表的主鍵是訂單 ID,回款表的主鍵是(編號,訂單 ID),訂單明細表的主鍵是(編號,訂單 ID),訂單的主鍵是回款和訂單明細的一部分,訂單表是主表,回款表和訂單明細表是子表。

【計算目標】 統計每個客戶的每個訂單中,最大和最小兩筆回款,最高和最低兩個價格

這裡並不是計算彙總值,而是要找出每個客戶的每筆訂單中回款金額最大和最小的兩筆回款,以及每筆訂單中訂購產品最高和最低的兩個價格,用以識別客戶型別及其回款能力。

集算器資料準備


A B C
1 =connect(“db”)

2 =A1.query(“select 訂單 ID, 客戶 ID from 訂單 order by 客戶 ID, 訂單 ID”)

3 =A1.query(“select 訂單 ID, 回款金額 from 回款 order by 訂單 ID, 回款金額 desc”) =A3.group(訂單 ID) =B3.(.m(1).m(-1))
4 [email protected](“select 訂單 ID, 單價 as 價格 from 訂單明細 order by 訂單 ID, 價格 desc”) =A4.group(訂單 ID) =B4.(.m(1).m(-1))
5 [email protected](A2:o, 訂單 ID;C3:op, 訂單 ID;C4:od, 訂單 ID)

6 =A5.new(o. 客戶 ID: 客戶,o. 訂單 ID: 訂單,op.m(1). 回款金額: 最大回款金額,op.m(-1). 回款金額: 最小回款金額,od.m(1). 價格: 最高單價,od.m(-1). 價格: 最低單價 )

指令碼解析:

1、A2 查詢訂單資料,按照客戶和訂單排序

2、A3 查詢回款資料,按照訂單排序,回款金額降序

3、B3 按照訂單 ID 分組,由於要查詢分組成員(最大和最小值),所以這裡需要使用 group 函式分組並保留分組結果(不聚合)

4、C3 找出每組中回款金額最大和最小兩條記錄

5、同理 A4-C4 按照訂單分組查詢每組中價格最高和最低兩條記錄

6、A5 根據客戶和訂單資訊左關聯上述兩個結果集(注意:關聯一定要在前面兩個分組後進行,如果先關聯則會出現多對多叉乘,導致結果錯誤)。

每一條關聯結果,訂單隻有一條記錄,回款和訂單明細則包含兩條記錄,這是主子表關聯關聯計算的特點,主表的一條記錄指向子表的多條記錄

7、根據關聯結果,生成最終結果集,併為報表返回結果集

以上通過集算器關聯運算解決了多資料集關聯報表的效能問題,實測中報表效能可獲得數倍到數百倍的提升(隨資料規模和關聯表數量線性增長)。同時集算器解決方案實現比較簡單,適用範圍更廣,適用於資料庫無法完成的跨異構庫關聯、文字關聯等情況,從而為報表效能優化、降低報表應用耦合性提供了新思路。