สถานการณ์
คอลัมน์ VARCHAR เก็บตัวเลขคั่นด้วย Comma เช่น '20,10,40,50,60' ต้องการคำนวณผลรวม (Sum) โดยไม่ต้อง Normalize ตาราง
วิธีแก้: สร้าง Stored Function
ใช้ Loop วนลูปตัดค่าทีละตัวด้วย SUBSTRING_INDEX และ SUBSTRING แล้วสะสมผลรวม
ตัวอย่างการสร้าง Function
1. สร้างตาราง
mysql> CREATE TABLE DemoTable (
-> ListOfValues VARCHAR(50)
-> );
Query OK, 0 rows affected (0.56 sec)
2. เพิ่มข้อมูล
mysql> INSERT INTO DemoTable VALUES('20,10,40,50,60');
Query OK, 1 row affected (0.14 sec)
3. สร้าง Function
mysql> DELIMITER ??
mysql> CREATE FUNCTION totalSumInCommaSeparatedString(input VARCHAR(50))
-> RETURNS INT
-> DETERMINISTIC
-> NO SQL
-> BEGIN
-> DECLARE totalSum INT DEFAULT 0;
-> WHILE INSTR(input, ',') > 0 DO
-> SET totalSum = totalSum + SUBSTRING_INDEX(input, ',', 1);
-> SET input = SUBSTRING(input, INSTR(input, ',') + 1);
-> END WHILE;
-> RETURN totalSum + input;
-> END ??
Query OK, 0 rows affected (0.17 sec)
mysql> DELIMITER ;
4. ใช้งาน Function
mysql> SELECT totalSumInCommaSeparatedString(ListOfValues) AS TotalSum FROM DemoTable;
ผลลัพธ์:
+----------+ | TotalSum | +----------+ | 180 | +----------+ 1 row in set (0.00 sec)
อธิบาย Logic ของ Function
INSTR(input, ',') > 0- ตรวจสอบว่ายังมี Comma อยู่ไหมSUBSTRING_INDEX(input, ',', 1)- ดึงค่าตัวแรก (ก่อน Comma แรก)SUBSTRING(input, INSTR(input, ',') + 1)- ตัดทิ้งค่าตัวแรกและ Comma แรก- วนลูปจนไม่มี Comma เหลือ → บวกค่าสุดท้ายเข้าไป
เวอร์ชันที่รองรับทศนิยม (DECIMAL)
CREATE FUNCTION sum_csv_decimal(input VARCHAR(1000))
RETURNS DECIMAL(15,2)
DETERMINISTIC
NO SQL
BEGIN
DECLARE total DECIMAL(15,2) DEFAULT 0;
DECLARE val VARCHAR(50);
WHILE INSTR(input, ',') > 0 DO
SET val = SUBSTRING_INDEX(input, ',', 1);
SET total = total + CAST(val AS DECIMAL(15,2));
SET input = SUBSTRING(input, INSTR(input, ',') + 1);
END WHILE;
RETURN total + CAST(input AS DECIMAL(15,2));
END
ข้อจำกัดและข้อควรระวัง
- Function เรียกทุกแถว → ช้าบนข้อมูลเยอะ (Row-by-row processing)
- ไม่รองรับ Negative numbers ที่มีเครื่องหมายลบ (ต้องปรับ Logic)
- ไม่รองรับ Space รอบ Comma (ต้อง TRIM หรือ REPLACE ก่อน)
- Max recursion depth / Loop iterations อาจจำกัดได้
แนวทางที่ดีกว่า (MySQL 8.0+)
ใช้ Recursive CTE
WITH RECURSIVE cte AS (
SELECT
ListOfValues,
CAST(SUBSTRING_INDEX(ListOfValues, ',', 1) AS DECIMAL(10,2)) AS val,
SUBSTRING(ListOfValues, INSTR(ListOfValues, ',') + 1) AS rest
FROM DemoTable
UNION ALL
SELECT
rest,
CAST(SUBSTRING_INDEX(rest, ',', 1) AS DECIMAL(10,2)),
SUBSTRING(rest, INSTR(rest, ',') + 1)
FROM cte
WHERE INSTR(rest, ',') > 0
)
SELECT SUM(val) FROM cte;
Best Practice
- Normalize ข้อมูล: แยกเป็นตารางความสัมพันธ์ (One-to-Many)
- ใช้ JSON Array (MySQL 5.7+) แทน CSV String
- ใช้ Generated Column คำนวณ Sum ไว้ล่วงหน้า