ทำไมต้องรู้จักการกำหนดกฎให้ข้อมูลในฐานข้อมูล
เวลาเราสร้างแอปพลิเคชัน ข้อมูลที่เก็บไว้ใน PostgreSQL (ระบบจัดการฐานข้อมูลแบบเชิงสัมพันธ์) เปรียบเสมือนสมุดบันทึกรายชื่อลูกค้า ถ้าเราปล่อยให้เขียนอะไรลงไปก็ได้ เช่น ลืมใส่ชื่อ หรือใส่เลขเบอร์โทรซ้ำกัน มั่วไปหมด ข้อมูลในระบบจะพังและใช้งานไม่ได้เลย
เราจึงต้องมี Constraints (กฎเกณฑ์ที่ใช้ควบคุมความถูกต้องของข้อมูล) เพื่อบังคับให้ข้อมูลที่กรอกเข้ามาต้องอยู่ในรูปแบบที่เรากำหนดไว้เท่านั้น นี่คือหัวใจสำคัญของการออกแบบฐานข้อมูลที่ดี ซึ่งโปรแกรมเมอร์มืออาชีพต้องให้ความสำคัญตั้งแต่เริ่มโปรเจกต์
การเข้าใจเรื่องนี้จะช่วยให้แอปของคุณทำงานได้แม่นยำและไม่มีบั๊ก (ข้อผิดพลาดของโปรแกรม) เรื่องข้อมูลซ้ำซ้อนหรือข้อมูลหาย วันนี้เราจะมาดูเครื่องมือพื้นฐานที่ช่วยให้ฐานข้อมูลของคุณมีระเบียบและน่าเชื่อถือมากขึ้นครับ
รู้จักกับ Primary Key กุญแจสำคัญในการแยกแยะข้อมูล
Primary Key (กุญแจหลักที่ใช้ระบุตัวตนของข้อมูลแต่ละแถว) คือสิ่งที่บอกว่าข้อมูลชุดนี้เป็นของใครแบบไม่ซ้ำกับคนอื่น เหมือนเลขบัตรประชาชนที่ใช้แยกคนในประเทศ การตั้งค่านี้สำคัญมากเพราะถ้าไม่มีมัน เราจะหาหรือแก้ไขข้อมูลได้ยากมาก
ถ้าเราไม่มีกุญแจหลัก เวลาจะลบข้อมูลลูกค้าชื่อ "สมชาย" เราอาจจะลบผิดคนถ้ามีสมชายหลายคนในระบบ การกำหนด Primary Key จะทำให้ทุกแถวในตารางมี "ชื่อเรียก" ที่ไม่ซ้ำกันแน่นอน ทำให้การดึงข้อมูลออกมาทำได้รวดเร็วและแม่นยำ
ในการออกแบบตาราง เรามักเลือกคอลัมน์ที่เป็นค่าเฉพาะตัวมาทำหน้าที่นี้ แต่ถ้าไม่มีคอลัมน์ไหนเหมาะ เราก็จะสร้างคอลัมน์ใหม่ขึ้นมาเพื่อทำหน้าที่นี้โดยเฉพาะครับ
-- สร้างตารางสมาชิกโดยกำหนดให้ id เป็น Primary Key
CREATE TABLE members (
id SERIAL PRIMARY KEY,
name TEXT
);
อธิบายโค้ด: id คือชื่อคอลัมน์ SERIAL คือคำสั่งให้เลขรันอัตโนมัติ และ PRIMARY KEY คือการบอกว่าคอลัมน์นี้ห้ามซ้ำและห้ามว่าง ผลลัพธ์ที่ได้คือตารางชื่อ members ที่พร้อมใช้งานและมีหมายเลขประจำตัวสมาชิกให้ทันทีที่เพิ่มข้อมูล
การรันเลขอัตโนมัติด้วย SERIAL และ IDENTITY
การมานั่งพิมพ์เลขไอดีเองทีละเลขเป็นเรื่องที่เหนื่อยและเสี่ยงต่อการผิดพลาด SERIAL (คำสั่งสร้างตัวเลขรันอัตโนมัติแบบเก่า) จึงถูกสร้างมาเพื่อช่วยให้เราไม่ต้องกังวลเรื่องการนับเลขเอง เพราะฐานข้อมูลจะจัดการให้เราเองทุกครั้งที่มีการเพิ่มข้อมูลใหม่
ในปัจจุบัน PostgreSQL แนะนำให้ใช้คำสั่ง GENERATED ALWAYS AS IDENTITY แทน SERIAL เพราะเป็นมาตรฐานสากลและใช้งานได้ปลอดภัยกว่า แต่ทั้งสองแบบก็มีจุดประสงค์เดียวกันคือ การรันเลขให้เราโดยอัตโนมัติเพื่อใช้เป็นตัวระบุตัวตน
คุณแค่เลือกใช้ตามความเหมาะสมของโปรเจกต์ แต่ถ้าถามว่าตัวไหนทันสมัยกว่า ก็ต้องยกให้ IDENTITY ครับ มันจะช่วยให้โค้ดของคุณดูเป็นระเบียบและเป็นไปตามมาตรฐานที่คนทั่วโลกใช้กันในปัจจุบัน
-- วิธีการใช้ GENERATED ALWAYS AS IDENTITY
CREATE TABLE products (
product_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
product_name TEXT
);
อธิบายโค้ด: INT คือชนิดข้อมูลตัวเลขจำนวนเต็ม GENERATED ALWAYS AS IDENTITY คือคำสั่งสั่งให้ฐานข้อมูลคอยเพิ่มเลขให้เองทุกครั้งที่เพิ่มสินค้า ผลลัพธ์คือคุณสามารถเพิ่มข้อมูลสินค้าได้โดยไม่ต้องสนใจเลขไอดี ระบบจะใส่เลข 1, 2, 3 ไปเรื่อยๆ ให้เอง
บังคับใส่ข้อมูลด้วย NOT NULL
บางครั้งเรามีคอลัมน์ที่จำเป็นต้องมีข้อมูลเสมอ เช่น ชื่อลูกค้า หรืออีเมล ถ้าปล่อยให้ว่างเปล่า ระบบอาจจะทำงานผิดพลาดได้ NOT NULL (ข้อกำหนดที่ห้ามให้ช่องนั้นว่างเปล่า) จึงเป็นตัวช่วยป้องกันความผิดพลาดในจุดนี้
ลองนึกภาพว่าเราทำแอปขายของออนไลน์ แต่ลูกค้าสั่งซื้อโดยไม่ใส่อีเมลไว้ติดต่อกลับ แบบนี้จะเกิดปัญหาตอนส่งของแน่นอน การใช้ NOT NULL จะช่วยให้ฐานข้อมูลปฏิเสธการบันทึกทันทีถ้าเราลืมกรอกข้อมูลที่จำเป็นลงไป
การกำหนดค่านี้ตั้งแต่ตอนสร้างตารางจะช่วยประหยัดเวลาในการเขียนโค้ดเช็คข้อมูลฝั่งโปรแกรม (ฝั่งที่ติดต่อกับผู้ใช้) ได้เยอะมาก เพราะเรามั่นใจได้เลยว่าข้อมูลในฐานข้อมูลของเราจะมีครบถ้วนตามที่ต้องการครับ
-- กำหนดให้ username ห้ามเป็นค่าว่าง
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username TEXT NOT NULL
);
อธิบายโค้ด: NOT NULL วางไว้หลังชนิดข้อมูล TEXT เพื่อบอกว่าคอลัมน์ username ต้องมีค่าเสมอ ผลลัพธ์คือถ้าคุณพยายามเพิ่มข้อมูลโดยไม่ใส่ชื่อผู้ใช้ ฐานข้อมูลจะส่งข้อความเตือนกลับมาว่า null value in column "username" violates not-null constraint ทันที
ป้องกันข้อมูลซ้ำด้วย UNIQUE
ในบางกรณีเราต้องการให้ข้อมูลไม่ซ้ำกัน แต่ไม่ต้องการใช้เป็น Primary Key เช่น อีเมล หรือ เบอร์โทรศัพท์ ที่แต่ละคนต้องมีหนึ่งเดียวในระบบ UNIQUE (กฎที่บังคับว่าห้ามมีข้อมูลซ้ำกันในคอลัมน์นั้น) คือคำตอบของปัญหานี้
ถ้าเราไม่กำหนด UNIQUE ไว้ คนหนึ่งคนอาจจะสมัครสมาชิกด้วยอีเมลเดิมซ้ำได้ถึงสิบครั้ง ซึ่งจะทำให้ระบบเรายุ่งเหยิงและจัดการข้อมูลลำบากมาก การสั่งห้ามซ้ำตั้งแต่ระดับฐานข้อมูลถือเป็นการป้องกันที่ต้นเหตุที่ดีที่สุด
มือใหม่มักลืมใส่ตรงนี้และไปแก้ปัญหาด้วยการเขียนโค้ดเช็คในภาษาโปรแกรม ซึ่งอาจจะพลาดได้ง่ายกว่ามาก การให้ฐานข้อมูลเป็นคนคุมกฎนี้จึงเป็นวิธีที่โปรแกรมเมอร์มืออาชีพเลือกใช้ครับ
-- กำหนดให้ email ห้ามซ้ำกัน
CREATE TABLE members (
id SERIAL PRIMARY KEY,
email TEXT UNIQUE
);
อธิบายโค้ด: คำว่า UNIQUE จะเข้าไปควบคุมคอลัมน์ email ผลลัพธ์คือถ้ามีใครพยายามสมัครด้วยอีเมลที่เคยมีคนใช้ไปแล้ว ฐานข้อมูลจะหยุดการทำงานและแจ้งเตือนว่า duplicate key value violates unique constraint ทำให้ข้อมูลในระบบสะอาดและไม่ซ้ำซ้อน
คุมขอบเขตข้อมูลด้วย CHECK Constraints
บางครั้งข้อมูลก็ต้องมีเงื่อนไขพิเศษ เช่น อายุต้องมากกว่า 18 ปี หรือ ราคาสินค้าต้องไม่ติดลบ CHECK (กฎที่ใช้ตรวจสอบเงื่อนไขเฉพาะ) ช่วยให้เราเขียนเงื่อนไขทางคณิตศาสตร์หรือตรรกะง่ายๆ ลงไปในฐานข้อมูลได้เลย
การใช้ CHECK ช่วยลดความเสี่ยงที่ข้อมูลผิดปกติจะหลุดเข้าไปในระบบได้ดีมาก เช่น ถ้าเราทำระบบขายของ เราคงไม่อยากให้ราคาสินค้าเป็นเลขติดลบเพราะความผิดพลาดในการกรอกข้อมูล การตั้งเงื่อนไขไว้จะทำให้ระบบของเราปลอดภัยและน่าเชื่อถือขึ้น
นี่คือตัวอย่างของการเขียนโปรแกรมเชิงป้องกัน (Preventive Programming) ที่ทำให้ซอฟต์แวร์ของคุณแข็งแกร่ง ไม่ว่าจะเจอผู้ใช้งานกรอกข้อมูลมาแบบไหน คุณก็มั่นใจได้ว่าข้อมูลในฐานข้อมูลจะถูกต้องตามกฎที่คุณวางไว้
-- เช็คว่าราคาต้องมากกว่า 0 เสมอ
CREATE TABLE products (
id SERIAL PRIMARY KEY,
price NUMERIC CHECK (price > 0)
);
อธิบายโค้ด: CHECK (price > 0) เป็นการตั้งเงื่อนไขว่าค่าใน price ต้องมากกว่า 0 เท่านั้น ผลลัพธ์คือถ้าคุณพยายามใส่ราคาเป็น 0 หรือติดลบ ฐานข้อมูลจะปฏิเสธรายการนั้นทันทีและแสดงข้อความ new row for relation violates check constraint
สรุป: เอาไปใช้จริงในโปรเจกต์แรกของคุณ
การออกแบบฐานข้อมูลที่ดีคือจุดเริ่มต้นของโปรแกรมเมอร์ที่เก่ง เมื่อคุณเริ่มทำโปรเจกต์แรก เช่น แอปสมุดจดบันทึก ให้เริ่มจากคิดว่าอะไรคือสิ่งที่ห้ามซ้ำ อะไรคือสิ่งที่ห้ามว่าง และอะไรคือเงื่อนไขพิเศษของข้อมูลนั้นๆ
ลองนำสิ่งที่เรียนวันนี้ไปใช้ โดยเริ่มจากสร้างตาราง tasks (งานที่ต้องทำ) ให้มี id เป็น PRIMARY KEY, title เป็น NOT NULL, และอาจมีคอลัมน์ priority ที่ใช้ CHECK เพื่อกำหนดว่าต้องเป็นเลข 1 ถึง 5 เท่านั้น
อย่าลืมว่าการทำฐานข้อมูลให้แน่นหนาตั้งแต่แรก จะช่วยให้คุณประหยัดเวลาแก้บั๊กในอนาคตได้มหาศาล ขอให้สนุกกับการเขียนโค้ดและสร้างสรรค์โปรเจกต์ของคุณให้เต็มที่ครับ!