PlusMagi's Blog By Pitt Phunsanit Backend,Database Database Relationships: วิธีการออกแบบตารางสำหรับความสัมพันธ์แบบ Many-to-Many (N:M) โดยใช้ Junction Table

Database Relationships: วิธีการออกแบบตารางสำหรับความสัมพันธ์แบบ Many-to-Many (N:M) โดยใช้ Junction Table

ในโลกของการพัฒนาระบบซอฟต์แวร์ ข้อมูลส่วนใหญ่มักไม่ได้อยู่โดดเดี่ยว แต่มีความเชื่อมโยงและปฏิสัมพันธ์กันอย่างซับซ้อน การออกแบบโครงสร้างตาราง (Schema Design) ที่แข็งแกร่งจึงเป็นรากฐานสำคัญของระบบที่สามารถขยายตัวได้ (Scalable System) เมื่อเราต้องจัดการกับความสัมพันธ์ที่ซับซ้อน โดยเฉพาะความสัมพันธ์แบบ Many-to-Many (N:M) การทำความเข้าใจหลักการของ Junction Table จึงเป็นทักษะที่วิศวกรข้อมูลทุกคนต้องรู้

ความท้าทายของความสัมพันธ์แบบ Many-to-Many (N:M)

ความสัมพันธ์แบบ N:M คือสถานการณ์ที่เอนทิตี (Entity) สองตัวสามารถมีความเชื่อมโยงกันได้มากกว่าหนึ่งครั้ง ตัวอย่างคลาสสิกคือความสัมพันธ์ระหว่าง ‘นักเรียน’ และ ‘วิชาเรียน’ โดยที่นักเรียนคนหนึ่งสามารถลงทะเบียนได้หลายวิชา และวิชาหนึ่งก็มีนักเรียนจำนวนมากที่ลงทะเบียนได้

หากเราพยายามเก็บความสัมพันธ์นี้ไว้ในตารางใดตารางหนึ่งโดยตรง จะเกิดปัญหาที่เรียกว่า Data Redundancy (ข้อมูลซ้ำซ้อน) และทำให้การ Query ข้อมูลที่เกี่ยวข้องทำได้ยากและไม่มีประสิทธิภาพ การออกแบบที่ดีจึงต้องแยกความสัมพันธ์นี้ออกมาเป็นตารางเฉพาะ

หลักการใช้ Junction Table ใน PostgreSQL

ทางออกเชิงสถาปัตยกรรมที่ถูกต้องคือการใช้ Junction Table (หรือ Linking Table) ซึ่งเป็นตารางตัวกลางที่มีหน้าที่เพียงอย่างเดียวคือการเก็บ ‘คู่’ ของ Primary Key (PK) จากตารางหลักทั้งสอง

Junction Table จะไม่มีข้อมูลเนื้อหาหลัก (Content Data) ของตัวเอง แต่จะประกอบด้วย Foreign Keys (FK) ที่อ้างอิงถึง PK ของตารางต้นทางเท่านั้น การทำเช่นนี้ไม่เพียงแต่ช่วยรักษาความสมบูรณ์ของข้อมูล (Data Integrity) แต่ยังทำให้การ Query ข้อมูลที่ซับซ้อนทำได้อย่างรวดเร็วและแม่นยำตามหลักการ Normalization ของฐานข้อมูล

ตัวอย่างการใช้งานจริงด้วย PostgreSQL

สมมติว่าเราต้องการสร้างระบบที่เชื่อมโยง ‘ผู้ใช้’ (Users) กับ ‘บทความ’ (Articles) โดยที่ผู้ใช้หนึ่งคนสามารถเขียนได้หลายบทความ และบทความหนึ่งบทความสามารถมีผู้เขียนได้หลายคน (Co-authoring)

เราจะสร้าง 3 ตาราง:

  1. users: เก็บข้อมูลผู้ใช้
  2. articles: เก็บข้อมูลบทความ
  3. user_article_link: (Junction Table) เก็บคู่ (user_id, article_id)

SQL

-- 1. สร้างตารางหลัก Users
CREATE TABLE users (
    user_id SERIAL PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL
);

-- 2. สร้างตารางหลัก Articles
CREATE TABLE articles (
    article_id SERIAL PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    content TEXT
);

-- 3. สร้าง Junction Table (user_article_link)
-- ตารางนี้เก็บเพียง Foreign Keys สองตัว
CREATE TABLE user_article_link (
    user_id INTEGER REFERENCES users(user_id) ON DELETE CASCADE,
    article_id INTEGER REFERENCES articles(article_id) ON DELETE CASCADE,
    role VARCHAR(50) DEFAULT 'author', -- อาจเพิ่มข้อมูลความสัมพันธ์เฉพาะเจาะจงได้
    PRIMARY KEY (user_id, article_id)
);

-- ตัวอย่างการใส่ข้อมูล
INSERT INTO users (username) VALUES ('Alice'), ('Bob');
INSERT INTO articles (title, content) VALUES ('PostgreSQL Guide', 'Advanced SQL');

-- Alice (ID 1) เขียนบทความ (ID 1) และ Bob (ID 2) ก็ร่วมเขียนด้วย
INSERT INTO user_article_link (user_id, article_id, role) VALUES (1, 1, 'primary_author');
INSERT INTO user_article_link (user_id, article_id, role) VALUES (2, 1, 'co_author');

การ Query ข้อมูลความสัมพันธ์

การดึงข้อมูลทั้งหมดที่เชื่อมโยงกันต้องใช้คำสั่ง JOIN เพื่อรวมข้อมูลจากทั้งสามตาราง:

SQL

SELECT
    u.username AS User,
    a.title AS ArticleTitle,
    ual.role AS Role
FROM
    users u
JOIN
    user_article_link ual ON u.user_id = ual.user_id
JOIN
    articles a ON ual.article_id = a.article_id
WHERE
    a.title = 'PostgreSQL Guide';

บทสรุป: หัวใจของการออกแบบระบบที่ยืดหยุ่น

การใช้ Junction Table เป็นรูปแบบการออกแบบที่สำคัญอย่างยิ่งในการจัดการความสัมพันธ์แบบ N:M ในระดับ Senior Architect การทำเช่นนี้ช่วยให้ฐานข้อมูลของเรามีความเป็นระเบียบ (Normalized) ป้องกันปัญหาข้อมูลซ้ำซ้อน และที่สำคัญที่สุดคือทำให้ระบบมีความยืดหยุ่นสูง สามารถรองรับการขยายตัวของเอนทิตีใหม่ๆ หรือความสัมพันธ์ที่ซับซ้อนได้โดยไม่กระทบต่อโครงสร้างหลักของตารางเดิม การทำความเข้าใจหลักการนี้จึงเป็นกุญแจสำคัญในการสร้างระบบฐานข้อมูลที่มีประสิทธิภาพและเชื่อถือได้