Khi chỉ cần một bản ghi duy nhất — bài viết mới nhất, đơn hàng đầu tiên, bình luận cuối cùng — đừng dùng order_by(...)[0] rồi tự xử lý trường hợp rỗng. Django có sẵn bốn phương thức first(), last(), latest() và earliest(): hai phương thức đầu trả về None khi không có bản ghi, hai phương thức sau dựa vào một trường ngày giờ để lấy bản ghi mới nhất hoặc cũ nhất, và báo lỗi rõ ràng khi không tìm thấy.
Vấn đề
Cách quen thuộc nhất để lấy một bản ghi là cắt queryset bằng chỉ số: Post.objects.order_by('-created_at')[0]. Cách này chạy đúng khi bảng có dữ liệu, nhưng ném IndexError ngay khi queryset rỗng — lỗi thường chỉ xuất hiện ở production, khi một chuyên mục mới chưa có bài nào hoặc sau khi dữ liệu bị xóa. Một lỗi khác hay gặp hơn: gọi latest() mà quên khai báo trường ngày giờ, Django ném ValueError với thông báo yêu cầu truyền tên trường hoặc khai báo get_latest_by trong Meta.
Code lỗi: dùng [0] và latest() không khai báo trường
# Lỗi 1: IndexError khi chuyên mục chưa có bài nàonewest = Post.objects.filter(category="django").order_by("-created_at")[0]# Lỗi 2: ValueError vì model chưa khai báo get_latest_bynewest = Post.objects.filter(category="django").latest()
Giải pháp
first() và last() là cách an toàn nhất khi bản ghi có thể không tồn tại: chúng trả về None thay vì ném lỗi, nên bạn chỉ cần một câu if để xử lý. first() lấy bản ghi đầu tiên theo thứ tự hiện tại của queryset, last() lấy bản ghi cuối cùng — muốn kết quả ổn định thì luôn gọi order_by() trước, đừng ỷ vào thứ tự mặc định của database.
Code đúng: first() trả None khi rỗng
newest = Post.objects.filter(category="django").order_by("-created_at").first()if newest is None: return render(request, "empty.html")oldest = Post.objects.filter(category="django").order_by("created_at").first()
Khi cần “mới nhất theo ngày giờ”, latest() và earliest() rõ nghĩa hơn: chúng nhận tên trường làm tham số, hoặc dùng get_latest_by khai báo trong Meta của model. Khác với first(), hai phương thức này ném DoesNotExist khi không có bản ghi nào — hợp lý khi bạn chắc chắn dữ liệu phải tồn tại, vì lỗi lộ ra ngay thay vì lan thành None ở xa.
Code đúng: latest() và earliest() với get_latest_by
class Post(models.Model): title = models.CharField(max_length=200) created_at = models.DateTimeField(auto_now_add=True) class Meta: get_latest_by = "created_at"newest = Post.objects.filter(category="django").latest()oldest = Post.objects.filter(category="django").earliest()# Tương đương: latest("created_at") và earliest("created_at")
Áp dụng vào dự án của bạn
Quy tắc chọn nhanh: dữ liệu có thể rỗng — trang chuyên mục, trang cá nhân của user mới — thì dùng first() kèm kiểm tra None; dữ liệu bắt buộc phải có — bài mới nhất trên trang chủ, đơn hàng gần nhất trong báo cáo — thì dùng latest() để lỗi lộ sớm. Và hãy khai báo get_latest_by trong Meta ngay khi tạo model có trường ngày giờ: một dòng này giúp mọi chỗ gọi latest() trong codebase ngắn gọn và thống nhất.
Bài viết liên quan
- Django: lấy object an toàn bằng get_object_or_404, khỏi viết try/except DoesNotExist trong 5 phút
- Django: phân trang danh sách bằng Paginator trong 5 phút, khỏi viết tay offset/limit
- Django: thống kê theo nhóm bằng values() + annotate() — GROUP BY trong 1 query
Câu hỏi thường gặp
first() khác gì [0]?
[0] ném IndexError khi queryset rỗng, còn first() trả về None. Cả hai đều chỉ lấy một bản ghi từ database (thêm LIMIT 1), nên hiệu năng tương đương.
last() có phải tải toàn bộ bảng về không?
Không. Django đảo ngược thứ tự rồi lấy một bản ghi, tương đương ORDER BY ... DESC LIMIT 1 ở tầng SQL. Nhưng hãy luôn order_by() trước để biết chắc “cuối cùng” là gì.
latest() và order_by(‘-created_at’).first() khác nhau thế nào?
latest() ném DoesNotExist khi không có bản ghi, còn first() trả None. Ngoài ra latest() cần một trường ngày giờ (tham số hoặc get_latest_by), còn first() dùng thứ tự hiện tại của queryset.
Dùng latest() với trường không phải ngày giờ được không?
Được, Django chấp nhận mọi trường có thứ tự như số hay chuỗi, nhưng chỉ nên dùng với DateField hoặc DateTimeField; với trường khác, tên phương thức dễ gây hiểu lầm.
💬 Bạn đã thử cách này trong dự án của mình chưa? Nếu gặp lỗi hoặc có cách làm gọn hơn, để lại bình luận bên dưới — mình đọc hết và sẽ bổ sung những ý hay vào bài.
Nguồn tham khảo
- Django documentation, QuerySet API reference — first() / last(): https://docs.djangoproject.com/en/6.1/ref/models/querysets/#first
- Django documentation, QuerySet API reference — latest() / earliest(): https://docs.djangoproject.com/en/6.1/ref/models/querysets/#latest
- Django documentation, Model Meta options — get_latest_by: https://docs.djangoproject.com/en/6.1/ref/models/options/#get-latest-by
