利用Access製作成績報表(四)(自訂函數的應用—-設計模擬Excel2010統計函數PERCENTRANK.INC)

前言:在前一篇文章(利用Access製作成績報表(三)(函數的應用))中我們談到利用Access系統內建函數去達成我們所要的運算,雖然Access已經提供了很多函數,但是面對使用者種種不同的需求,很難完全滿足使用者,所以Access提供使用者自訂函數的功能。

image(利用"運算式建立器"輸入函數建立運算式)

如何建立自訂函數?

在Office的系列軟體都提供VBA的環境,我們可以利用它來建立自訂函數,打開Access資料庫執行"建立/巨集與程式碼/模組"

image

執行之後會在資料庫中加入一個VBA模組,預設的名稱為"Module1"

image

在右邊視窗Option Compare Database 之下加入程式碼。

因為Access並未提供類似Excel2010統計函數PERCENTRANK.INC函數,所以在前文中(利用Access製作成績報表(三)(函數的應用))我們要計算學生考試成績佔全體考生的百分比,是直接在Excel2010中先利用PERCENTRANK.INC函數計算出來,再將資料表連結至資料庫中,所以如果使用者沒有Excel2010基本上無法得到正確的計算結果,所以一勞永逸的作法,這裡我們自己寫一個模擬PERCENTRANK.INC的自訂函數,這樣使用者可以不受限於2010才可使用。

先聲明作者並未受過正統的演算法訓練,筆者設計之函數演算法與Excel2010統計函數PERCENTRANK.INC的函數演算法是否相同不得而知,計算的結果與Excel2010統計函數PERCENTRANK.INC的函數所計算的結果近似,如需精確之計算結果請斟酌使用。

我所設計之模擬Excel2010統計函數PERCENTRANK.INC的函數程式碼如下:

'作者:楊煥謀
'歡迎引用請註明出處
'2011/12/25 寫於耶誕節
Function getrankp(ByVal dbtable As String, ByVal dbfield As String, ByVal x As Variant, Optional ByVal bolinc As Boolean = True) As Single
Dim k As Long, i As Long, j As Long
Dim var() As Variant
Dim ob As Database
Dim strSQL As String
Dim rs As Recordset
Dim lngrs As Long

Set ob = Application.CurrentDb

strSQL = "SELECT " & dbfield & " FROM " & dbtable & " ;"
Set rs = ob.OpenRecordset(strSQL, dbOpenDynaset, dbReadOnly)
     rs.MoveLast
    lngrs = rs.RecordCount - 1
ReDim var(lngrs)
rs.MoveFirst
For i = 0 To lngrs

var(i) = rs.Fields(0)

rs.MoveNext

Next i




j = UBound(var)
k = 0

If bolinc = True Then '包括0與1
For i = 0 To j

If var(i) >= x Then

k = k + 1

End If

Next i


If k = 1 Then '代表第1名
getrankp = 1

Else

getrankp = ((j + 1 - k) / (j + 1))

End If


Else
'不包括0與1
For i = 0 To j

If var(i) > x Then

k = k + 1

End If

Next i


For i = 0 To j

If var(i) = x Then

k = k + 1

End If

Next i



getrankp = ((j + 2 - k) / (j + 2))




End If



End Function

設計好我們的函數要如何應用?

在(利用Access製作成績報表(三)(函數的應用))中我們是在報表設計中使用函數進行特定資料的運算,在這裡我們把自訂函數運用在產生查詢資料表中,為什麼如此?假設使用者的Excel資料表沒有先算好百分比與等級欄位(例如不是在Excel2010無法使用PERCENTRANK.INC),如下表。

image

則連結的資料中便沒有百分比與等級這兩個欄位,這時候我們可以透過設計查詢新增一個含有這兩個欄位(這兩個欄位的資料就必須透過我們的自訂函數產生)的查詢資料表,再利用此查詢資料表作為報表的資料來源。

執行"建立/查詢/查詢精靈"

image

執行"簡單查詢精靈"

image

選擇"資料表:成績表",這個表中已經有用Excel算出的百分比與等級欄位,剛好可以與我所設計的函數進行驗算。

image

除了年級欄位不選取,其他欄位都加入,按"下一步"。

image

選擇詳細查詢,按"下一步"。

image

將標題設定為"成績表自訂函數測試查詢",然後選擇"修改查詢的設計",按"完成"。

image

在設計模式中可以可以看到查詢各個欄位的基本屬性。

image

我們在最右邊空白欄位,在欄位列輸入"百分等第:",然後按下滑鼠右鍵。

image

點選浮動功能表中"建立器"選項。"運算式建立器"中"運算式元素"列表將"函數"展開,點選資料庫名稱可以看到右邊"運算式類別"出現"Module1","運算式值"出現"getrankp",點選此項目會在"運算式建立器"表單下面出現getrankp這個函數需要的參數。

image

以下就getrankp的四個參數分別說明:

dbtable : 資料表名稱,參數型別string

dbfield : 欄位名稱,參數型別string

x : 資料列的值,參數型別variant

bolinc : 選擇性參數,預設為true代表包括0,1。如果不包括模擬Excel2010統計函數PERCENTRANK.EXC

在"輸入運算式"視窗中"百分等第:"後輸入 getrankp("成績表","成績表.總分",CVar([成績表]![總分])) ,按下"確定"。

image

在下一空白欄位同樣步驟輸入"級別等第:",按滑鼠右鍵,一樣點選"建立器"。

image

在"運算式建立器"輸入 以下運算式 級別等第: Switch([百分等第]<=0.25,"待加強",([百分等第]>0.25) And ([百分等第]<=0.85),"基礎",[百分等第]>0.85,"精熟")

image

按下"確定",後回到查詢設計模式。

image

在資料表列表視窗中按右鍵,點選"資料工作表檢視",可以看到查詢計算的結果。

image

"百分比"與"等級"兩欄是原本Excel工作表的資料,"百分等第"與"級別等第"是利用自訂函數與Access内建函數所算出來的,驗算一下"百分比"與"百分等第"的值發現應該差在進位的部分,可以再加入round函數進行修正,這一部分就留給讀者去完成吧!

範例檔案

利用Access製作成績報表(三)(函數的應用)

接續上篇文章(利用Access製作成績報表(二)(群組功能應用)),在前文中提到利用加入等級群組,將各等級分組統計,但是這樣的方式無法進行全班以座號排序列表,這樣與一般成績表習慣的呈現方式相異,一般成績表呈現應該如下圖(一)。簡單的說;我們希望以班級為群組,每班以座號排序,然後統計各等級的人數,應該要怎麼做?

image圖(一)

首先我們先利用報表精靈測試看看是否可以達到我們的目的,經過測試要達到這樣的需求,沒有辦法直接利用報表精靈來完成,必須使用者在報表設計中加入額外的函數計算才能達成。

Access提供的函數很多,如何找到我們需要的函數,報表精靈完成的報表可以提供一個索引,利用以下的步驟影片完成一個"測試的成績"報表。

依照上述的步驟,可以完成以下的報表,在報表摘要的部分,我們可以看到一列資料 摘要 "班級" = 1 (29 詳細記錄)這列摘要資料代表班號為1的資料有29筆,因為我們以班級為群組,所以摘要資料中的筆數很明顯是班級群組的資料,所以這個資料來源必為計數的運算結果,我們可以透過報表的設計模式看到它是如何得到的。

image(圖二)

在報表的視窗,點選"測試成積表"報表,然後按滑鼠右鍵,在浮動功能表中點選"設計檢視"。

image(圖三)

在設計模式下我們可以看到報表的一些秘密,例如摘要 "班級" = 1 (29 詳細記錄) 這列資料是怎麼來的?

image(圖四)

用滑鼠點選一下="摘要 " & "’班級’ = " & " " & [班級] & " (" & Count(*) & " " & IIf(Count(*)=1,"詳細記錄","詳細記錄") & ")"這個資料區塊,可以發現報表是用一個"文字方塊控制項"(Text10)來呈現這個資料,在設計模式中可以看到報表其實也如同表單一樣,它是由一些"標簽控制項"與"文字方塊控制項"組合而成,然後散佈在報表的不同區塊中,"標簽控制項"主要呈現靜態的文字內容;例如欄位標題內容。而"文字方塊控制項"主要是隨著資料來源異動內容,簡單的說就是資料表的細部內容,所以文字方塊控制制項中會有一個"控制項資料來源"的屬性。

image(圖五)

舉例來說仔細觀察右側的屬性表,可以發現這個Text10文字方塊控制項的資料來源是透過以下這列公式而來的:

="摘要 " & "’班級’ = " & " " & [班級] & " (" & Count(*) & " " & IIf(Count(*)=1,"詳細記錄","詳細記錄") & ")"

最左邊=代表是這個控制項資料來源的屬性值等於 "摘要 " & "’班級’ = " & " " & [班級] & " (" & Count(*) & " " & IIf(Count(*)=1,"詳細記錄","詳細記錄") & ")" 這個運算式運算的結果。

 

簡單說明這個運算式各個運算符號與函數的意義:

& 運算符號是字串相加,運算式字串必須用""號包括其中,舉例 "班級=" & "3" 輸出的結果 班級=3   。

[班級] 這個是代表報表表單中名為"班級"的"文字方塊控制項"欄位的資料,因為我們利用資料表中"班級"欄位分群組,所以在設計模式中報表"班級群組首"區塊中有一個名為"班級"的"文字方塊控制項",在運算式中我們用[班級]來代表,其呈現的是班級群組所代表的值。

Count(expr)這個函數是SQL聚合函數,expr是代表函數參數,expr可以輸入指定的欄位名稱,例如Count(‘座號’)則為計算座號欄的記錄數,但是若座號欄為Null 則Count不計算記錄數,如果要計算多個欄位用&來連接,舉例Count(‘座號 & 姓名’),計算查詢傳回的座號和姓名兩欄的記錄數目,但是如果兩欄記錄都為Null則則Count不計算記錄數。函數中參數若為*,則Count(*) 會將所有記錄數目傳回,包括Null的欄位記錄,所以為了確定可計算出記錄筆數,報表精靈在運算式中使用Count(*) 。

另外利用報表精靈產生報表,報表中不同區塊其代表的是不同查詢條件產生的查詢結果,所以當以班級為群組層次,報表群組區塊(包括群組首與群組尾)其查詢相當於執行以下的SQL語法

SELECT 成績表.[班級] FROM 成績表 GROUP BY 成績表.[班級];

此查詢執行結果如下:

image(圖六)

 

把成績表中班級的資料相同者視為同一群組,等於列出所有班級資料料。而在報表群組區塊中執行SQL的聚合函數Count(*),相當於執行以下SQL語法

SELECT 成績表.[班級] , Count(*) AS 班級人數  FROM 成績表 GROUP BY 成績表.[班級];
查詢產生的結果如下:

image(圖七)

而詳細資料區塊,就是利用群組資料當作查詢條件所得的查詢結果,例如班級=1的詳細資料相當於執行以下的SQL語法:

SELECT 成績表.班級, 成績表.座號, 成績表.學號, 成績表.姓名, 成績表.總分, 成績表.等級 FROM 成績表 WHERE (((成績表.班級)=1));
查詢產生的結果如下:

image(圖八)

知道報表精靈資料產生的概念,簡單的說透過群組層次區塊,可以將群組查詢的結果傳入詳細資料區塊中,當成查詢的條件。如果要計算出同一個班級中某一等級的人數,例如1班精熟級的人數,執行的SQL語法如下

SELECT Count(*) AS 精熟級 FROM 成績表 WHERE (((成績表.班級)=1) AND ((成績表.等級)=’精熟’));

執行結果如下

image(圖九)

要在報表區塊中產生這樣的運算,很明顯必須傳入兩個查詢條件,一個是"班級",另一個是"等級"。要利用報表精靈達成這個目的,按照前一篇文章利用Access製作成績報表(二)(群組功能應用),只要再新增一個等級群組層次即可計算出,但是這樣我們無法產生一個依照班級座號排序的成績表,只能產生如以下的成績表。

image(圖十)

如果我們希望同一班能按座號排序,同一班為同一群組,不再區分其它群組,則不能再新增群組層次。如何才能不破壞資料排序,計算出各等級的人數,顯然不能利用Count()函數,因為Count函數的參數只能是欄位名稱或*,基本上無法加上類似WHERE的條件。

還好Access還提供一類型的"範圍聚合函數",此類函數等於是SQL的聚合函數,再加上範圍條件(WHERE),我們可以用DCount(Expr, Domain, Criteria)這個範圍聚合常數來達成目的。以下參考微軟的手冊分別說明此函數三個參數的意函:

參數Expr:此參數是字串型態,代表要計數的資料表或查詢的欄位名稱,以上述報表為例,我們要針對報表中"詳細資料"區塊中查詢的結果"等級"欄位進行計數,我們可以輸入"[成績表]![等級]",一定要有引號因為是字串型態參數。

參數Domain:此參數是字串型態,代表要計數的資料表名稱、查詢名稱或資料集名稱,以上述報表為例,我們的目標資料表是"成績表"。

參數Criteria:此參數是字串型態,用來限制要執行 DCount 函數的資料範圍。例如,criteria 通常相當於 SQL 運算式中的 WHERE 子句,但不用加上 WHERE 這個字。如果省略 criteria,則 DCount 函數會計算整個範圍的 exprcriteria 中包含的所有欄位也都必須是 domain 中的欄位,否則 DCount 函數會傳回 Null。以上述報表為例,我們要計算1班"精熟"等級的人數,我們的WHERE條件可以寫成(((成績表.班級)=1) AND ((成績表.等級)=’精熟’))

,但是這樣的寫法,只能算1班的資料,當班級群組層次資料改變為2的時候,這個條件((成績表.班級)=1)必須變成((成績表.班級)=2)才能統計2班的資料,這樣是無法在輸出報表時隨著班級變動而進行正確的計數,所以如果要讓班級等於變動的群組值,必須把條件改成"([成績表]![班級] = " & [Text20] & " )",其中[Text20]是我們報表中一個"控制項資料來源"來自[班級]資料的文字方塊的控制項名稱,我們要把此控制項置於"詳細資料"區塊中,那麼它呈現的資料就是班級群組的資料,所以當班級群組資料變動[Text20]的值也跟著變動,這樣就可以計算不同班級的資料。

知道算式的寫法,接下來我們進入報表的設計模式,在班級群組尾區塊,除了原有的班級人數統計的文字方塊,另外再放置四個文字方塊控制項

image(圖十一)

然後在每一個文字方塊的控制項資料來源屬性,依照欲計算得等級輸入運算式如下

 

image(圖十二)

計算基礎級人數:

=" 基礎級人數 = " & DCount("[成績表]![等級]","成績表","( [成績表]![等級] = ‘基礎’ ) AND ([成績表]![班級] = " & [Text20] & " )")

計算待加強級人數:

=" 待加強級人數(後百分之25) = " & DCount("[成績表]![等級]","成績表","( [成績表]![等級]  =  ‘待加強’ )  AND  ([成績表]![班級] = " & [Text20] & " )")

計算精熟級人數:

=" 精熟級人數(前百分15) = " & DCount("[成績表]![等級]","成績表","( [成績表]![等級]  =  ‘精熟’ )  AND  ([成績表]![班級] = " & [Text20] & " )")

我們還可以加入計算通過率

=" 通過率(全班精熟級+基礎級人數)/(全班總人數) = " & Round(1-(DCount("[成績表]![等級]","成績表","( [成績表]![等級]  =  ‘待加強’ )  AND  ([成績表]![班級] = " & [Text20] & " )")/Count(*)),3)

 

如果對於Access運算式不熟,可以按下控制項資料來源屬性輸入方塊最右側image按鈕,叫出"運算式建立器"

image(圖十三)

在"運算式建立器"的表單中可以快速查詢函數的使用方法與相關資料欄位,再透過簡單的引導就可以建立運算式。

image(圖十四)

完成的報表預覽列印的結果如下:

image(圖十五)

 

範例檔案

利用Access製作成績報表(二)

接續上一篇利用Access製作成績報表(一) ,如果要將各班不同級別的人數統計出來如下圖,該如何進行?

最快的方式就是利用報表精靈重新建立報表,在"群組層次"中再加入"級別"此群組。

image

然後以"座號"為排序,點選"摘要選項"。

image

"顯示"設定"詳細資料及摘要值",摘要值我們勾選平均、最大、最小,按下"確定"

image

再按下一步

image

選擇版面設定

image

輸入報表標題,按下完成

image

產生預覽的報表

image

這個版面並沒有將所有欄位資料呈現,必須再進行修改,點選"成績報表",按滑鼠右鍵在快顯功能表單中選擇"設計檢視’

image

在設計畫面進行修改,將各欄位寬度調整至一頁所能呈現的,再將"報表首"區段中的"成績報表"修改為"九年級競試成績表1",則報表的表頭會更改成"九年級競試成績表1"。

image

調整完即可呈現如下圖之結果,按照級別統計出摘要值。

但是到此我們還有一個問題,就是預覽列印時會發現,並不是一個班一張報表,因為每班人數不同很可能一張報表有兩個班級的資料,如何才能呈現一個班一頁報表?這個目的必須透過選擇"區段"中"群組尾1"(即班級群組)的屬性中"強迫跳頁"這個屬性,設定為"在區段後",則報表會以班級群組進行強迫跳頁,這樣列印時即可一班一頁的列印出來。

image

 

接下來我們要進一步修改我們的報表,這樣的列表可能會讓學生不舒服,因為同樣的等級學生都集中在一起,所以我們要把報表進行重新設計,還是要統計出每班各級別的人數,但是學生資料要依照座號排序,要如何做?

下回分解!