บทนำเชิงสถาปัตยกรรม (Conceptual Narrative & The Matrix Pivot Problem):
ในรายงานธุรกิจทั่วไป เช่น ตารางรายการสั่งซื้อ ข้อมูลจะขยายตัวในแนวตั้ง (Vertical Stretch) ตามจำนวนแถวจากฐานข้อมูล คอลัมน์ทุกตัวจะคงที่ เช่น รหัส, ชื่อ, จำนวน, ราคา แต่ในงานวิเคราะห์เชิงลึกระดับผู้บริหาร เช่น "รายงานสรุปรายได้แยกตามแผนก (แนวตั้ง) และไตรมาส (แนวนอน)" คอลัมน์แนวนอนจะไม่ได้ถูกกำหนดไว้ตายตัวตั้งแต่ช่วงออกแบบ (Design Time) หากไตรมาสใหม่เกิดขึ้น คอลัมน์จะต้องงอกออกมาเองโดยอัตโนมัติ
หากใช้ SQL แบบดั้งเดิม คุณต้องเขียน CASE WHEN หรือคำสั่ง Pivot ที่ซับซ้อน และถ้ามี Quarter ใหม่เพิ่มเข้ามา คุณต้องแก้ทั้ง SQL และ JRXML! สถาปัตยกรรม Crosstab Matrix Engine ของ JasperReports ออกแบบมาเพื่อแก้ปัญหานี้โดยเฉพาะ: รับ Raw Data Stream แถวเดี่ยวเข้ามา แล้วทำการแตกแกน หมุนข้อมูลเป็นเมทริกซ์ 2 มิติ (X, Y) พร้อมคำนวณผลรวมทั้งแถวและคอลัมน์ให้อัตโนมัติในหน่วยความจำระดับ JVM
<bucket> โดยจัดเก็บ Index ในรูป Bucket Keys เพื่อจับคู่จุดตัดของแถวและคอลัมน์ได้อย่างรวดเร็ว
calculation="Sum", "Count", "Average") พร้อมทั้งสร้างแถว/คอลัมน์ Total อัตโนมัติ
CrosstabBucketingService เพื่อสร้างโครงสร้างเมทริกซ์จนครบทั้งผืนก่อน (Pass 1) แล้วจึงคำนวณขนาดและวาดลงหน้ากระดาษ (Pass 2)
columnBreakOffset
การทำงานของ Crosstab ภายใน JRVerticalFiller มีความแตกต่างจากการ Fill แบนด์ปกติอย่างสิ้นเชิง โดยมี Flow การทำงานดังนี้:
วงจรการประมวลผลภายใน JVM (Crosstab Execution Flow):
[Row_i, Col_j]columnBreakOffsetความแตกต่างระหว่าง Table Element และ Crosstab:
| คุณสมบัติ | Table Element | Crosstab Element |
|---|---|---|
| โครงสร้างคอลัมน์ | คงที่ (Static Design Time) | ไดนามิกตามข้อมูล (Runtime Driven) |
| การบริโภค Memory | ต่ำ (Streaming ประมวลผลทีละแถว) | สูง (ต้อง Buffer Matrix ไว้ทั้งผืน) |
| การขยายตัว | แนวตั้งอย่างเดียว (Vertical) | สองมิติพร้อมกัน (Horizontal & Vertical) |
ในสถาปัตยกรรม JasperReports 7.0.x การประกาศ Crosstab ได้ถูกปรับปรุงให้อยู่ภายใต้ Unified Element Model โดยใช้แท็ก <element kind="crosstab">:
<!-- Crosstab Declaration (7.0.x Unified Model) -->
<element kind="crosstab" x="0" y="20" width="555" height="200">
<!-- 1. Row Group: แกน Y (แตกแถวตามภูมิภาค) -->
<rowGroup name="RegionGroup" width="120" totalPosition="End">
<bucket class="java.lang.String">
<expression><![CDATA[$F{region}]]></expression>
</bucket>
<crosstabRowHeader>
<cellContents>
<element kind="textField" x="0" y="0" width="120" height="20">
<expression><![CDATA[$V{RegionGroup}]]></expression>
</element>
</cellContents>
</crosstabRowHeader>
</rowGroup>
<!-- 2. Column Group: แกน X (แตกคอลัมน์ตามไตรมาส) -->
<columnGroup name="QuarterGroup" height="20" totalPosition="End">
<bucket class="java.lang.String">
<expression><![CDATA[$F{quarter}]]></expression>
</bucket>
<crosstabColumnHeader>
<cellContents>
<element kind="textField" x="0" y="0" width="80" height="20">
<expression><![CDATA[$V{QuarterGroup}]]></expression>
</element>
</cellContents>
</crosstabColumnHeader>
</columnGroup>
<!-- 3. Measure: ตัวเลขผลรวมตรงจุดตัด -->
<measure name="TotalRevenue" class="java.math.BigDecimal" calculation="Sum">
<expression><![CDATA[$F{revenue}]]></expression>
</measure>
</element>
ข้อกำหนดทางเทคนิค (Full-Coverage 100%):
1. ใช้แท็กสมัยใหม่ <element kind="crosstab"> ตามมาตรฐาน 7.0.x ห้ามใช้แท็กโบราณ
2. กำหนด <rowGroup name="RegionGroup"> พร้อม totalPosition="End" เพื่อสร้างแถวสรุปผลรวมด้านท้าย
3. กำหนด <columnGroup name="QuarterGroup"> เพื่อแตกคอลัมน์ไตรมาสตามแกนแนวนอน
4. ประกาศ <measure name="TotalAmount" calculation="Sum"> ชนิด `java.math.BigDecimal` เพื่อรวมยอดเงิน
ข้อกำหนดทางเทคนิค (Full-Coverage 100%):
1. ประกาศ Measure 2 ตัวใน Crosstab เดียวกัน: TotalQty (Integer) และ TotalRevenue (BigDecimal)
2. กำหนด columnBreakOffset="15" เพื่อควบคุมระยะตัดหน้าเมื่อคอลัมน์ล้นแนวนอน
3. ฝัง <element kind="chart" chartType="bar"> ใน Summary Band เพื่อแสดงการเปรียบเทียบภาพรวม
4. ผูก <categoryDataset> ให้สอดคล้องกับแกนข้อมูลของ Crosstab
แบบทดสอบ 15 ข้อเชิงลึก วัดความเข้าใจเรื่อง Crosstab Engine, Multi-dimensional Bucketing และ Memory Virtualization: