ป้ายกำกับ: Recursive Cte

OpenXML SDK Case: การสร้างหัวกระดาษ ท้ายกระดาษ (Header & Footer) และเลขหน้า (Page Numbers)OpenXML SDK Case: การสร้างหัวกระดาษ ท้ายกระดาษ (Header & Footer) และเลขหน้า (Page Numbers)

ในโลกของการพัฒนาซอฟต์แวร์ที่ต้องมีการสร้างรายงาน (Reporting) หรือเอกสารที่มีโครงสร้างสูง การควบคุมองค์ประกอบด้าน Layout เช่น หัวกระดาษ ท้ายกระดาษ และเลขหน้า ให้ปรากฏอย่างถูกต้องและเป็นระบบ ถือเป็นความท้าทายทางสถาปัตยกรรมข้อมูลที่สำคัญยิ่ง แม้ว่า OpenXML SDK จะใช้ในการจัดการไฟล์ Word โดยตรง แต่ในมุมมองของ System Architect ที่ต้องสร้างแหล่งข้อมูล (Source of Truth) สำหรับเอกสารเหล่านั้น การเข้าใจหลักการทำงานเชิงโครงสร้างนี้ และจำลองมันด้วยพลังของฐานข้อมูลอย่าง PostgreSQL จึงเป็นสิ่งจำเป็น

แนวคิด: การจำลองโครงสร้างเอกสารในระดับ Database

โดยปกติแล้ว หัวกระดาษและท้ายกระดาษไม่ได้เป็นเพียงแค่ข้อความ แต่ถูกผูกติดกับ Section Properties ของเอกสาร ซึ่งหมายถึงการที่ข้อมูลต้องมีการทำซ้ำ (Repetition) และการนับลำดับ (Sequencing) ที่แม่นยำ การนำหลักการนี้มาใช้ใน PostgreSQL เราจะไม่สร้างไฟล์ Word โดยตรง แต่เราจะสร้าง ‘Metadata’ หรือ ‘Logic Flow’ ที่กำหนดว่าข้อมูลใดควรปรากฏเมื่อไหร่และอย่างไร

องค์ประกอบสำคัญที่ต้องจำลองด้วย SQL

  1. Sectioning Logic: การแบ่งเอกสารออกเป็นส่วนๆ (เช่น หน้าปก, บทนำ, เนื้อหาหลัก) ซึ่งใน PostgreSQL สามารถใช้ Common Table Expressions (CTEs) หรือการจัดกลุ่มข้อมูลตามลำดับเหตุการณ์ได้
  2. Sequential Numbering (Pagination): การสร้างเลขหน้าหรือหมายเลขบทที่ต้องเพิ่มขึ้นทีละ 1 อย่างต่อเนื่องและถูกต้อง แม้ว่าจะมีการข้ามส่วนใดส่วนส่วนหนึ่งไปก็ตาม ซึ่งเหมาะกับการใช้ฟังก์ชัน generate_series()
  3. Repeating Content (Header/Footer): การกำหนดชุดข้อมูลที่เป็นค่าคงที่หรือคำนวณจาก Context ปัจจุบัน เพื่อให้แสดงซ้ำในทุกๆ ‘หน้า’ หรือ ‘ส่วน’ ที่ถูกประมวลผล

การใช้ PostgreSQL ทำให้เราสามารถจัดการความสัมพันธ์เชิงลำดับ (Temporal and Sequential Relationship) ของข้อมูลเหล่านี้ได้อย่างมีประสิทธิภาพสูงกว่าการจัดเก็บเป็นเพียงตารางข้อความธรรมดา

Case Study: การสร้างเลขหน้าและส่วนหัวด้วย Recursive CTE

เราสามารถใช้เทคนิค Recursive Common Table Expression (CTE) เพื่อจำลองกระบวนการไหลของเอกสารที่ซับซ้อน ซึ่งรวมถึงการนับเลขหน้าและการกำหนดค่า Header/Footer ที่เปลี่ยนแปลงไปตาม Section ได้

SQL

WITH RECURSIVE DocumentFlow AS (
    -- Anchor Member: เริ่มต้นที่ส่วนแรก (Page 1)
    SELECT
        1::integer AS page_number,
        'Section A: Cover Page'::text AS section_name,
        'Document Start'::text AS header_content,
        NULL::jsonb AS footer_data, -- หน้าปกอาจไม่มีเลขหน้า
        1 AS start_page
    UNION ALL
    -- Recursive Member: สร้างหน้าถัดไป (Page 2, 3, ...) 
    SELECT
        df.page_number + 1,
        CASE WHEN df.page_number < 5 THEN 'Section A' ELSE 'Section B: Main Body' END,
        'Report Title - Page ' || (df.page_number + 1)::text,
        jsonb_build_object('date', CURRENT_DATE, 'chapter', 2),
        CASE WHEN df.page_number < 5 THEN 1 ELSE df.start_page END
    FROM DocumentFlow df
    WHERE df.page_number < 10 --จำลองถึงหน้า 10
)
SELECT * FROM DocumentFlow;

คำอธิบายโค้ด:

  • WITH RECURSIVE DocumentFlow AS (...): ใช้ CTE เพื่อสร้างชุดข้อมูลที่วนซ้ำ (Recursive) ซึ่งจำลองการไหลของหน้ากระดาษ
  • page_number: ถูกเพิ่มค่าขึ้นเรื่อยๆ ในแต่ละรอบของการทำซ้ำ ทำให้เกิดลำดับเลขหน้าที่ถูกต้อง
  • header_content: แสดงถึงเนื้อหาหัวกระดาษที่ถูกคำนวณแบบ Dynamic (เช่น ‘Report Title – Page X’) ซึ่งสามารถเปลี่ยนตามเงื่อนไขของ Section ได้
  • jsonb: ใช้เก็บข้อมูล Metadata ของส่วนท้ายกระดาษ (Footer) ที่มีความยืดหยุ่นสูง เช่น วันที่หรือหมายเลขบท

สรุปและข้อได้เปรียบเชิงสถาปัตยกรรม

การใช้ PostgreSQL ในลักษณะนี้ ไม่เพียงแต่ช่วยให้เราสร้างลำดับตัวเลข (Pagination) ได้อย่างแม่นยำ แต่ยังเป็นการพิสูจน์ว่าโครงสร้างเอกสารที่ซับซ้อนสามารถถูกควบคุมด้วย Logic ของฐานข้อมูลได้ทั้งหมด การเปลี่ยนจากการเขียนโค้ดเพื่อจัดการ Layout ไปสู่การกำหนด Rule-Based Data Flow ทำให้ระบบของเรามีความทนทาน (Robustness) และง่ายต่อการบำรุงรักษาในระยะยาว นี่คือหัวใจสำคัญของการเป็น Senior Architect ที่มองเห็นโครงสร้างของข้อมูลที่อยู่เบื้องหลังหน้าตาของแอปพลิเคชัน