ทำความรู้จัก JSONB ใน PostgreSQL
เวลาเราเก็บข้อมูลในฐานข้อมูลปกติ เรามักจะแบ่งเป็นคอลัมน์ชัดเจน เช่น ชื่อ นามสกุล หรืออายุ แต่บางครั้งข้อมูลที่เราต้องเก็บมันเปลี่ยนไปมาตลอดเวลา เช่น รายละเอียดสินค้าที่บางชิ้นมีสี บางชิ้นมีขนาด หรือบางชิ้นมีวัสดุต่างกัน การสร้างคอลัมน์แยกทุกอย่างจะทำให้ตารางดูรกและจัดการยาก PostgreSQL JSONB (รูปแบบการเก็บข้อมูลแบบ JSON ที่ประมวลผลได้รวดเร็ว) จึงเข้ามาแก้ปัญหานี้
ลองนึกภาพว่าเรามีกล่องเก็บของแบบช่องเล็กๆ กับกล่องเก็บของแบบอเนกประสงค์ JSONB เปรียบเสมือนกล่องอเนกประสงค์ที่เราโยนของลงไปได้ทุกอย่างโดยไม่ต้องจัดระเบียบเป๊ะๆ ตั้งแต่แรก ข้อมูลประเภทนี้เรียกว่า NoSQL (การเก็บข้อมูลที่ไม่ต้องมีโครงสร้างตายตัวเหมือนตารางปกติ) ซึ่งยืดหยุ่นมากสำหรับโปรเจกต์ที่ยังไม่รู้ว่าอนาคตจะเก็บอะไรเพิ่มบ้าง
เหตุผลที่มือใหม่ควรใช้ JSONB ไม่ใช่แค่เพราะความยืดหยุ่น แต่เป็นเพราะมันเก็บข้อมูลในรูปแบบที่คอมพิวเตอร์อ่านง่ายและเร็ว ต่างจาก JSON แบบธรรมดาที่เก็บเป็นข้อความดิบๆ JSONB จะถูกแปลงเป็นรูปแบบไบนารี (ข้อมูลเลขฐานสอง) ก่อนเก็บ ทำให้ตอนเราจะค้นหาข้อมูลข้างใน มันทำได้ไวมากจนแทบไม่ต่างจากการ Query (คำสั่งถามข้อมูลจากฐานข้อมูล) ข้อมูลในคอลัมน์ปกติเลย
เตรียมตารางสำหรับเก็บข้อมูล JSONB
การใช้งานจริงเริ่มจากการสร้างตารางที่รองรับข้อมูลแบบ JSONB การประกาศชนิดข้อมูลให้เป็น jsonb ทำได้เหมือนกับการประกาศ text หรือ int (เลขจำนวนเต็ม) ปกติเลย ข้อดีคือเราสามารถผสมผสานการเก็บข้อมูลแบบตารางทั่วไป กับข้อมูลที่ยืดหยุ่นไว้ในตารางเดียวกันได้
ขั้นตอนการสร้างตารางมีดังนี้:
- สร้างตารางด้วยคำสั่ง
CREATE TABLE(คำสั่งสร้างตารางใหม่) - กำหนดคอลัมน์ชื่อ
dataให้เป็นประเภทjsonb - เพิ่มข้อมูลเข้าไปด้วยคำสั่ง
INSERT(คำสั่งเพิ่มข้อมูลใหม่)
-- สร้างตารางเก็บข้อมูลสินค้า
CREATE TABLE products (
id serial PRIMARY KEY,
name text,
attributes jsonb
);
-- เพิ่มข้อมูลสินค้าลงไป
INSERT INTO products (name, attributes)
VALUES ('Laptop', '{"color": "silver", "ram": "16GB", "cpu": "i7"}');
บรรทัดแรกเป็นการสร้างตารางชื่อ products โดยมี id เป็นตัวระบุข้อมูลไม่ซ้ำกัน คอลัมน์ attributes ถูกกำหนดให้เป็น jsonb เพื่อเก็บรายละเอียดที่ไม่ตายตัว บรรทัดสุดท้ายเป็นการเพิ่มข้อมูลโดยใส่ข้อมูลแบบ JSON เข้าไปในคอลัมน์ attributes ได้เลย
ผลลัพธ์ที่ควรเห็นคือข้อความแจ้งเตือน INSERT 0 1 ซึ่งแปลว่าระบบเพิ่มข้อมูลลงไปเรียบร้อยแล้ว 1 แถว ถ้าเราลองใช้คำสั่ง SELECT * FROM products; เราจะเห็นข้อมูลที่เก็บอยู่ในรูปแบบ JSON ครบถ้วนตามที่เราใส่เข้าไป
การดึงข้อมูลภายในด้วย Operator
เมื่อเราเก็บข้อมูลเข้าไปแล้ว สิ่งที่ต้องทำต่อคือการดึงเฉพาะส่วนที่ต้องการออกมา PostgreSQL มี Operator (สัญลักษณ์พิเศษที่ใช้สั่งงาน) ที่ทรงพลังมากในการเจาะเข้าไปใน JSONB เพื่อหาค่าที่ซ่อนอยู่ข้างใน ทำให้เราไม่ต้องดึงข้อมูลทั้งก้อนออกมาประมวลผลข้างนอก
ตัวแรกคือ -> ซึ่งใช้ดึงค่าออกมาเป็นประเภท JSON เช่น ถ้าเราอยากได้ข้อมูล color จากสินค้า เราก็แค่ระบุชื่อคอลัมน์ตามด้วยสัญลักษณ์นี้ อีกตัวคือ ->> ซึ่งคล้ายกันแต่จะดึงออกมาเป็นข้อความธรรมดา (Text) ซึ่งมักจะใช้ง่ายกว่าในการเขียนโปรแกรมต่อ
การเข้าใจความต่างของสองตัวนี้สำคัญมาก เพราะถ้าเราดึงออกมาเป็น JSON มันจะยังติดเครื่องหมายคำพูดหรือโครงสร้างอยู่ แต่ถ้าดึงเป็น Text เราจะได้ค่าข้างในออกมาใช้งานได้ทันที เช่น เอาไปเปรียบเทียบในเงื่อนไข WHERE (เงื่อนไขกรองข้อมูล) ของเรา
-- ดึงค่าสีออกมาเป็นข้อความ
SELECT attributes->>'color' FROM products WHERE name = 'Laptop';
คำสั่งนี้ใช้ ->> เพื่อดึงค่าของ color ออกมาโดยเฉพาะจากคอลัมน์ attributes โดยเลือกเฉพาะแถวที่ name เป็น Laptop เท่านั้น สัญลักษณ์ ->> จะทำให้ผลลัพธ์ที่ได้ออกมาเป็นข้อความ silver ตรงๆ
ผลลัพธ์ที่ควรเห็นคือ silver แสดงขึ้นมาบนหน้าจอ ถ้าใช้ -> แทน ผลลัพธ์จะออกมาเป็น "silver" ซึ่งมีเครื่องหมายคำพูดติดมาด้วย มือใหม่มักพลาดตรงนี้จนทำให้เอาไปเปรียบเทียบเงื่อนไขต่อไม่ได้
ค้นหาข้อมูลแบบเจาะลึกด้วย @>
ถ้าเราอยากหาว่า สินค้าตัวไหนบ้างที่มี ram เป็น 16GB เราไม่จำเป็นต้องเขียนโค้ดซับซ้อน เราสามารถใช้ Operator พิเศษที่ชื่อว่า @> (Containment Operator) ซึ่งหมายถึง "ประกอบด้วย" ได้ทันที มันช่วยให้เราค้นหาจากข้อมูลที่ซ้อนอยู่ข้างในได้แม่นยำและรวดเร็ว
การใช้ @> คือการถามฐานข้อมูลว่า "มีโครงสร้าง JSON นี้อยู่ข้างในใช่ไหม" วิธีนี้มีประสิทธิภาพสูงกว่าการดึงข้อมูลออกมาตรวจสอบทีละบรรทัดด้วยโปรแกรม เพราะตัวฐานข้อมูลเองจะจัดการค้นหาให้ตั้งแต่วินาทีที่ Query เข้าไปถึง
ข้อควรระวังคือเรื่องรูปแบบของ JSON ที่เราใช้ค้นหา ต้องเขียนให้ถูกต้องตามมาตรฐาน JSON ทุกประการ เช่น ต้องใช้เครื่องหมายคำพูดคู่เท่านั้น ห้ามใช้เครื่องหมายคำพูดเดี่ยวเด็ดขาด เพราะถ้าพลาดแม้แต่นิดเดียว ฐานข้อมูลจะแจ้งเตือนว่าคำสั่งผิดพลาดทันที
-- ค้นหาสินค้าที่มี ram เป็น 16GB
SELECT name FROM products WHERE attributes @> '{"ram": "16GB"}';
คำสั่งนี้เป็นการกรองแถวข้อมูลในตาราง products โดยดูว่าคอลัมน์ attributes มีส่วนประกอบของ {"ram": "16GB"} อยู่หรือไม่ ถ้าเจอข้อมูลไหนที่มีคีย์ ram และค่าเป็น 16GB มันจะส่งชื่อสินค้าตัวนั้นกลับมาให้เรา
ผลลัพธ์ที่ควรเห็นคือชื่อ Laptop ปรากฏขึ้นมา หากไม่มีสินค้าตัวไหนตรงกับเงื่อนไข ผลลัพธ์จะเป็นช่องว่างเปล่าๆ ซึ่งหมายความว่าไม่มีข้อมูลที่ตรงตามเงื่อนไขที่เรากำหนด
เพิ่มความเร็วด้วย GIN Index
เมื่อข้อมูลในตารางเริ่มมีเยอะขึ้น การค้นหาข้อมูลภายใน JSONB อาจจะเริ่มช้าลง PostgreSQL จึงมีฟีเจอร์ Index (ตัวช่วยดัชนีเหมือนสารบัญหนังสือ) ที่ออกแบบมาเพื่อ JSONB โดยเฉพาะ เรียกว่า GIN Index (Generalized Inverted Index) ซึ่งจะช่วยให้การค้นหาเร็วขึ้นมหาศาล
คิดซะว่าข้อมูลในตารางคือหนังสือเล่มหนา ถ้าเราไม่มีสารบัญ เราต้องเปิดอ่านทุกหน้าเพื่อหาคำที่ต้องการ แต่ถ้าเราทำ GIN Index ก็เหมือนเราสร้างสารบัญท้ายเล่มที่บันทึกไว้ว่าคำนี้อยู่หน้าไหนบ้าง ทำให้เรากระโดดไปเจอข้อมูลได้ทันทีโดยไม่ต้องไล่อ่านทีละบรรทัด
การสร้าง GIN Index ควรทำเฉพาะตอนที่ข้อมูลเริ่มเยอะขึ้นแล้วจริงๆ เพราะการทำ Index จะกินพื้นที่หน่วยความจำและทำให้การเพิ่มข้อมูลใหม่ช้าลงเล็กน้อย เนื่องจากฐานข้อมูลต้องคอยอัปเดตสารบัญทุกครั้งที่มีการเปลี่ยนข้อมูล แต่สำหรับโปรเจกต์ทั่วไปที่เน้นอ่านข้อมูลมากกว่าเขียน นี่คือสิ่งที่คุ้มค่ามาก
-- สร้างดัชนีช่วยค้นหาให้เร็วขึ้น
CREATE INDEX idx_products_attributes ON products USING GIN (attributes);
บรรทัดนี้คือการสั่งให้สร้าง Index ชื่อ idx_products_attributes บนตาราง products โดยบอกให้ใช้เทคนิค GIN กับคอลัมน์ attributes เพื่อให้การค้นหาด้วย @> เร็วขึ้นอย่างเห็นได้ชัดในข้อมูลระดับหลักหมื่นหรือหลักแสนแถว
ผลลัพธ์ที่ควรเห็นคือ CREATE INDEX แสดงว่าระบบสร้างดัชนีเสร็จแล้ว หลังจากนี้การทำ SELECT ด้วยเงื่อนไข WHERE attributes @> '...' จะทำงานไวขึ้นมากโดยที่เราไม่ต้องเปลี่ยนโค้ดส่วนอื่นเลย
สรุป: การนำไปใช้จริงในงานโปรแกรมเมอร์
การใช้ JSONB ไม่ได้แปลว่าเราต้องทิ้งฐานข้อมูลแบบตารางไปเลย แต่เป็นการเพิ่มอาวุธในมือให้เราเลือกใช้ได้เหมาะสมกับงานมากขึ้น ตัวอย่างเช่น การทำระบบ User Profile (ข้อมูลส่วนตัวของผู้ใช้) ที่บางคนอาจจะมีแค่ชื่อกับอีเมล แต่บางคนอาจจะมีช่องทางโซเชียลมีเดียหลายช่องทางที่ไม่เหมือนกัน
มือใหม่ที่กำลังฝึกทำโปรเจกต์ พอร์ตโฟลิโอ ควรเริ่มจากการเก็บข้อมูลปกติก่อน แล้วลองเปลี่ยนข้อมูลที่เปลี่ยนบ่อยหรือมีโครงสร้างไม่แน่นอนมาเก็บใน JSONB คุณจะพบว่ามันช่วยลดเวลาการแก้โครงสร้างตาราง (Migration) ไปได้เยอะมาก ทำให้คุณมีเวลาไปโฟกัสกับการเขียนฟีเจอร์ให้ผู้ใช้ได้ใช้งานจริง
จดจำไว้ว่าเครื่องมือที่ดีที่สุดคือเครื่องมือที่แก้ปัญหาได้ตรงจุด ถ้าข้อมูลของคุณมีความสัมพันธ์ชัดเจนและคงที่ การเก็บในคอลัมน์ปกติยังคงเป็นทางเลือกที่ดีที่สุด แต่ถ้างานของคุณต้องการความยืดหยุ่นสูง JSONB คือเพื่อนแท้ที่จะช่วยให้คุณทำงานได้ไวและมีประสิทธิภาพมากขึ้นในฐานะโปรแกรมเมอร์มืออาชีพ