1. 程式人生 > >自定義資料來源是報表開發的常態

自定義資料來源是報表開發的常態

報表專案中,大部分報表簡單的搞搞即可完成。但是,總有一部分複雜報表需要自定義資料集才能實現。自定義資料集是指報表的資料來源不能通過簡單SQL實現,需要用報表工具提供的API,呼叫程式設計師開發的程式來實現。這部分報表數量不多,但是程式設計、除錯工作量較大,在整個專案中佔用的時間反而更長。

為什麼自定義資料集會成為報表專案的常態? 

報表由兩部分組成的:資料計算和報表呈現。自定義報表出現的原因,是因為資料庫的原始資料結構與報表要展現的資料之間差異大,造成報表資料計算過程比較複雜。

有些報表連線的原始資料庫是生產資料庫,資料結構不適合報表直接展現,所以要寫比較複雜的程式;即使報表連線的是經過整理的資料倉庫,其資料結構也不一定能適合所有的報表,特別是專案後期出現的報表,一般都要在資料倉庫的基礎上做進一步複雜計算才能在報表中展現出來。 

出現自定義資料來源的另外一個原因,是需要連線多個數據庫或者其他種類的異構資料來源,如oracle、db2、My sql和檔案等,也需要自定義資料來源。

自定義資料來源唯一的好處:無論資料來源計算多麼複雜,資料來自多少個不同的資料庫或者檔案,只要會寫程式碼、肯寫程式碼,一定能把所有資料集中到一起完成計算。

現有的程式設計手段對自定義資料來源沒有特別理想的。

以Java為例,用Java來自定義資料來源的工作量較大,難度高,因為Java並沒有提供常用類庫,我們要耗費大量時間和精力來手工實現細節,包括聚合,過濾,分組,排序,排名等。比如,資料儲存和訪問的細節:每條資料,每個二維表都需用List/map等物件組合起來,再用巢狀的多層迴圈來算。這類計算往往涉及到批量資料之間的集合和關係運算,或者物件之間和物件屬性之間的相對位置的運算,這些底層邏輯搞起來非常費力。如果還有複雜的有序計算,Java處理的工作量就更大了。 

舉例:某網路平臺需要監測一定週期內的使用者狀況,為運營部門出具日報、週報、月報、年報等報表,每類報表中均包含本期與上期、上上期資料比較。此處以日報為例(月報年報只是統計週期不同),報表格式如下:


報表分為兩部分,上半部分為使用者明細資料,由於使用者較多,報表中只顯示按本期線上時長排序後的前十名和後十名;下半部分為本期資料與上期、上上期的比較結果。

以潤乾報表為例,使用Java來計算自定義資料來源的主要程式碼如下:

獲取報表引數

        Map map = ctx.getParamMap(false);

        if (map != null) {

            Iterator it = map.keySet().iterator();

            while (it.hasNext()) {

                // 分別取得引數

                String key = it.next().toString();

                data_date = map.get(key).toString();

            }

        }

執行資料庫sql取數

            String sql ="select a.userid auserid,a.first_logout_time,b.userid buserid,b.onlinetime bonlinetime,b.account baccount,"

                  +"c.userid cuserid,c.onlinetime conlinetime,c.account caccount,d.userid duserid,d.onlinetime donlinetime,d.account daccount,"

                  +"from"

                  +"(select   v.userid, v.first_logout_time"

                  +"from     t_dw_zx_valid_account v"

                  +"where    v.standard_7d_time is not null) a,"

                  +"(select   userid, sum(onlinetime) onlinetime, max(account)"

                  +"from     t_dw_zx_account_status_day"

                  +"where    logtime >= to_date('"+start_time_tm+"','yyyy-mm-dd hh:mi:ss')"

                  +"and      logtime <"+end_time_tm+"','yyyy-mm-dd hh:mi:ss')"

                  +"group by userid"

                  +"having max(account) is not null) b,"

                  +"(select   userid, sum(onlinetime) onlinetime, max(account)"

                  +"from     t_dw_zx_account_status_day"

                  +"where    logtime >= to_date('"+start_time_lm+"','yyyy-mm-dd hh:mi:ss')"

                  +"and      logtime <  to_date('"+start_time_tm+"','yyyy-mm-dd hh:mi:ss')"

                  +"group by userid"

                  +"having max(account) is not null) c,"

                  +"(select   userid, sum(onlinetime) onlinetime, max(account)"

                  +"from     t_dw_zx_account_status_day"

                  +"where    logtime >= to_date('"+start_time_lm_1+"','yyyy-mm-dd hh:mi:ss')"

                  +"and      logtime <  to_date('"+start_time_lm+"','yyyy-mm-dd hh:mi:ss')"

                  +"group by userid"

                  +"having max(account) is not null) d"

                  +"where  a.userid = b.userid(+)"

                  +"and    a.userid = c.userid(+)"

                  +"and    a.userid = d.userid(+))"

                  +"order by b.onlinetime desc";

為降低複雜度,資料初步加工(分組、過濾、排序)仍然使用sql完成。

獲取列名

            for(int i=0;i<colCount;i++){

                colName.add(rsmd.getColumnName(i+1));//列名

                type = rsmd.getColumnType(i+1);

            }

讀取表資料,將其存入List

            while (rs.next()) {

                List<Object> rowData = new ArrayList<Object>();

                for(int i=0;i<colCount;i++){

                    rowData.add(rs.getObject(i+1));

                    System.out.println("rowData"+i+"="+rowData.get(i));

                }

                data.add(rowData);

            }

構造資料集ds1

DataSet ds1 = new DataSet("ds1");

        for (int i = 0; i < colName.size(); i++) {

            ds1.addCol(colName.get(i));// 設定資料集的欄位

        }

        Row rr = null;

遍歷List計算彙總值

        for(int i=0;i<data.size();i++){

            List<Object> row_data = data.get(i);

            boolean flag1=false;

            boolean flag2=false;

            boolean flag3=false;

            boolean flag4=false;

            boolean flag5=false;

            for(int j=0;j<row_data.size();j++){

                Object single_data = row_data.get(j);

                String str_single_data = single_data.toString();

                /****************計算彙總值************************/

                if(j==3 && single_data!=null){//buserid is not null

                    flag1=true;

                }elseif(j==6 && single_data!=null){//cuserid is not null

                    flag2=true;

                }elseif(j==9 && single_data!=null){//duserid is not null

                    flag3=true;

                }elseif(j==2){

                    if(str_single_data.compareTo(start_time_lm)>=0 && str_single_data.compareTo(start_time_tm)<0){

                        flag4=true;//

                    }

                    if(str_single_data.compareTo(start_time_lm)<0){

                        flag5=true;

                    }

                }

                if (flag2&&flag3){

                    count1++;  

                }

                if(flag3&&!flag1){

                    count2++;  

                }

                if(flag4){

                    count3++;

                }

                if(flag1&&flag4){

                    count4++;

                }

                if(!flag3&&flag2&&flag5){

                    count5++;

                }

                if(!flag1&&!flag3&&flag2&&flag5){

                    count6++;

                }

            }

前十名資料

            if(i<=10){

                // 設定資料集中的資料

                rr = ds1.addRow();

                for (int j = 0; j <row_data.size(); j++) {

                    rr.setData(j + 1, row_data.get(j));             }

            }

        }

後十名

for(int i=0;i<data.size();i++){

            List<Object> row_data = data.get(i);

                    if(i>data.size()-10){

                // 設定資料集中的資料

                rr = ds1.addRow();

                for (int j = 0; j <row_data.size(); j++) {

                    rr.setData(j + 1, row_data.get(j));

                }

            }

        }

程式碼已經很長了,加上獲取資料庫連線、計算前n天和後n天日期的程式碼會更長。而且,資料的分組排序是資料庫(sql)完成的,如果用Java程式碼量會更大。

我們試著用集算器來弄,找剛進組的小孩做。

上邊的例子,集算器程式碼如下:


 

A4-A6:進行資料過濾;

A7-A9:按userid分組;

A10:將以上結果集進行關聯;

A11:基於A10進行過濾後按線上時長排序;

A12:新序表,用於讀取前後十名記錄;

A13-A14:通過序號分別取前後十名記錄;

A15-A20:計算彙總值;

A22:將前十名、後十名記錄以及彙總值分別以不同結果集通過集算器JDBC返回給報表。

一個螢幕內搞定整個程式碼,比較簡潔。集算器對有序運算的支援,使得取前後十名(A13、A14)非常容易,不用像Java必須寫個迴圈。報表工具通過Jdbc呼叫自定義資料來源。與Java的API介面比較,Jdbc呼叫更簡單。另外,集算器程式碼是解釋執行的,無需編譯,程序升級時替換指令碼程式檔案即可。

學習成本方面,理解集算器的序表、遊標花了點功夫,總體看效果不錯。

相關推薦

VS2010 建立和使用定義資料來源報表詳細過程

VS2010 建立和使用自定義資料來源報表詳細過程 首先看一下最終效果(顯示資料進行了加密,所以成了亂碼) 圖1.窗體的效果 圖2.點選預覽時的效果 1.建立顯示報表的窗體 新建窗體命名為ReportForm並修改窗體名為學生成績 在窗體上只需要拖放一個ReportVi

定義資料來源報表開發常態

報表專案中,大部分報表簡單的搞搞即可完成。但是,總有一部分複雜報表需要自定義資料集才能實現。自定義資料集是指報表的資料來源不能通過簡單SQL實現,需要用報表工具提供的API,呼叫程式設計師開發的程式來實現。這部分報表數量不多,但是程式設計、除錯工作量較大,在整個專案中佔用的

cordova跨平臺app開發02_定義插件開發與安裝

xtend else callback 視頻 方法名 pty ges ray expect 視頻地址:http://t.cn/RacmXiy cordova的自定義插件由js、原生代碼文件(java、oc)、plugin.xml三部分組成。 cordvoa提供了命令來創

php微信定義菜單開發

php menu wechat 微信自定義菜單需要有一個微信服務號,在開發之前需要獲取access_token,獲取方法很簡單,登陸微信公眾賬號,進入開發者模式,就可以看到{開發者憑據}:下面AppId和AppSecret,開發者文檔說明 :接口調用請求說明http請求方式: GEThttps:/

openERP筆記 定義模塊開發

oom size 創建工程 for 編號 文檔 姓名 use python ##需求描述 輸入和查詢課程,把信息儲存到課程對象裏 課程包含以下信息:名稱,價格,天數,開始日期,教師,學員 每個課程可以有多個學員,要記錄學員的姓名、電話、電子郵件 課程可以添加教材和作業等

Android 開發定義控制元件開發-01

最近一直在忙於公司的專案,因為要去現場測試正式使用,專案不大但是經手了三個人,到我這裡只能去填坑了,不說這個了,說一下今天得主題,自定義控制元件之基本圖形繪製。 我們平時畫圖需要兩種工具:紙和筆。在Android中 Paint 就是畫筆,而Canvas類就是紙,在這裡叫做畫布。 所以

微信訂閱號定義選單java開發

小弟應運營要求要更改公眾號選單,本以為是在公眾號官網上進行配置即可,,誰知道 竟然是開發者模式編輯的。無奈,從未接觸過公眾號開發的我。有開始啃開了微信開發的api,原來的做這個得小夥伴,跑路了。但沒有交接。哎~~~ 廢話不多說。開啟流程。 1、小弟這個僅僅是【訂閱號】的開發。進入公眾號後左邊

定義的日曆開發筆記

開發思路 - 使用HTML合理規劃元件結構 - 為元件編寫美觀的樣式 - 如何使用javascript獲取元件所需資料 - 將資料與HTML結構結合 - 使用者時間處理 原生js的操作和jq

SpringXD 定義Job模組開發

SpringXD中的Job實際即為Spring Batch中的Job,因此我們先按照Spring Batch的規範開發一個簡單的Job。 專案依賴: <dependencies> <dependency&

spring-security-oauth2(七) 定義簡訊登陸開發

簡訊登陸開發 原理 基本原理:SmsAuthenticationFilter接受請求生成SmsAuthenticationToken,然後交給系統的AuthenticationManager進行管理,然後找到SmsAuthenticationProvider,然後再呼叫UserDeta

Android 開發定義控制元件開發-02

1.畫筆的基本設定 : 1.setColor() 該函式的作用是設定畫筆顏色,完整的函式宣告如下: void setColor(int color) 我們知道,一種顏色是由紅、綠、藍三色合成出來的,所以引數 color 只能取8位的0xAARRGGBB樣式顏色值。 其中:

Spark Streaming--2 定義資料來源

通過繼承Receiver,並實現onStart、onStop方法來自定義資料來源採集。 需要自己開一個sockect,,然後輸入內容。 nc -lk master 8888 package com.jiangnan.spark import java.io.{BufferedRead

定義Delphi XE開發的移動端程式啟動閃屏Splash功能(轉)

第五步,選中[Splash tile mode]的下拉框,這是Splash介面中的標題模式, 一般選擇不顯示標題的。這個選項作一下解釋: 1. disabled -按圖片尺寸大小顯示,顯示位置有Splash Gravity設定 2. clamp - 圖形邊框適應螢幕大小 3. repeat - 螢幕

flume ng進擊之路 (三) —— 定義source API開發

概述 關於flume ng的簡單介紹,可以參考flume ng進擊之路 (一)—— 入門,同時flume ng也提供了各種各樣的source和sink介面供我們在生成環境中使用,但是在生產環境中,我們常常需要定製的source或者sink來滿足我們的要求。

定義指令碼引擎開發紀實 -前序

前序 大家可能對JavaScript,VBScript,Python,Lua,Shell等指令碼語言並不陌生,有時候也對Swift語言的特性表現出很大的興趣。大家在樂此不彼的使用這些語言的時候,有沒有想過要自己實現一個指令碼引擎呢?(拜託,自己寫的能跟那些成熟

定義水晶報表的外觀

1、外觀:設定 Crystal Report Viewer 的屬性:BestFitPage 布林值。獲取或設定頁面檢視是大小合適還是用滾動條進行裁剪。配合設定 Width、Height 來實現無空白和無滾動條的顯示!DisplayGroupTree 布林值。獲取或設定樹檢視是

ffmpeg 定義資料來源, 可以是檔案,可以是記憶體,可以是網路, 愛咋的咋的

ffmpeg 自定義資料來源, 可以是檔案,可以是記憶體,可以是網路, 愛咋的咋的 // ffmpeg_custom_context.cpp : Defines the entry point for the console application. //

Android定義控制元件開發系列(三)——仿支付寶六位支付密碼輸入頁面

        在移動互聯領域,有那麼幾家龍頭一直是我等學習和追求的目標,比如支付寶、微信、餓了麼、酷狗音樂等等,大神舉不勝舉,他們設計的介面、互動方式已經培養了中國(有可能會是世界)民眾的操作習慣:舉個小例子,對話方塊“確定”按鈕的左右位置就很有學問,如果大家都是左邊取消

sharepoint 2010 定義欄位開發(1) 建立一個簡單的列表定義欄位

在sharepoint 2010 中,最常用的就是對自定義列表或者文件庫的使用,建立一個自定義列表或者文件庫,新增一些需要的欄位,sharepoint 2010 自帶了很多不同型別的控制元件供欄位使用,如下圖 很多特殊情況下,這些型別控制元件,不一定能滿足我們的需求,所以

在native執行緒利用JNI 反射定義類--ndk開發參考2

從前面我們知道,在虛擬機器初始化後,執行Java程式碼的方法時,要先查詢到類,也就是呼叫函式FindClass。接著後面分析怎麼樣從dex檔案載入類資料到記憶體,現在開始對查詢函式FindClass進行分析,就很好理解了,因為前面介紹載入類到記憶體的流程已經很清楚。函式FindClass程式碼如下: sta