Thông báo: Download 4 khóa học Python từ cơ bản đến nâng cao tại đây.
Mối quan hệ One-To-Many trong Django với Python
Một trong những mối quan hệ phổ biến nhất là mối quan hệ một-nhiều, nơi một bản ghi trong một bảng có thể liên kết với nhiều bản ghi trong bảng khác. Ví dụ điển hình của mối quan hệ này là giữa phòng ban và nhân viên, nơi một phòng ban có thể có nhiều nhân viên nhưng mỗi nhân viên chỉ thuộc về một phòng ban. Trong bài viết này, mình sẽ tìm hiểu cách sử dụng ForeignKey trong Django để thiết lập và làm việc với mối quan hệ một-nhiều một cách hiệu quả.
![banquyen png](https://freetuts.net/public/banquyen.png)
Mối quan hệ One-To-Many là gì?
Trong một mối quan hệ một-nhiều, một hàng trong một bảng được liên kết với một hoặc nhiều hàng trong một bảng khác. Ví dụ, một phòng ban có thể có một hoặc nhiều nhân viên và mỗi nhân viên thuộc về một phòng ban.
Mối quan hệ giữa các phòng ban và nhân viên là một mối quan hệ một-nhiều. Ngược lại, mối quan hệ giữa nhân viên và các phòng ban là một mối quan hệ nhiều-một.
Để tạo một mối quan hệ một-nhiều trong Django, bạn sử dụng ForeignKey. Ví dụ, đoạn mã sau đây sử dụng ForeignKey để tạo một mối quan hệ một-nhiều giữa các mô hình Department và Employee:
Bài viết này được đăng tại [free tuts .net]
from django.db import models class Contact(models.Model): phone = models.CharField(max_length=50, unique=True) address = models.CharField(max_length=50) def __str__(self): return self.phone #Bài viết này được đăng tại freetuts.net class Department(models.Model): name = models.CharField(max_length=255) description = models.TextField(null=True, blank=True) #Bài viết này được đăng tại freetuts.net def __str__(self): return self.name class Employee(models.Model): first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100) contact = models.OneToOneField( Contact, on_delete=models.CASCADE, null=True ) #Bài viết này được đăng tại freetuts.net department = models.ForeignKey( Department, on_delete=models.CASCADE ) def __str__(self): return f'{self.first_name} {self.last_name}'
Cách hoạt động
Đầu tiên, định nghĩa lớp mô hình Department.
Thứ hai, sửa đổi lớp Employee bằng cách thêm mối quan hệ một-nhiều sử dụng ForeignKey:
department = models.ForeignKey( Department, on_delete=models.CASCADE )
Trong ForeignKey, mình truyền Department là đối số đầu tiên và đối số từ khóa on_delete
là models.CASCADE.
on_delete=models.CASCADE
chỉ ra rằng nếu một phòng ban bị xóa, tất cả các nhân viên liên kết với phòng ban đó cũng sẽ bị xóa.
Lưu ý rằng bạn định nghĩa trường ForeignKey ở phía “nhiều” của mối quan hệ.
Tạo lệnh Migrations trong Django
Sử dụng lệnh makemigrations
:
python manage.py makemigrations
Django sẽ đưa ra thông báo sau:
It is impossible to add a non-nullable field 'department' to employee without specifying a default. This is because the database needs something to populate existing rows. Please select a fix: 1) Provide a one-off default now (will be set on all existing rows with a null value for this column) 2) Quit and manually define a default value in models.py. Select an option:
Trong mô hình Employee, mình định nghĩa trường department là một trường không thể null. Do đó, Django cần một giá trị mặc định để cập nhật trường department (hoặc cột department_id) cho các hàng hiện có trong bảng cơ sở dữ liệu.
Ngay cả khi bảng hr_employees
không có hàng nào, Django cũng yêu cầu bạn cung cấp một giá trị mặc định.
Như đã hiển thị rõ ràng trong thông báo, Django cung cấp cho bạn hai tùy chọn. Bạn cần nhập 1 hoặc 2 để chọn tùy chọn tương ứng.
Trong tùy chọn đầu tiên, Django yêu cầu một giá trị mặc định tạm thời. Nếu bạn nhập 1, thì Django sẽ hiển thị dòng lệnh Python:
>>> None
Sau khi bạn cung cấp một giá trị mặc định, Django sẽ tạo các migrations như sau:
Migrations for 'hr': hr\migrations\0002_department_employee_department.py - Create model Department - Add field department to employee
Trong cơ sở dữ liệu, Django tạo bảng hr_department
và thêm cột department_id
vào bảng hr_employee
. Cột department_id
của bảng hr_employee
liên kết với cột id của bảng hr_department
.
Nếu bạn chọn tùy chọn thứ hai bằng cách nhập số 2, Django sẽ cho phép bạn tự định nghĩa giá trị mặc định cho trường department. Trong trường hợp này, bạn có thể thêm giá trị mặc định vào trường department trong models.py như sau:
department = models.ForeignKey( Department, #Bài viết này được đăng tại freetuts.net on_delete=models.CASCADE, default=None )
Sau đó, bạn có thể tạo các migrations sử dụng lệnh makemigrations
:
python manage.py makemigrations
Nó sẽ hiển thị đầu ra sau:
Migrations for 'hr': hr\migrations\0002_department_employee_department.py - Create model Department - Add field department to employee
Nếu bảng hr_employee
có bất kỳ hàng nào, bạn cần xóa tất cả trước khi di chuyển các migrations mới:
python manage.py shell_plus >>> Employee.objects.all().delete()
Sau đó, bạn có thể thực hiện các thay đổi vào cơ sở dữ liệu bằng lệnh migrate:
python manage.py migrate
Đầu ra:
Operations to perform: Apply all migrations: admin, auth, contenttypes, hr, sessions Running migrations: Applying hr.0002_department_employee_department... OK
Một cách khác để xử lý vấn đề này là đặt lại migrations, mình sẽ đề cập trong hướng dẫn đặt lại migrations.
Tương tác với models trong Django
Đầu tiên, chạy lệnh shell_plus
:
python manage.py shell_plus
Thứ hai, tạo một phòng ban mới với tên IT:
>>> d = Department(name='IT', description='Information Technology') >>> d.save()
Tạo hai nhân viên và gán họ vào phòng ban IT:
>>> e = Employee(first_name='freetuts', last_name='.net', department=d) >>> e.save() >>> e = Employee(first_name='.net', last_name='freetut', department=d) >>> e.save()
Truy cập đối tượng phòng ban từ đối tượng nhân viên:
>>> e.department <Department: IT> >>> e.department.description 'Information Technology'
Truy cập đối tượng phòng ban từ đối tượng nhân viên:
>>> e.department <Department: IT> >>> e.department.description 'Information Technology'
Lấy tất cả các nhân viên của một phòng ban sử dụng thuộc tính employee_set
như sau:
>>> d.employee_set.all() <QuerySet [<Employee: freetuts.net>, <Employee: Jane Doe>]>
Lấy tất cả các nhân viên của một phòng ban sử dụng thuộc tính employee_set
như sau:
>>> d.employee_set.all() <QuerySet [<Employee: John Doe>, <Employee: .net freetuts>]>
Lưu ý rằng mình không định nghĩa thuộc tính employee_set
trong mô hình Department. Nội bộ, Django tự động thêm thuộc tính employee_set
vào mô hình Department khi mình định nghĩa mối quan hệ một-nhiều sử dụng ForeignKey.
Phương thức all()
của employee_set
trả về một QuerySet chứa tất cả các nhân viên thuộc về phòng ban.
Sử dụng select_related()
để join các bảng trong Django
Thoát khỏi shell_plus và chạy lại. Lần này, mình thêm tùy chọn --print-sql
để hiển thị SQL được tạo mà Django sẽ thực thi đối với cơ sở dữ liệu:
python manage.py shell_plus --print-sql
Đoạn mã sau trả về nhân viên đầu tiên:
>>> e = Employee.objects.first() SELECT "hr_employee"."id", "hr_employee"."first_name", "hr_employee"."last_name", "hr_employee"."contact_id", "hr_employee"."department_id" FROM "hr_employee" #Bài viết này được đăng tại freetuts.net ORDER BY "hr_employee"."id" ASC LIMIT 1 Execution time: 0.003000s [Database: default]
Để truy cập phòng ban của nhân viên đầu tiên, bạn sử dụng thuộc tính department
:
>>> e.department SELECT "hr_department"."id", "hr_department"."name", "hr_department"."description" FROM "hr_department" WHERE "hr_department"."id" = 1 LIMIT 21 #Bài viết này được đăng tại freetuts.net Execution time: 0.013211s [Database: default] <Department: IT>
Trong trường hợp này, Django thực thi hai truy vấn. Truy vấn đầu tiên chọn nhân viên đầu tiên và truy vấn thứ hai chọn phòng ban của nhân viên được chọn.
Nếu bạn chọn N nhân viên để hiển thị trên một trang web, thì bạn cần thực thi N + 1 truy vấn để lấy cả nhân viên và phòng ban của họ. Truy vấn đầu tiên (1) chọn N nhân viên và N truy vấn chọn N phòng ban cho mỗi nhân viên. Vấn đề này được gọi là vấn đề truy vấn N + 1.
Để khắc phục vấn đề truy vấn N + 1, bạn có thể sử dụng phương thức select_related()
để chọn cả nhân viên và phòng ban sử dụng một truy vấn duy nhất. Ví dụ:
>>> Employee.objects.select_related('department').all() SELECT "hr_employee"."id", "hr_employee"."first_name", "hr_employee"."last_name", "hr_employee"."contact_id", "hr_employee"."department_id", "hr_department"."id", "hr_department"."name", "hr_department"."description" FROM "hr_employee" #Bài viết này được đăng tại freetuts.net INNER JOIN "hr_department" ON ("hr_employee"."department_id" = "hr_department"."id") LIMIT 21 Execution time: 0.012124s [Database: default] <QuerySet [<Employee: John Doe>, <Employee: .net freetuts>]>
Trong ví dụ này, Django chỉ thực thi một truy vấn để join bảng hr_employee
và hr_department
.
Kết bài
Trong một mối quan hệ một-nhiều, một hàng trong một bảng được liên kết với một hoặc nhiều hàng trong một bảng khác. Để thiết lập mối quan hệ này trong Django, bạn sử dụng trường ForeignKey. Điều này cho phép bạn dễ dàng mô hình hóa các mối quan hệ phức tạp giữa các bảng dữ liệu và duy trì tính toàn vẹn của cơ sở dữ liệu.
Khi định nghĩa ForeignKey, hãy nhớ đặt nó trong mô hình ở phía "nhiều" của mối quan hệ. Điều này đảm bảo rằng mỗi bản ghi ở phía "nhiều" có thể liên kết với một bản ghi ở phía "một".
Cuối cùng, để tối ưu hóa các truy vấn và tránh vấn đề N + 1, bạn nên sử dụng phương thức select_related()
. Phương thức này giúp join hai hoặc nhiều bảng trong mối quan hệ một-nhiều, giảm thiểu số lượng truy vấn cần thiết và tăng hiệu suất cho ứng dụng của bạn.
Bằng cách hiểu và áp dụng những kỹ thuật này, bạn sẽ có thể quản lý và khai thác dữ liệu một cách hiệu quả trong các dự án Django của mình.