รายงานเป็นหน้าต่างของผลงาน
วัตถุประสงค์
นำเสนอโครงสร้างของรายงาน รู้จัก EVENT ของรายงาน และการเรียกใช้รายงานด้วย VBA
เนื้อหา
ส่วนของ Report ใน Access เป็นองค์ประกอบที่ทำให้งานที่ผมทำออกมา ดูมีราคาขึ้นมากเลยครับ เพราะ Access เอื้อให้เราสร้างรายงานได้ง่าย สวยงาม ซึ่งหากมาวิเคราะห์กันจริงๆ ก็มีเครื่องมือในการออกแบบรายงานแบบนี้มานานแล้ว ตั้งแต่ Access Version 2 โดยทั่วไปการทำรายงานใน Access จะคล้ายกับ Crystal report แต่ความยืดหยุ่นจะสูงกว่า
เชื่อว่ากว่าจะมาถึงตรงนี้ หลายๆ ท่านคงได้เคยใช้ Access ในการออกรายงานมาบ้างแล้ว บทนี้จะมีส่วนที่เกี่ยวกับการเขียนโปรแกรมไม่มาก แต่ผมอยากเล่าประสพการณ์ และ tip ในการออกรายงานด้วย Access เป็นหลักครับ
หาข้อมูลมากออกรายงานให้ได้
ก่อนที่จะทำการทำรายงานสักรายงาน เราต้องทราบให้ได้ว่าข้อมูลที่จะมาแสดงในรายงานของเรา จะมีลักษณะ Record เป็นอย่างไร ใช้ Query โดยการหยิบ Field จาก Table ใดบ้าง เราจะกำหนดไว้ใน Record Source ของรายงาน
สมมุติโจทย์ว่าเราต้องการรายงานที่แสดงยอดขายในแต่ละเดือน และ ปี แยกรายการสินค้า โดยใช้ Table ของ Northwind นะครับ รายการขายของ Northwind จะอยู่ใน Table Orders และ Order Detail
รายงานและข้อมูลดิบที่ต้องการนำมาแสดง
สิ่งที่ยากก็คือเราต้องแยกให้ออกว่า ข้อมูลส่วนใดคือข้อมูลดิบจริง มีหลักง่ายๆ ว่า
· รายการดิบที่จะเป็นต้น ทางนั้นจะเป็นรายการต่อ 1 บรรทัดในรายงาน ที่เรียกว่า Detail เราต้องดูที่ Detail เป็นหลักเลยครับ ว่าจะมี Field อะไรบ้าง เช่นกรณีนี้ต้องการให้มี Product id, Product name และ รวมยอดขาย
· การสรุปรวม “ต่ออะไรบ้าง “ การสรุปรวมแยกแต่ละส่วนในรายงานปลายทาง ก็จะเป็น Field ที่เราต้องหยิบมาเป็นข้อมูลดิบในการออกรายงานด้วย กรณีนี้เรากำลังสรุปแยก ปี และ เดือน ด้วย ปีและเดือนจึงต้องกลายเป็นส่วนของ record
· ในส่วนที่เป็น Detail ในบางครั้งรายการที่แสดง ไม่ใช้เป็นรายการดิบๆ แต่มีการรวมตัวเลข นั้นหมายความว่าช่องตัวเลขนั้น ต้องมาจากการใช้ Query ที่เป็น Group By ช่องใดที่เกิดจากการรวมตัวเลข เราก็จะใช้ Sum ที่ Field นั้นๆ กรณีนี้ รายการย่อยๆ มีการ Sum ที่ ยอดขาย เราจึงทำQuery เป็น Group by แล้ว Sum ที่ Field นี้
ให้ลองสร้างรายงานใหม่ขึ้นมา 1 รายงาน แล้วกำหนด Query ที่ใช้ในรายงานดูนะครับ
SELECT Year([ShippedDate]) AS YearGroup,
Month([ShippedDate]) AS MonthGroup,
Sum([Order Details].Quantity) AS SubQuantity,
Products.ProductID,
Products.ProductName
FROM Products INNER JOIN
(Orders INNER JOIN [Order Details] ON Orders.OrderID = [Order Details].OrderID) ON Products.ProductID = [Order Details].ProductID
WHERE (((Orders.ShippedDate) Is Not Null))
GROUP BY Year([ShippedDate]), Month([ShippedDate]), Products.ProductID,
Products.ProductName;
รวมตัวเลขตาม Group
การสรุปรวมตัวเลขในแต่ละปี หรือ เดือน ก็เพียงแต่สั่งให้ Access สร้าง บริเวณ Group Header / Footer เพิ่มขึ้นมาครับ เราก็จะนำ Field ไปวาง แต่เราจะใส่ Function แทนการใส่ Field ธรรมดา เช่นใส่ว่า =Sum([SubQuantity]) ตาม ตยนี้เป็นต้น ( subQuantity เป็นชื่อ Field ที่ผม Sum Quantity มาจาก Query ไว้แล้ว )
รู้จัก REPORT EVENT
ภายในตัว Report มี Event ที่เราสามารถนำมาทำงานอะไรได้เยอะครับ แต่เราเข้าไปเกี่ยวข้องกับการเขียน code บน Report น้อยกว่า Form มาก ด้วยหลายๆ เหตุผลครับ หลักๆ คือรายงานควรทำง่ายแก้ง่าย หากผูก code ไว้มากมาก จะทำให้ดูแลโปรแกรมได้ยาก
อย่างไรก็ดี เราควรรู้จักกับ Event ของ Report ที่สามารถช่วยให้เราสามารถ ออก Report ที่ซับซ้อนได้มากขึ้น เรามีแต่ Event บนตัว Report เท่านั้นนะครับ ไม่มี Event บน Control ที่อยู่บน Report เนื่องจาก เราไม่มีการ บันทึกข้อมูลใน Report จึงไม่มี Event ของ Control บน Report เหมือนกับ Form
เราแบ่ง Event ได้เป็น กลุ่มใหญ่ ๆ คือ
· Event ของตัว Report
· Event เมื่อมีการประมวผล หรือพิมพ์รายการในส่วน Detail
· Event เมื่อมีการทำ Page Header/Footer เมื่อมีการกำหนดให้มี Page Header /Footer
· Event เมื่อมีการทำ Report Header/Footer เมื่อมีการกำหนดให้มี Report Header /Footer
· Event เมื่อมีการทำ Group Header / Footer สำหรับกรณี Report ที่มี Group Header / Footer โดยแยกเป็น Event ของ Group แต่ละตัว
Event ของ Report
คล้ายกับชุด Event ของ Form มี Open, Close, Activate, Deactivate, Error เช่นกัน มีกลไกการทำงานแบบเดียวกับ Form ทุกประการ มี Event ที่แตกต่างคือ
§ NODATA EVENT
Event ตัวนี้เกิดขึ้นเมื่อ Report ตัวนั้น ไม่มีข้อมูลที่จะนำมาแสดง หรือ RecordSource ที่กำหนดไว้ ให้จำนวน Record เป็น 0 เราสามารถ Cancel การ Open Report ตัวนี้ได้โดยการระบุ Cancel = True , Event ตัวนี้มีประโยชน์มาก เพราะ ช่วยให้การพิมพ์ Report ที่ไม่มีข้อมูล ไม่ให้แสดงข้อความว่า Error บนหน้ากระดาษ โดยการยกเลิก Report เสียก่อน
Private Sub Report_NoData(Cancel As Integer)
MsgBox ("ไม่พบข้อมูลในรายงาน")
Cancel = True
End Sub
Event ของ Detail
ทุกทุก บรรทัดของข้อมูลที่มีการแสดงบน Detail มี Event กลุ่มนี้เกิดขึ้นเสมอ เราสามารถทำการพิเศษๆ บน Event เหล่านี้ได้ ส่วนใหญ่เราใช้ Event Format ส่วน อีก 2 ตัวเราใช้น้อยดังต่อไปนี้
§ FORMAT EVENT
เกิดขึ้นเมื่อ Access ดึงข้อมูลมาวางบน Report และเริ่มจัดรูปแบบเพื่อเตรียมพิมพ์ ใน EVENT นี้เราสามารถอ่านค่าของ Control มาทำการคำนวณพิเศษๆ ได้ ในทุกๆ บรรทัดของข้อมูลที่มีการอ่านมาแสดงผล
สมมุติว่าเราไม่ใส่ pages ( จำนวนหน้า ) ลงใน Control , Access จะไม่อ่านข้อมูลจนหมด เพื่อทดสอบว่า รายงานของเราต้องใช้กี่หน้า แต่ถ้าเรากำหนด ก็จะเกิด Event นี้เท่ากับจำนวน Record ที่มีทันทีเพื่อ Access จะได้ทราบว่าต้องใช้กระดาษกี่หน้า และหากมีการสั่งพิมพ์อีกครั้งหนึ่ง Event เหล่านี้จะเกิดซ้ำ ดังนั้นการทำการบน Event จะต้องคำนึงถึงการกลับมาเรียกซ้ำได้ ของ Event นี้
นอกจากนี้ การตั้งให้ Object CanGrow, CanShrink ก็เป็นส่วนที่ทำให้ มีการทำการ Format ซ้ำได้ เพราะ Access ต้องทดสอบจำนวนหน้าโดยการวาง Control ลงบนหน้ากระดาษ หากล้นหน้า ก็ต้องขึ้นหน้าใหม่ แล้วทดลองวาง Control ใหม่ ตรงนี้จะเกิด Event ซ้าซ้อน ซึ่งต่างจาก Case แรก เพราะ Event ที่ซ้ำจะเกิดติดๆกันในระหว่าง 1 Control เราสามารถตรวจสอบการเรียกซ้ำ จากค่า FormatCount ถ้า FormatCount > 1 แสดงว่าเป็นการเกิด Event Format ซ้ำซ้อน ไม่ได้เกิดในครั้งแรก เป็นต้น
ในแต่ละ Detail เราสามารถสั่งให้มีการยกเลิกรายการบน Report ได้โดยใช้คำสั่ง Cancel = True เช่นกัน วิธีนี้ทำให้รายการที่ถูก Cancel หายไปจาก รายงาน ตัวอย่าง Code ชุดนี้เป็นการบังคับให้แสดงเฉพาะรายชื่อ Supplier ที่มีรหัสเป็นเลขคู่ ทดลองสร้าง รายงานที่มี Supplier เป็น Recordsource และบรรจุ Code ชุดนี้เข้าไป
Private Sub Detail_Format(Cancel As Integer, FormatCount As Integer)
If (Me.SupplierID And 1) > 0 Then
Cancel = True
End If
End Sub
§ RETREAT EVENT
Event นี้ไม่เหมือนกับ Format เป็น Event ที่อาจเกิดมากกว่า 1 ครั้งได้เหมือนกัน Event นี้คล้ายกับการทำ Format ซ้ำ แต่เกิดขึ้นระหว่าง Section ของ Report เช่นระหว่างที่ทำการ Format Detail อยู่ แล้วต้องขึ้นหน้าใหม่ เพราะหน้ากระดาษไม่พอ แต่มีการกำหนดให้ Group Header ขึ้นทุกหน้า จึงเกิดการ Format ส่วน Group header ตรงนี้จะเกิด Event Retreat บน GroupHeader แล้วเมื่อทำการ Format เสร็จ ก็จะกลับมาที่ Detail อีกครั้ง แล้วเริ่มทำการพิมพ์ข้อมูลในส่วน Detail ต่อไป ตรงนี้จะเกิด Event Retreat บน Detail อีกครั้งหนึ่ง ในขณะกลับมาจาก GroupHeader, Event นี้คล้ายกับการ GotFocus ของ Form นั่นเอง ไม่ค่อยได้มีโอกาสได้ใช้เท่าไร
§ PRINT EVENT
เมื่อทำการพิมพ์ข้อมูลลงบน Report เกิดหลังจากทำการ Format เสร็จสมบูรณ์แล้ว ดังนั้นเราจะตรวจสอบที่ Format ก่อน แล้วหากเราไม่ทำการ Cancel , ก็จะเกิด Event นี้ขึ้น เราอาจทำการตรวจสอบอีกครั้งที่ Event นี้ก็ได้ แต่ในทางปฏิบัติ ก็ใช้ Format Event แทนกันได้ ใน Event นี้ก็มี Format Count ให้ตรวจสอบการเรียก Event นี้ซ้ำเช่นกัน และสามารถ Cancel ได้เหมือน Format ทุกประการ
EVENT ของ Page / Report / Group
เหมือนกับ Event ของ Detail แต่จังหวะในการเกิด Event ไม่เหมือนกัน แตกต่างกันไปตามประเภทของ Group by ที่เราสร้างขึ้นมา แต่ทุกตัวก็จะแบ่งได้เป็น 2 ชุดหลักๆ คือ เป็น Header กับ Footer
EVENT | คำอธิบาย |
ReportHeader_Format | เมื่อมีการเริ่มทำการ Format หัว Report เกิดขึ้นหนึ่งครั้งเท่านั้น เรามักใช้ในการ Initial ค่าตัวแปรเริ่ม |
ReportFooter_Format | เมื่อมีการเริ่มทำการ Format ท้าย Report ใช้สำหรับการคำนวณปิดท้าย |
PageHeader_Format | เมื่อมีการเริ่มทำการ Format หัวกระดาษ |
PageFooter_Format | เมื่อมีการเริ่มทำการ Format ท้ายกระดาษ |
GroupnHeader_Format | เมื่อมีการเริ่มทำการ Format Group ที่ n ที่หัวกระดาษ สำหรับ Set ค่าตัวแปรในแต่ละหมวด |
GroupnFooter_Format | เมื่อมีการเริ่มทำการ Format Group ที่ n ที่ท้ายกระดาษ ใช้สำหรับการคำนวณปิดท้ายของแต่ละ Group |
Record Source สำคัญมากมาก
เพราะ Record Source ที่เรานำมาใช้แสดงรายงาน เป็นฐานข้อมูลที่จะปรากฎในรายงาน ความช้าเร็ว ของรายงาน ส่วนหนึ่งก็ขึ้นกับว่าเราทำ Record Source ที่มาแสดงในรายงานไว้ดีหรือไม่ เราควร Join table อย่างไร จึงทำให้รายงานเร็ว ? อันต้องขึ้นกับการออบแบบฐานข้อมูลไว้ในตอนแรก ประกอบกับ การเข้าความสัมพันธ์ของ Table ว่า Link กันอย่างไร หาก link ผิดไม่เป็นตามโครงสร้าง ก็จะช้ามากมาก
เปลี่ยน Record source ของ Report
ทำได้ไม่ยาก ปกติเราจะเขียนโปรแกรมไว้ที่ Event ของ Report_Open เพื่อใช้เปลี่ยน Record Source ลองแก้ไข ตย ที่ผ่านมาครับ แล้วใส่ code ชุดนี้ครับ สามารถเลือกปีได้ครับ เพื่อนำไป where ข้อมูลที่จะออกรายงาน
Option Compare Database
Private Sub Report_Open(Cancel As Integer)
Dim x
Dim sqlx
x = InputBox("Enter year, blank for all")
If x = "" Then
sqlx = " SELECT Year([ShippedDate]) AS YearGroup, " & _
" Month([ShippedDate]) AS MonthGroup, " & _
" Sum([Order Details].Quantity) AS SubQuantity, " & _
" Products.ProductID, " & _
" Products.ProductName " & _
" FROM Products INNER JOIN " & _
" (Orders INNER JOIN [Order Details] ON Orders.OrderID = [Order Details].OrderID) “ & _
“ ON Products.ProductID = [Order Details].ProductID " & _
" WHERE(((Orders.ShippedDate) Is Not Null)) " & _
" GROUP BY Year([ShippedDate]), Month([ShippedDate]), Products.ProductID, " & _
" Products.ProductName; "
Else
sqlx = " SELECT Year([ShippedDate]) AS YearGroup, " & _
" Month([ShippedDate]) AS MonthGroup, " & _
" Sum([Order Details].Quantity) AS SubQuantity, " & _
" Products.ProductID, " & _
" Products.ProductName " & _
" FROM Products INNER JOIN " & _
" (Orders INNER JOIN [Order Details] ON Orders.OrderID = [Order Details].OrderID) “ & _
“ ON Products.ProductID = [Order Details].ProductID " & _
" WHERE(((Orders.ShippedDate) Is Not Null)) and Year([ShippedDate])= " & x & _
" GROUP BY Year([ShippedDate]), Month([ShippedDate]), Products.ProductID, " & _
" Products.ProductName; "
End If
Me.RecordSource = sqlx
End Sub
การเรียกรายงานด้วย VBA
คำสั่งง่ายๆ ครับในการเปิดรายงานคือ docmd.openreport ตามด้วยชื่อ Report ที่เป็น String เป็นคำสั่งธรรมดาครับ แต่ที่น่าสนใจคือ เราสั่งให้รายงานเปิดแบบมีเงื่อนไขได้ โดยการระบุที่ where
ผมจะทดลองทำ form มาเปิดรายงานชื่อ “Summary of Sales by Year” ซึ่งเป้นรายงานใน Northwind.mdb นะครับ ให้เลือกปีได้
1. สร้าง Form เปล่าๆ 1 Form
2. วาง Control ที่เป็น text box 2ตัว ตั้งชื่อว่า txtYearFrom กับ txtYearTo
3. สร้างปุ่ม สำหรับสั่งพิมพ์
4. วาง checkbox สำหรับ ให้เลือก Preview หรือ Print เลย ให้ชื่อว่า chkPrint
5. ใส่ Code ตามนี้เลยครับ
แสดงปุ่มสำหรับเปิดรายงาน และ code
จากโปรแกรมจะเห็นว่า เรามีการสร้าง textbox สำหรับใส่ ปีที่จะพิมพ์ แล้วเราก็นำปีที่ได้มาสร้างเป็น ตัวแปลในขณะจะทำการพิมพ์รายงาน โดยการทำเป็นเงื่อนไขไว้ใน ตัวแปร WX แล้วส่งให้ docmd.openreport ไปเปิดต่ออีกทีครับ ส่วน "YEAR(ShippedDate) between " & Me.txtFromYear & " and " & Me.txtToYear เป็นการสร้าง เงื่อนไขว่า ปีของ Shippeddate ต้องอยู่ในช่วง ของค่าปีที่ใส่ใน textbox ชื่อ txtYearfrom กับ txtYearto
ค่า previewX เป็นตัวแปรที่เก็บว่า ตอนนี้ user เลือกที่จะ Preview หรือ จะพิมพ์ ตรงๆออกจากเครื่องพิมพ์ ค่า acViewPreview กับ acViewNormal เป็น ค่าคงที่เรียกใช้ได้จาก Access ครับ
ลองทำให้เลือกรายงานได้
ชื่อรายงานที่สั่งพิมพ์ ในตย ข้างต้น เป็น Summary of Sales by year ลองทำเป็นตัวแปรดูครับ แทนที่จะเป็นชื่อคงที่ ก็ให้ user เลือกชื่อรายงานแทน โดยการทำเป็น textbox ครับ ให้ User มาใว่ชื่อรายงานที่จะพิมพ์ ถ้าจะหรูกว่านี้ ก็ต้องสร้างตารางมาเก็บชื่อรายงานที่เข้าใจง่ายๆ กับ ชื่อรายงานใน Access เพื่อให้ user เรียกง่าย และแทนที่จะเป็น Textbox ก็เป็น combobox แทน ลองทำดูนะครับ
ไม่มีความคิดเห็น:
แสดงความคิดเห็น