Skip to content

5.3 Debugging Cross-Platform Mismatch

“ฝ่ายบัญชีบอก ยอดรวมเดือนนี้ ฿1,200,000 แต่ database ของเราบอก ฿1,100,000 — ต่างกัน 100,000 บาท ใครผิด?”

เวลาที่ใช้: ~12 นาที

ฝ่ายบัญชีใช้ Google Sheets สรุปยอดรายเดือน ฝ่าย IT เก็บข้อมูลเดียวกันใน PostgreSQL ทุกเดือนตัวเลขเท่ากัน — จนเดือนนี้:

แหล่งยอดรวม
Google Sheets (บัญชี)฿1,200,000.00
PostgreSQL (IT)฿1,100,000.00
ผลต่าง฿100,000.00

ปัญหาอยู่ที่ไหน?

  1. ตรวจ row count — ทั้งสองฝ่ายมี 50,000 แถว เท่ากัน ไม่ใช่ปัญหาแถวหาย

  2. ตรวจ data type ของ column amount

    ใน SQL:

    SELECT column_name, data_type
    FROM information_schema.columns
    WHERE table_name = 'transactions' AND column_name = 'amount';

    ผลลัพธ์: data_type = 'real'

    ปัญหาอยู่ตรงนี้! REAL = floating-point 4 bytes

  3. ดูว่า rounding error สะสมแค่ไหน

    -- เปรียบเทียบ REAL vs NUMERIC
    SELECT
    SUM(amount) AS real_sum,
    SUM(CAST(amount AS NUMERIC(10,2))) AS numeric_sum
    FROM transactions;

    REAL ทำให้ทุกแถวมี error เล็กๆ — แต่ 50,000 แถวรวมกัน error สะสมเป็น แสนบาท

ลองจำลองปัญหานี้ใน 4 เครื่องมือ:

24.99
// จำลอง: แต่ละแถวมี error เล็กๆ จาก float
// ลาก copy ลง 50,000 แถว (หรือใช้ SEQUENCE)
// ยอดที่ควรได้:
=24.99 * 50000 → 1,249,500.00
// แต่ถ้าเก็บเป็น float จริงๆ:
=TEXT(24.99, "0.00000000000000000") → 24.98999999999999900
// error ต่อแถว ≈ 0.00000000000000100
// 50,000 แถว × error เล็กๆ = error ใหญ่พอจับได้
ยอดต่างกัน 100,000 บาท Row count เท่ากัน ตรวจ data type Column amount เป็น REAL REAL = float 4 bytes ทุกแถวมี rounding error เล็กๆ 50,000 แถว x error = 100,000 บาท! แก้: ALTER COLUMN เป็น NUMERIC
  1. แก้ schema:

    ALTER TABLE transactions
    ALTER COLUMN amount TYPE NUMERIC(10,2);
  2. ตรวจยอดใหม่:

    SELECT SUM(amount) FROM transactions;
    -- ตอนนี้ตรงกับ Sheets แล้ว
  3. อัปเดต type contract ให้ระบุ NUMERIC(10,2) ไม่ใช่ REAL

  4. เพิ่ม check ใน import checklist ว่า columns ที่เก็บเงินต้องเป็น NUMERIC เสมอ

สิ่งที่ได้เรียนรู้รายละเอียด
Float ไม่ใช่สำหรับเงินREAL / float มี rounding error ที่สะสมได้
Error เล็กๆ รวมกันใหญ่0.000001 x 50,000 แถว = จับต้องได้
Type contract ป้องกันปัญหาถ้า contract ระบุ NUMERIC ตั้งแต่แรก ปัญหานี้ไม่เกิด
ตรวจ schema ก่อน debug logicส่วนใหญ่ bug ไม่ได้อยู่ที่ logic แต่อยู่ที่ data type
  1. สร้างตาราง 2 แบบใน SQL (REAL vs NUMERIC) แล้ว insert ข้อมูลเดียวกัน 10,000 แถว
  2. เปรียบเทียบ SUM() ของทั้งสองตาราง
  3. ลองเปลี่ยนจำนวนเงินต่อแถวเป็นค่าที่มี rounding error มากๆ (เช่น 19.99, 0.33) แล้วดูว่า difference เปลี่ยนไปอย่างไร
ดูเฉลย
CREATE TABLE test_real (amount REAL);
CREATE TABLE test_numeric (amount NUMERIC(10,2));
-- ใช้ 0.33 ซึ่งเป็น repeating decimal ใน binary
INSERT INTO test_real SELECT 0.33 FROM generate_series(1, 10000);
INSERT INTO test_numeric SELECT 0.33 FROM generate_series(1, 10000);
SELECT
(SELECT SUM(amount) FROM test_real) AS real_sum,
(SELECT SUM(amount) FROM test_numeric) AS numeric_sum;
-- real_sum ≈ 3299.999... (ผิด)
-- numeric_sum = 3300.00 (ถูก)

ยิ่งจำนวนเงินมี repeating decimal ใน binary มากเท่าไหร่ (0.1, 0.33, 0.99) error ก็ยิ่งมาก