หลังจากที่เราได้เรียนรู้วิธีการใช้ฟังก์ชัน initializeSheets() ในการเตรียมหน้าแท็บฐานข้อมูลให้พร้อมใช้งานไปแล้วในบทความก่อนหน้านี้ ตอนนี้ระบบของเรามีหน้าตาโครงสร้างตารางที่สวยงามและพร้อมรับแรงกระแทกจากข้อมูลแล้วครับ
แต่คำถามคือ "แล้วเราจะเอาข้อมูลจากหน้าเว็บส่งเข้าไปบันทึกได้อย่างไร? และเมื่อบันทึกไปแล้ว จะดึงข้อมูลเฉพาะเจาะจงกลับออกมาแสดงผลได้อย่างไร?"
ยินดีต้อนรับเข้าสู่บทความตอนที่ 2 ในหมวด DATA ROUTING & CRUD ซึ่งเป็นหัวใจสำคัญของการสร้างแอปพลิเคชันทุกประเภทในโลกใบนี้ครับ!
1. ทำความรู้จักคำว่า "DATA ROUTING" และ "CRUD"
สำหรับมือใหม่ สองคำนี้อาจจะฟังดูเหมือนศัพท์เทคนิคขั้นสูง แต่ถ้าเราแกะความหมายออกมาจริงๆ มันคือเรื่องที่เข้าใจง่ายมากๆ ครับ
Data Routing คืออะไร?
คำว่า Routing (เราติ้ง) แปลว่า "การเลือกเส้นทาง" ดังนั้นในทางซอฟต์แวร์ Data Routing คือ การเขียนโค้ดเพื่อควบคุมทิศทางของข้อมูลที่วิ่งเข้ามา เช่น เมื่อหน้าบ้านส่งข้อมูลมา โค้ดส่วนนี้จะทำหน้าที่คิดว่า "ข้อมูลนี้เป็นข้อมูลของระบบไหน? อ๋อ... เป็นข้อมูลเข้างานของพนักงาน งั้นต้องวิ่งไปส่งให้ฟังก์ชันบันทึกเวลาทำงานนะ อย่าไปส่งผิดแท็บ" มันเหมือนเป็นบุรุษไปรษณีย์ที่คอยคัดแยกจดหมายให้ไปถูกบ้านนั่นเอง

CRUD คืออะไร?
คำนี้เป็นตัวย่อของ 4 ฟังก์ชันพื้นฐานที่ระบบฐานข้อมูลทั่วโลกต้องมี:
C - Create: การสร้างหรือบันทึกข้อมูลใหม่ลงตาราง
R - Read: การอ่านหรือดึงข้อมูลจากตารางมาแสดงผล (เช่น การค้นหาประวัติ)
U - Update: การแก้ไขข้อมูลเดิมที่มีอยู่แล้วให้เป็นค่าใหม่
D - Delete: การลบข้อมูลที่ไม่ต้องการออกจากระบบ
ในบทความนี้ เราจะโฟลว์ไปที่ 2 ตัวหลักที่สำคัญที่สุดสำหรับมือใหม่ก่อน นั่นคือ Create (บันทึกข้อมูล) และ Read (ดึงข้อมูลด้วยเงื่อนไขเฉพาะเจาะจง) ครับ
2. ทำไมเราต้องเขียนโค้ดแยกส่วน CRUD ออกจากกัน?
โปรแกรมเมอร์มือใหม่หลายคนมักจะเขียนโค้ดทุกอย่างรวมกันเป็นก้อนเดียวขนาดยักษ์ (ที่เรียกว่า Spaghetti Code) เช่น เขียนโค้ดเช็คหน้าชีท บันทึกข้อมูล และจัดฟอร์แมตในฟังก์ชันเดียวกันทั้งหมด
การทำแบบนั้นจะทำให้เกิดปัญหาใหญ่ตามมา:
หาจุดพังยาก: เวลาที่ระบบบันทึกข้อมูลไม่ได้ คุณต้องไล่โค้ดตั้งแต่บรรทัดแรกจนถึงบรรทัดสุดท้ายเพื่อหาจุดผิดพลาด
โค้ดซ้ำซ้อน: ถ้าคุณมีหน้าเว็บ 3 หน้าที่ต้องการบันทึกประวัติการใช้งานเหมือนกัน คุณจะต้องเขียนโค้ดบันทึกซ้ำๆ กันทั้ง 3 จุด
การสร้างระบบ Data Routing เพื่อส่งงานต่อไปยังฟังก์ชัน CRUD ที่แยกกันอย่างเป็นสัดส่วน จึงช่วยให้โค้ดของคุณสะอาด ปลอดภัย และหากต้องการแก้ไขฟังก์ชันบันทึกในอนาคต คุณก็แค่มาแก้ที่จุดเดียว โดยไม่กระทบกับระบบค้นหาข้อมูลเลยครับ
3. ส่องโค้ดตัวอย่าง: ระบบจัดการข้อมูลพื้นฐาน
นี่คือตัวอย่างการเขียนโค้ดระบบ Data Routing และฟังก์ชัน CRUD สำหรับบันทึกข้อมูลผู้ใช้งาน และการค้นหาข้อมูลด้วยเงื่อนไข (เช่น ค้นหาประวัติด้วยเบอร์โทรศัพท์) ซึ่งถูกออกแบบมาให้เชื่อมต่อกับหน้าชีทที่เราเตรียมไว้ครับ
/**
* หมวดหมู่: DATA ROUTING
* ฟังก์ชัน: routeDataAction
* วัตถุประสงค์: ทำหน้าที่เป็นตัวคัดแยกและส่งต่อคำสั่งจากหน้าเว็บไปยังฟังก์ชัน CRUD ที่ถูกต้อง
*/
function routeDataAction(request) {
var action = request.action; // รับคำสั่งว่าต้องการทำอะไร เช่น 'save' หรือ 'search'
var payload = request.data; // รับก้อนข้อมูลที่ส่งมา
switch(action) {
case "create_user":
return createUserRow(payload); // ส่งต่อไปฟังก์ชันบันทึกข้อมูล
case "get_user_by_phone":
return readUserByPhone(payload.phone); // ส่งต่อไปฟังก์ชันค้นหาด้วยเบอร์โทร
default:
return { status: "error", message: "ไม่พบคำสั่งที่ระบุในระบบ" };
}
}
/**
* หมวดหมู่: CRUD - CREATE
* ฟังก์ชัน: createUserRow
* วัตถุประสงค์: บันทึกรายชื่อและข้อมูลผู้ใช้งานใหม่ลงใน Google Sheets
*/
function createUserRow(data) {
try {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName("User_Records"); // วิ่งไปหาแท็บที่เก็บข้อมูลผู้ใช้
// ตรวจสอบความปลอดภัย: หากหน้าชีทโดนลบ ให้ระบบแจ้งเตือนแทนการพัง
if (!sheet) {
return { status: "error", message: "ไม่พบหน้าชีทระบบ กรุณารันตั้งค่าเริ่มต้นก่อน" };
}
// สร้างแถวข้อมูลใหม่ตามลำดับคอลัมน์ [ID, ชื่อ, เบอร์โทรศัพท์, วันที่สมัคร]
var newRow = [
"UID-" + new Date().getTime(), // สร้าง ID แบบสุ่มจากเวลาปัจจุบัน
data.fullName,
data.phoneNumber,
new Date()
];
// ต่อท้ายแถวข้อมูลใหม่ลงในตาราง
sheet.appendRow(newRow);
return { status: "success", message: "บันทึกข้อมูลผู้ใช้งานเรียบร้อยแล้ว!" };
} catch(error) {
return { status: "error", message: "เกิดข้อผิดพลาดในการบันทึก: " + error.toString() };
}
}
/**
* หมวดหมู่: CRUD - READ
* ฟังก์ชัน: readUserByPhone
* วัตถุประสงค์: ค้นหาข้อมูลผู้ใช้งานจากเบอร์โทรศัพท์ที่ระบุ
*/
function readUserByPhone(phoneToFind) {
try {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName("User_Records");
if (!sheet) return { status: "error", message: "ไม่พบหน้าชีทระบบ" };
// ดึงข้อมูลทั้งหมดในชีทออกมา (ยกเว้นแถวแรกที่เป็นหัวตาราง)
var lastRow = sheet.getLastRow();
if (lastRow <= 1) {
return { status: "empty", message: "ยังไม่มีข้อมูลผู้ใช้งานในระบบ" };
}
// ดึงข้อมูลทั้งหมดตั้งแต่แถวที่ 2 คอลัมน์ที่ 1 ถึงแถวสุดท้าย
var dataRange = sheet.getRange(2, 1, lastRow - 1, sheet.getLastColumn());
var allRows = dataRange.getValues();
// วนลูปเพื่อค้นหาข้อมูล (คอลัมน์ที่ 3 หรือ Index 2 คือเบอร์โทรศัพท์)
for (var i = 0; i < allRows.length; i++) {
var currentRow = allRows[i];
var userPhone = currentRow[2].toString().trim();
if (userPhone === phoneToFind.toString().trim()) {
// ถ้าเจอข้อมูลที่ตรงกัน ให้ส่งก้อนข้อมูลกลับออกไปทันที
return {
status: "found",
data: {
userId: currentRow[0],
fullName: currentRow[1],
phoneNumber: currentRow[2],
registerDate: currentRow[3]
}
};
}
}
// หากวนลูปจนจบแล้วยังไม่เจอเบอร์โทรที่ระบุ
return { status: "not_found", message: "ไม่พบข้อมูลผู้ใช้งานที่ผูกกับเบอร์โทรศัพท์นี้" };
} catch(error) {
return { status: "error", message: "เกิดข้อผิดพลาดในการค้นหา: " + error.toString() };
}
}
1. Data Routing — ตัวคัดแยกคำสั่ง หน้าที่ของฟังก์ชันนี้คือ:- รับคำสั่งจาก Frontend
- ตรวจสอบประเภทคำสั่ง
- ส่งต่อไปยังฟังก์ชันที่ถูกต้อง
function routeDataAction(request) {
var action = request.action;
var payload = request.data;
switch(action) {
case "create_user":
return createUserRow(payload);
case "get_user_by_phone":
return readUserByPhone(payload.phone);
default:
return {
status: "error",
message: "ไม่พบคำสั่งที่ระบุ"
};
}
}
function routeDataAction(request) {
var action = request.action;
var payload = request.data;
switch(action) {
case "create_user":
return createUserRow(payload);
case "get_user_by_phone":
return readUserByPhone(payload.phone);
default:
return {
status: "error",
message: "ไม่พบคำสั่งที่ระบุ"
};
}
}2. CRUD — CREATE (บันทึกข้อมูล)
ฟังก์ชันนี้ใช้สำหรับ:-
รับข้อมูลผู้ใช้ใหม่
-
สร้าง ID
-
บันทึกลง Google Sheets
function createUserRow(data) {
try {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName("User_Records");
if (!sheet) {
return {
status: "error",
message: "ไม่พบหน้าชีทระบบ"
};
}
var newRow = [
"UID-" + new Date().getTime(),
data.fullName,
data.phoneNumber,
new Date()
];
sheet.appendRow(newRow);
return {
status: "success",
message: "บันทึกข้อมูลเรียบร้อยแล้ว"
};
} catch(error) {
return {
status: "error",
message: error.toString()
};
}
}
function createUserRow(data) {
try {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName("User_Records");
if (!sheet) {
return {
status: "error",
message: "ไม่พบหน้าชีทระบบ"
};
}
var newRow = [
"UID-" + new Date().getTime(),
data.fullName,
data.phoneNumber,
new Date()
];
sheet.appendRow(newRow);
return {
status: "success",
message: "บันทึกข้อมูลเรียบร้อยแล้ว"
};
} catch(error) {
return {
status: "error",
message: error.toString()
};
}
}3. CRUD — READ (ค้นหาข้อมูล)
ฟังก์ชันนี้ใช้สำหรับ:-
อ่านข้อมูลจากชีท
-
ค้นหาด้วยเบอร์โทรศัพท์
-
ส่งข้อมูลกลับ
function readUserByPhone(phoneToFind) {
try {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName("User_Records");
if (!sheet) {
return {
status: "error",
message: "ไม่พบหน้าชีทระบบ"
};
}
var lastRow = sheet.getLastRow();
if (lastRow <= 1) {
return {
status: "empty",
message: "ยังไม่มีข้อมูล"
};
}
var dataRange = sheet.getRange(
2,
1,
lastRow - 1,
sheet.getLastColumn()
);
var allRows = dataRange.getValues();
for (var i = 0; i < allRows.length; i++) {
var currentRow = allRows[i];
var userPhone = currentRow[2]
.toString()
.trim();
if (
userPhone ===
phoneToFind.toString().trim()
) {
return {
status: "found",
data: {
userId: currentRow[0],
fullName: currentRow[1],
phoneNumber: currentRow[2],
registerDate: currentRow[3]
}
};
}
}
return {
status: "not_found",
message: "ไม่พบข้อมูล"
};
} catch(error) {
return {
status: "error",
message: error.toString()
};
}
}
function readUserByPhone(phoneToFind) {
try {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName("User_Records");
if (!sheet) {
return {
status: "error",
message: "ไม่พบหน้าชีทระบบ"
};
}
var lastRow = sheet.getLastRow();
if (lastRow <= 1) {
return {
status: "empty",
message: "ยังไม่มีข้อมูล"
};
}
var dataRange = sheet.getRange(
2,
1,
lastRow - 1,
sheet.getLastColumn()
);
var allRows = dataRange.getValues();
for (var i = 0; i < allRows.length; i++) {
var currentRow = allRows[i];
var userPhone = currentRow[2]
.toString()
.trim();
if (
userPhone ===
phoneToFind.toString().trim()
) {
return {
status: "found",
data: {
userId: currentRow[0],
fullName: currentRow[1],
phoneNumber: currentRow[2],
registerDate: currentRow[3]
}
};
}
}
return {
status: "not_found",
message: "ไม่พบข้อมูล"
};
} catch(error) {
return {
status: "error",
message: error.toString()
};
}
}5. เทคนิคสำหรับมือใหม่: วิธีป้องกันระเบิดเวลาในระบบ CRUD
การเขียนระบบจัดการข้อมูลบน Google Sheets มีจุดที่ต้องระวังเป็นพิเศษเพื่อไม่ให้ระบบช้าหรือเกิด Error เมื่อมีข้อมูลจำนวนมาก:
ใช้คำสั่ง appendRow อย่างระมัดระวัง: คำสั่ง sheet.appendRow() เป็นวิธีบันทึกข้อมูลที่ง่ายที่สุดเพราะมันจะวิ่งไปหาแถวว่างแถวสุดท้ายให้เราเองอัตโนมัติ แต่อย่าลืมตรวจสอบสิทธิ์การเขียนไฟล์ให้เรียบร้อย และตรวจสอบว่าไม่มีการเผลอเคาะเว้นวรรคทิ้งไว้ในแถวล่างๆ ของหน้าชีท ไม่งั้นข้อมูลจะโดนโยนลงไปต่อท้ายแถวที่ว่างจริงๆ ซึ่งอาจอยู่ไกลมาก
จัดการกับช่องว่างและการตัดคำ (Trim): สังเกตในโค้ดค้นหา จะมีการใช้คำสั่ง .toString().trim() ทุกครั้งก่อนนำมาเปรียบเทียบกัน เพราะบ่อยครั้งที่ผู้ใช้งานเผลอกดเคาะสเปซบาร์เว้นวรรคหน้าหรือหลังเบอร์โทรศัพท์ตอนกรอกฟอร์ม การใช้ .trim() จะช่วยตัดช่องว่างที่มองไม่เห็นเหล่านั้นออกไป ทำให้การค้นหาแม่นยำขึ้น 100%
ใส่คำสั่ง try...catch ครอบไว้เสมอ: ในโลกความเป็นจริง อะไรก็เกิดขึ้นได้ หน้าชีทอาจโดนพนักงานลบ อินเทอร์เน็ตอาจหลุดชั่วขณะ การใช้ try...catch จะช่วยดักจับ Error แล้วส่งข้อความเตือนที่อ่านรู้เรื่องกลับไปบอกผู้ใช้ แทนการปล่อยให้ระบบค้างเติ่งไปเฉยๆ ครับ
สรุป
การเข้าใจสถาปัตยกรรม Data Routing & CRUD จะเปลี่ยนมุมมองของคุณจากการเขียนสคริปต์แผ่นเดียวให้กลายเป็นการสร้างระบบฐานข้อมูลที่แท้จริง ซึ่งโค้ดรูปแบบแยกหน้าที่แบบนี้ จะมีความยืดหยุ่นสูงมาก และพร้อมที่จะถูกนำไปอัปเกรดเพื่อเปลี่ยนวิธีการจัดเก็บข้อมูลให้อยู่ในรูปแบบที่เหนือชั้นขึ้นไปอีกในบทความตอนหน้า นั่นคือ การมัดรวมข้อมูลเก็บเป็นก้อน JSON ในเซลล์เดียว ครับ ติดตามอ่านต่อได้เลย!
