跳到主要内容

เนื้อหาภาษาไทย / 泰语内容

系统指南 内容已审核

ความสัมพันธ์ระหว่าง Entity: One-to-One, One-to-Many, Many-to-Many

บทความนี้อธิบายถึงความสัมพันธ์ระหว่าง Entity ในฐานข้อมูล 3 ประเภทหลัก ได้แก่ One-to-One, One-to-Many, และ Many-to-Many พร้อมยกตัวอย่างและแสดงวิธีการจัดการความสั...

ความสัมพันธ์ระหว่าง Entity: One-to-One, One-to-Many, Many-to-Many

ในบทที่แล้ว เราได้เรียนรู้วิธีการจัดการข้อมูลพื้นฐาน (CRUD Operations) ด้วย ORM (Object-Relational Mapper) กันไปแล้ว แต่ในโลกของฐานข้อมูลจริงๆ ความสัมพันธ์ระหว่างตาราง (Table) ต่างๆ นั้นซับซ้อนกว่านั้นมาก แอปพลิเคชันส่วนใหญ่มักจะมีข้อมูลที่เชื่อมโยงกัน เช่น ผู้ใช้งาน (User) กับบทความ (Article), สินค้า (Product) กับหมวดหมู่ (Category), หรือ นักเรียน (Student) กับคอร์สเรียน (Course) เป็นต้น

ORM เข้ามาช่วยให้เราจัดการความสัมพันธ์เหล่านี้ได้ง่ายขึ้น โดยการแปลงความสัมพันธ์ระหว่างตารางในฐานข้อมูล ให้กลายเป็นความสัมพันธ์ระหว่าง Object ในโค้ดของเรา ทำให้เราสามารถเขียนโค้ดที่อ่านง่าย เข้าใจง่าย และบำรุงรักษาง่ายขึ้นได้มาก

ในบทนี้ เราจะมาเจาะลึกถึงประเภทของความสัมพันธ์ที่พบบ่อยที่สุดในฐานข้อมูล และวิธีการจัดการความสัมพันธ์เหล่านี้ด้วย ORM กันครับ

ประเภทของความสัมพันธ์

ความสัมพันธ์ระหว่าง Entity (หรือตารางในฐานข้อมูล) มีอยู่ 3 ประเภทหลักๆ คือ:

  • One-to-One (หนึ่งต่อหนึ่ง): หนึ่ง Entity สามารถเชื่อมโยงกับอีก Entity หนึ่งได้เพียงหนึ่งเดียวเท่านั้น
  • One-to-Many (หนึ่งต่อหลาย): หนึ่ง Entity สามารถเชื่อมโยงกับอีก Entity หนึ่งได้หลาย Entity
  • Many-to-Many (หลายต่อหลาย): หลาย Entity สามารถเชื่อมโยงกับอีกหลาย Entity ได้

เราจะมาดูแต่ละประเภทกันอย่างละเอียด พร้อมตัวอย่างการใช้งานจริงครับ

One-to-One Relationship (หนึ่งต่อหนึ่ง)

ความสัมพันธ์แบบ One-to-One คือความสัมพันธ์ที่ Entity หนึ่งเชื่อมโยงกับ Entity อื่นได้เพียง Entity เดียวเท่านั้น ตัวอย่างเช่น:

  • User กับ Profile: ผู้ใช้งานหนึ่งคน (User) มีข้อมูลส่วนตัว (Profile) ได้เพียงหนึ่งเดียว
  • Person กับ Passport: บุคคลหนึ่งคน (Person) มีหนังสือเดินทาง (Passport) ได้เพียงหนึ่งเดียว
  • Headquarter กับ Country: สำนักงานใหญ่ (Headquarter) ตั้งอยู่ในประเทศ (Country) ได้เพียงประเทศเดียว

ในการกำหนดความสัมพันธ์แบบ One-to-One ใน ORM เรามักจะใช้ Foreign Key (คีย์นอก) เพื่อเชื่อมโยง Entity ทั้งสองเข้าด้วยกัน

ตัวอย่าง (Doctrine):

// Entity User
/**
 * @Entity
 * @Table(name="users")
 */
class User
{
    /**
     * @Id
     * @GeneratedValue
     * @Column(type="integer")
     */
    private $id;

    /**
     * @Column(type="string", length=255)
     */
    private $username;

    /**
     * @OneToOne(targetEntity="Profile", mappedBy="user")
     */
    private $profile;

    // ... getters and setters ...
}

// Entity Profile
/**
 * @Entity
 * @Table(name="profiles")
 */
class Profile
{
    /**
     * @Id
     * @GeneratedValue
     * @Column(type="integer")
     */
    private $id;

    /**
     * @OneToOne(targetEntity="User", inversedBy="profile")
     * @JoinColumn(name="user_id", referencedColumnName="id")
     */
    private $user;

    /**
     * @Column(type="string", length=255)
     */
    private $fullname;

    // ... getters and setters ...
}

ในตัวอย่างนี้ @OneToOne annotation ใน User Entity บอกว่า User มีความสัมพันธ์แบบ One-to-One กับ Profile Entity และ mappedBy="user" บอกว่าความสัมพันธ์นี้ถูกจัดการโดย user property ใน Profile Entity ส่วนใน Profile Entity @OneToOne annotation บอกว่า Profile มีความสัมพันธ์แบบ One-to-One กับ User Entity และ inversedBy="profile" บอกว่าความสัมพันธ์นี้ถูกจัดการโดย profile property ใน User Entity และ @JoinColumn บอกว่า user_id column ใน profiles table เป็น Foreign Key ที่เชื่อมโยงกับ id column ใน users table

เหตุผลเชิงออกแบบและ Trade-offs: การใช้ One-to-One Relationship มักจะถูกใช้เมื่อเราต้องการแบ่งข้อมูลออกเป็นส่วนๆ เพื่อความชัดเจนในการจัดการ หรือเพื่อเพิ่มประสิทธิภาพในการ query ข้อมูล (เช่น ข้อมูลส่วนตัวที่ไม่จำเป็นต้องโหลดทุกครั้งที่โหลดข้อมูลผู้ใช้งาน) แต่ข้อเสียคือ อาจจะทำให้ query ซับซ้อนขึ้นเล็กน้อย หากต้องดึงข้อมูลจากทั้งสอง Entity บ่อยๆ

One-to-Many Relationship (หนึ่งต่อหลาย)

ความสัมพันธ์แบบ One-to-Many คือความสัมพันธ์ที่ Entity หนึ่งเชื่อมโยงกับ Entity อื่นได้หลาย Entity ตัวอย่างเช่น:

  • User กับ Article: ผู้ใช้งานหนึ่งคน (User) สามารถเขียนบทความ (Article) ได้หลายบทความ
  • Category กับ Product: หมวดหมู่หนึ่งหมวด (Category) สามารถมีสินค้า (Product) ได้หลายรายการ
  • Author กับ Book: นักเขียนหนึ่งคน (Author) สามารถเขียนหนังสือ (Book) ได้หลายเล่ม

ในการกำหนดความสัมพันธ์แบบ One-to-Many ใน ORM เรามักจะใช้ Foreign Key ใน Entity ฝั่ง "Many" เพื่อเชื่อมโยงกับ Entity ฝั่ง "One"

ตัวอย่าง (Doctrine):

// Entity User
/**
 * @Entity
 * @Table(name="users")
 */
class User
{
    /**
     * @Id
     * @GeneratedValue
     * @Column(type="integer")
     */
    private $id;

    /**
     * @Column(type="string", length=255)
     */
    private $username;

    /**
     * @OneToMany(targetEntity="Article", mappedBy="author")
     */
    private $articles;

    public function __construct() {
        $this->articles = new \Doctrine\Common\Collections\ArrayCollection();
    }

    // ... getters and setters ...

    public function addArticle(Article $article)
    {
        $this->articles[] = $article;
        $article->setAuthor($this); // จำเป็นต้อง set author ใน article ด้วย
        return $this;
    }
}

// Entity Article
/**
 * @Entity
 * @Table(name="articles")
 */
class Article
{
    /**
     * @Id
     * @GeneratedValue
     * @Column(type="integer")
     */
    private $id;

    /**
     * @Column(type="string", length=255)
     */
    private $title;

    /**
     * @ManyToOne(targetEntity="User", inversedBy="articles")
     * @JoinColumn(name="author_id", referencedColumnName="id")
     */
    private $author;

    // ... getters and setters ...

     public function setAuthor(User $author)
    {
        $this->author = $author;

        return $this;
    }
}

ในตัวอย่างนี้ @OneToMany annotation ใน User Entity บอกว่า User มีความสัมพันธ์แบบ One-to-Many กับ Article Entity และ mappedBy="author" บอกว่าความสัมพันธ์นี้ถูกจัดการโดย author property ใน Article Entity ส่วนใน Article Entity @ManyToOne annotation บอกว่า Article มีความสัมพันธ์แบบ Many-to-One กับ User Entity และ inversedBy="articles" บอกว่าความสัมพันธ์นี้ถูกจัดการโดย articles property ใน User Entity และ @JoinColumn บอกว่า author_id column ใน articles table เป็น Foreign Key ที่เชื่อมโยงกับ id column ใน users table

เหตุผลเชิงออกแบบและ Trade-offs: One-to-Many Relationship เป็นความสัมพันธ์ที่พบบ่อยมากในการออกแบบฐานข้อมูล ช่วยให้เราจัดระเบียบข้อมูลได้อย่างมีประสิทธิภาพ และสามารถ query ข้อมูลที่เกี่ยวข้องได้อย่างง่ายดาย ข้อควรระวังคือ เมื่อ Entity ฝั่ง "Many" มีจำนวนมาก อาจจะส่งผลต่อประสิทธิภาพในการ query ข้อมูลได้ ในกรณีนี้ อาจจะต้องพิจารณาการใช้ Pagination (การแบ่งหน้า) หรือ Indexing (การสร้างดัชนี) เพื่อเพิ่มประสิทธิภาพ

Many-to-Many Relationship (หลายต่อหลาย)

ความสัมพันธ์แบบ Many-to-Many คือความสัมพันธ์ที่หลาย Entity สามารถเชื่อมโยงกับอีกหลาย Entity ได้ ตัวอย่างเช่น:

  • Student กับ Course: นักเรียนหลายคน (Student) สามารถลงทะเบียนเรียนหลายคอร์ส (Course) ได้ และคอร์สหนึ่งคอร์สก็สามารถมีนักเรียนหลายคนลงทะเบียนได้
  • Product กับ Category: สินค้าหลายรายการ (Product) สามารถอยู่ในหลายหมวดหมู่ (Category) ได้ และหมวดหมู่หนึ่งหมวดก็สามารถมีสินค้าหลายรายการได้
  • Actor กับ Movie: นักแสดงหลายคน (Actor) สามารถเล่นในภาพยนตร์หลายเรื่อง (Movie) ได้ และภาพยนตร์หนึ่งเรื่องก็สามารถมีนักแสดงหลายคนได้

ในการกำหนดความสัมพันธ์แบบ Many-to-Many ใน ORM เรามักจะต้องใช้ Join Table (หรือบางครั้งเรียกว่า Pivot Table) เพื่อเก็บความสัมพันธ์ระหว่าง Entity ทั้งสอง Join Table จะมี Foreign Key สองตัว ซึ่งอ้างอิงไปยัง Primary Key ของ Entity ทั้งสอง

ตัวอย่าง (Doctrine):

// Entity Student
/**
 * @Entity
 * @Table(name="students")
 */
class Student
{
    /**
     * @Id
     * @GeneratedValue
     * @Column(type="integer")
     */
    private $id;

    /**
     * @Column(type="string", length=255)
     */
    private $name;

    /**
     * @ManyToMany(targetEntity="Course", inversedBy="students")
     * @JoinTable(name="student_courses",
     *      joinColumns={@JoinColumn(name="student_id", referencedColumnName="id")},
     *      inverseJoinColumns={@JoinColumn(name="course_id", referencedColumnName="id")}
     *      )
     */
    private $courses;

    public function __construct() {
        $this->courses = new \Doctrine\Common\Collections\ArrayCollection();
    }

    // ... getters and setters ...
}

// Entity Course
/**
 * @Entity
 * @Table(name="courses")
 */
class Course
{
    /**
     * @Id
     * @GeneratedValue
     * @Column(type="integer")
     */
    private $id;

    /**
     * @Column(type="string", length=255)
     */
    private $name;

    /**
     * @ManyToMany(targetEntity="Student", mappedBy="courses")
     */
    private $students;

    public function __construct() {
        $this->students = new \Doctrine\Common\Collections\ArrayCollection();
    }

    // ... getters and setters ...
}

ในตัวอย่างนี้ @ManyToMany annotation ใน Student Entity บอกว่า Student มีความสัมพันธ์แบบ Many-to-Many กับ Course Entity และ inversedBy="students" บอกว่าความสัมพันธ์นี้ถูกจัดการโดย students property ใน Course Entity และ @JoinTable บอกว่า Join Table ชื่อ student_courses มี student_id column เป็น Foreign Key ที่เชื่อมโยงกับ id column ใน students table และ course_id column เป็น Foreign Key ที่เชื่อมโยงกับ id column ใน courses table ส่วนใน Course Entity @ManyToMany annotation บอกว่า Course มีความสัมพันธ์แบบ Many-to-Many กับ Student Entity และ mappedBy="courses" บอกว่าความสัมพันธ์นี้ถูกจัดการโดย courses property ใน Student Entity

เหตุผลเชิงออกแบบและ Trade-offs: Many-to-Many Relationship เป็นความสัมพันธ์ที่ซับซ้อนที่สุด แต่ก็มีความยืดหยุ่นมากที่สุด ช่วยให้เราสร้างความสัมพันธ์ที่ซับซ้อนระหว่างข้อมูลได้อย่างมีประสิทธิภาพ การออกแบบ Join Table ที่ดีมีความสำคัญมาก เพื่อให้สามารถ query ข้อมูลได้อย่างรวดเร็ว และหลีกเลี่ยงปัญหา Data Integrity (ความถูกต้องของข้อมูล)

สรุป

ในบทนี้ เราได้เรียนรู้เกี่ยวกับประเภทของความสัมพันธ์ระหว่าง Entity ต่างๆ ในฐานข้อมูล และวิธีการจัดการความสัมพันธ์เหล่านี้ด้วย ORM (Doctrine ORM เป็นตัวอย่าง) การเข้าใจความสัมพันธ์เหล่านี้เป็นสิ่งสำคัญมากในการออกแบบฐานข้อมูลที่มีประสิทธิภาพ และสามารถจัดการข้อมูลได้อย่างถูกต้องและแม่นยำ ในบทต่อไป เราจะมาเรียนรู้วิธีการสร้าง Query อย่างยืดหยุ่นด้วย ORM โดยใช้ Query Builder กันครับ

Office.in.th Custom Service

想基于此结构开发或定制系统?

Office.in.th 承接企业级 Web Application、API Integration 以及政府/企业管理系统的设计与开发,并提供长期支持。

查看软件开发服务