Cách nhanh nhất để có được đối tượng đầu tiên từ một bộ truy vấn trong django?


193

Thường thì tôi thấy mình muốn lấy đối tượng đầu tiên từ một bộ truy vấn trong Django hoặc quay trở lại None nếu không có nào. Có rất nhiều cách để làm điều này mà tất cả đều hoạt động. Nhưng tôi tự hỏi đó là hiệu suất cao nhất.

qs = MyModel.objects.filter(blah = blah)
if qs.count() > 0:
    return qs[0]
else:
    return None

Liệu kết quả này trong hai cuộc gọi cơ sở dữ liệu? Điều đó có vẻ lãng phí. Đây có phải là nhanh hơn?

qs = MyModel.objects.filter(blah = blah)
if len(qs) > 0:
    return qs[0]
else:
    return None

Một lựa chọn khác sẽ là:

qs = MyModel.objects.filter(blah = blah)
try:
    return qs[0]
except IndexError:
    return None

Điều này tạo ra một cuộc gọi cơ sở dữ liệu duy nhất, đó là tốt. Nhưng đòi hỏi phải tạo ra một đối tượng ngoại lệ rất nhiều thời gian, đó là một việc rất tốn bộ nhớ để làm khi tất cả những gì bạn thực sự cần là một bài kiểm tra if tầm thường.

Làm thế nào tôi có thể làm điều này chỉ với một cuộc gọi cơ sở dữ liệu duy nhất và không có bộ nhớ với các đối tượng ngoại lệ?


21
Nguyên tắc nhỏ: Nếu bạn lo lắng về việc giảm thiểu các chuyến đi khứ hồi DB, đừng sử dụng len()trên các truy vấn, luôn luôn sử dụng .count().
Daniel DiPaolo

7
"Tạo một đối tượng ngoại lệ rất nhiều thời gian, đó là một việc rất tốn bộ nhớ" - nếu bạn lo lắng về việc tạo thêm một ngoại lệ, thì bạn đã làm sai khi Python sử dụng ngoại lệ ở mọi nơi. Bạn đã thực sự điểm chuẩn rằng nó chiếm nhiều bộ nhớ trong trường hợp của bạn?
lqc

1
@Leopd Và nếu bạn thực sự đã chấm điểm anwser theo bất kỳ cách nào (hoặc ít nhất là các bình luận), bạn sẽ biết nó không nhanh hơn. Nó thực sự có thể chậm hơn, vì bạn tạo ra một danh sách bổ sung chỉ để loại bỏ nó. Và tất cả chỉ là đậu phộng so với chi phí gọi hàm python hoặc sử dụng ORM của Django ngay từ đầu! Một lệnh gọi bộ lọc () chậm hơn rất nhiều lần, sau đó đưa ra một ngoại lệ (vẫn sẽ được đưa ra, vì đó là cách giao thức lặp hoạt động!).
lqc

1
Trực giác của bạn là chính xác rằng sự khác biệt hiệu suất là nhỏ, nhưng kết luận của bạn là sai. Tôi đã chạy một điểm chuẩn và câu trả lời được chấp nhận trên thực tế nhanh hơn bởi một tỷ lệ thực. Đi hình.
Leopd

11
Đối với những người sử dụng Django 1.6, cuối cùng họ đã thêm các phương thức first()last()tiện lợi: docs.djangoproject.com/en/dev/ref/models/querysets/#first
Wei Yen

Câu trả lời:


326

Django 1.6 (phát hành tháng 11 năm 2013) đã giới thiệu các phương thức tiện lợi first()last()nuốt ngoại lệ kết quả và trả về Nonenếu bộ truy vấn không trả về đối tượng.


1
nó không thực hiện [: 1], vì vậy nó không nhanh như vậy (trừ khi bạn cần đánh giá toàn bộ bộ truy vấn bằng mọi cách).
janek37

13
Ngoài ra, first()last()thực thi một ORDER BYđiều khoản trên một truy vấn. Nó sẽ làm cho kết quả xác định nhưng rất có thể sẽ làm chậm truy vấn.
Phil Krylov

@ janek37 không có sự khác biệt về hiệu suất. Như được chỉ định bởi cod3monk3y, đây là một phương pháp thuận tiện và nó không đọc toàn bộ bộ truy vấn.
Zompa

141

Đáp án đúng là

Entry.objects.all()[:1].get()

Mà có thể được sử dụng trong:

Entry.objects.filter()[:1].get()

Trước tiên, bạn không muốn biến nó thành một danh sách vì điều đó sẽ buộc một cuộc gọi cơ sở dữ liệu đầy đủ của tất cả các bản ghi. Chỉ cần làm như trên và nó sẽ chỉ kéo đầu tiên. Bạn thậm chí có thể sử dụng.order_by để đảm bảo bạn có được thứ bạn muốn đầu tiên.

Hãy chắc chắn để thêm .get()hoặc nếu không bạn sẽ nhận được một Truy vấn và không phải là một đối tượng.


9
Bạn vẫn sẽ cần phải bọc nó trong một thử ... ngoại trừ ObjectDoesNotExist, giống như tùy chọn thứ ba ban đầu nhưng có cắt.
Daniel W. Adair

1
Điểm đặt giới hạn là gì nếu cuối cùng bạn sẽ gọi get ()? Hãy để ORM và trình biên dịch SQL quyết định những gì tốt nhất cho phần phụ trợ của nó (ví dụ: trên Oracle Django mô phỏng LIMIT, vì vậy nó sẽ bị tổn thương thay vì giúp đỡ).
lqc

Tôi đã sử dụng câu trả lời này mà không có dấu .get (). Nếu một danh sách được trả về tôi sau đó trả về phần tử đầu tiên của danh sách.
Keith John Hutchison

Có gì khác nhau Entry.objects.all()[0]?
James Lin

15
@JamesLin Sự khác biệt là [: 1] .get () tăng DoesNotExist, trong khi [0] tăng IndexError.
Ropez

49
r = list(qs[:1])
if r:
  return r[0]
return None

1
Nếu bạn bật theo dõi tôi chắc chắn bạn thậm chí sẽ thấy điều này thêm LIMIT 1vào truy vấn và tôi không biết rằng bạn có thể làm tốt hơn thế này. Tuy nhiên, trong nội bộ __nonzero__QuerySetđược thực hiện như try: iter(self).next() except StopIteration: return false...vì vậy nó không thoát khỏi ngoại lệ.
Ben Jackson

@Ben: QuerySet.__nonzero__()không bao giờ được gọi vì QuerySetđược chuyển đổi thành a listtrước khi kiểm tra tính xác thực. Tuy nhiên, các trường hợp ngoại lệ khác vẫn có thể xảy ra.
Ignacio Vazquez-Abrams

@Aron: Điều đó có thể tạo ra một StopIterationngoại lệ.
Ignacio Vazquez-Abrams

chuyển đổi sang danh sách === cuộc gọi __iter__để có được một đối tượng lặp mới và gọi nextphương thức của nó cho đến khi StopIterationđược ném. Vì vậy, chắc chắn sẽ có một ngoại lệ ở đâu đó;)
lqc

14
Câu trả lời này hiện đã lỗi thời, hãy xem câu trả lời @ cod3monk3y cho Django 1.6+
ValAyal

37

Bây giờ, trong Django 1.9, bạn có first() phương thức cho các truy vấn.

YourModel.objects.all().first()

Đây là một cách tốt hơn .get()hoặc [0]bởi vì nó không đưa ra một ngoại lệ nếu bộ truy vấn trống, Therafore, bạn không cần phải kiểm tra bằng cách sử dụngexists()


1
Điều này gây ra GIỚI HẠN 1 trong SQL và tôi đã thấy các khiếu nại rằng nó có thể khiến truy vấn chậm hơn - mặc dù tôi muốn thấy điều đó được chứng minh: Nếu truy vấn chỉ trả về một mục, tại sao LIMIT 1 thực sự ảnh hưởng đến hiệu suất? Vì vậy, tôi nghĩ rằng câu trả lời trên là tốt, nhưng rất thích xem bằng chứng xác nhận.
rrauenza

Tôi sẽ không nói "tốt hơn". Nó thực sự phụ thuộc vào sự mong đợi của bạn.
trigras

7

Nếu bạn có kế hoạch lấy phần tử đầu tiên thường xuyên - bạn có thể mở rộng Truy vấn theo hướng này:

class FirstQuerySet(models.query.QuerySet):
    def first(self):
        return self[0]


class ManagerWithFirstQuery(models.Manager):
    def get_query_set(self):
        return FirstQuerySet(self.model)

Xác định mô hình như thế này:

class MyModel(models.Model):
    objects = ManagerWithFirstQuery()

Và sử dụng nó như thế này:

 first_object = MyModel.objects.filter(x=100).first()

Gọi các đối tượng = ManagerWithFirstQuery là các đối tượng = ManagerWithFirstQuery () - DON FORGET PARENTHESES - dù sao, bạn đã giúp tôi như vậy +1
Kamil

7

Điều này cũng có thể làm việc:

def get_first_element(MyModel):
    my_query = MyModel.objects.all()
    return my_query[:1]

nếu nó trống, sau đó trả về một danh sách trống, nếu không nó sẽ trả về phần tử đầu tiên trong danh sách.


1
Đây là giải pháp tốt nhất ... kết quả chỉ trong một cuộc gọi đến cơ sở dữ liệu
Shh

5

Nó có thể như thế này

obj = model.objects.filter(id=emp_id)[0]

hoặc là

obj = model.objects.latest('id')

3

Bạn nên sử dụng các phương pháp django, giống như tồn tại. Nó ở đó để bạn sử dụng nó.

if qs.exists():
    return qs[0]
return None

1
Ngoại trừ, nếu tôi hiểu chính xác như vậy, Python thành ngữ thường sử dụng cách tiếp cận dễ dàng hơn để yêu cầu sự tha thứ so với sự cho phép ( EAFP ) thay vì cách tiếp cận Look Before You Leap .
BigSmoke

EAFP không chỉ là một đề xuất về phong cách, nó có lý do (ví dụ: kiểm tra trước khi mở tệp không ngăn ngừa lỗi). Ở đây tôi nghĩ rằng sự cân nhắc có liên quan là tồn tại + get item gây ra hai truy vấn cơ sở dữ liệu, có thể không mong muốn tùy thuộc vào dự án và chế độ xem.
Éric Araujo

2

Vì django 1.6, bạn có thể sử dụng bộ lọc () với phương thức First () như vậy:

Model.objects.filter(field_name=some_param).first()
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.