วิธีที่เร็วที่สุดในการรับวัตถุแรกจากชุดแบบสอบถามใน django หรือไม่?


193

บ่อยครั้งที่ฉันพบว่าตัวเองต้องการรับวัตถุแรกจากชุดสืบค้นใน Django หรือกลับมาNoneถ้าไม่มี มีหลายวิธีในการทำเช่นนี้ซึ่งทั้งหมดทำงานได้ แต่ฉันสงสัยว่าอันไหนที่มีประสิทธิภาพดีที่สุด

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

สิ่งนี้ส่งผลให้มีการเรียกฐานข้อมูลสองสายหรือไม่ ดูเหมือนว่าสิ้นเปลือง เร็วกว่าไหม?

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

ตัวเลือกอื่นจะเป็น:

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

สิ่งนี้สร้างการเรียกฐานข้อมูลเดียวซึ่งดี แต่ต้องมีการสร้างออบเจ็กต์ข้อยกเว้นเป็นจำนวนมากซึ่งเป็นสิ่งที่ต้องใช้ความจำอย่างมากเมื่อทุกสิ่งที่คุณต้องการคือการทดสอบที่ไม่สำคัญ

ฉันจะทำสิ่งนี้ได้ด้วยการโทรฐานข้อมูลเดียวและไม่ต้องปั่นหน่วยความจำด้วยวัตถุยกเว้น?


21
กฎของหัวแม่มือ: หากคุณกำลังกังวลเกี่ยวกับการลด DB รอบการเดินทางไม่ได้ใช้ในชุดข้อความใช้เสมอlen() .count()
Daniel DiPaolo

7
"การสร้างออบเจ็กต์ข้อผิดพลาดบ่อยครั้งซึ่งเป็นเรื่องที่ต้องใช้ความจำอย่างมาก" - หากคุณกังวลเกี่ยวกับการสร้างข้อยกเว้นพิเศษอีกข้อหนึ่งคุณจะทำสิ่งนั้นผิดเพราะ Python ใช้ข้อยกเว้นทั้งหมด คุณใช้เบนช์มาร์กจริง ๆ หรือไม่ว่าในกรณีของคุณ
lqc

1
@Lopopd และถ้าคุณต้องการเปรียบเทียบ anwser ในทางใดทางหนึ่ง (หรืออย่างน้อยความคิดเห็น) คุณจะรู้ว่ามันไม่เร็วขึ้น ที่จริงแล้วอาจช้าลง 'ทำให้การสร้างรายการพิเศษของคุณเป็นการโยนทิ้ง และทั้งหมดนั้นก็แค่ถั่วลิสงเมื่อเทียบกับค่าใช้จ่ายในการเรียกใช้ฟังก์ชั่นหลามหรือใช้ ORM ของ Django ในตอนแรก! การเรียกใช้ตัวกรองเดี่ยว () เป็นจำนวนมากช้ากว่าหลาย ๆครั้งแล้วยกระดับข้อยกเว้น
lqc

1
สัญชาตญาณของคุณถูกต้องว่าความแตกต่างด้านประสิทธิภาพนั้นเล็ก แต่ข้อสรุปของคุณผิด ฉันใช้มาตรฐานและคำตอบที่ยอมรับได้จริงเร็วกว่าด้วยอัตรากำไรที่แท้จริง ไปคิด
Leopd

11
สำหรับคนที่ใช้ Django 1.6 ในที่สุดพวกเขาก็เพิ่มวิธีการfirst()และlast()ความสะดวกสบาย: docs.djangoproject.com/en/dev/ref/models/querysets/#first
Wei Yen

คำตอบ:


328

Django 1.6 (เปิดตัวพฤศจิกายน 2013)เปิดตัววิธีการอำนวยความสะดวก first()และlast()ที่กลืนยกเว้นที่เกิดและผลตอบแทนNoneถ้า queryset ส่งกลับไม่มีวัตถุ


2
มันไม่ได้ทำ [: 1] ดังนั้นจึงไม่เร็ว (เว้นแต่คุณจะต้องประเมินชุดแบบสอบถามทั้งหมด)
janek37

13
นอกจากนี้first()และlast()บังคับใช้ORDER BYคำสั่งย่อยในแบบสอบถาม มันจะทำให้ผลการกำหนด แต่ส่วนใหญ่อาจจะช้าลงแบบสอบถาม
Phil Krylov

@ janek37 ไม่มีความแตกต่างของประสิทธิภาพ ตามที่ระบุโดย cod3monk3y มันเป็นวิธีที่สะดวกและไม่อ่านคิวรีทั้งหมด
Zompa

143

คำตอบที่ถูกต้องคือ

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

ซึ่งสามารถใช้ใน:

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

คุณไม่ต้องการเปลี่ยนเป็นรายการก่อนเพราะจะบังคับให้มีการเรียกฐานข้อมูลทั้งหมดของระเบียนทั้งหมด เพียงทำตามด้านบนและมันจะดึงครั้งแรกเท่านั้น คุณสามารถใช้งานได้.order_byเพื่อให้แน่ใจว่าคุณได้รับสิ่งที่ต้องการก่อน

ให้แน่ใจว่าได้เพิ่ม.get()หรืออื่น ๆ ที่คุณจะได้รับ QuerySet กลับและไม่ใช่วัตถุ


9
คุณยังคงต้องล้อมรอบด้วยการลอง ... ยกเว้น ObjectDoesNotExist ซึ่งเหมือนกับตัวเลือกที่สามต้นฉบับ แต่มีการแบ่งส่วน
Danny W. Adair

1
อะไรคือจุดของการตั้งค่า LIMIT ถ้าคุณจะโทรหารับ () ในที่สุด? ปล่อยให้ ORM และคอมไพเลอร์ SQL ตัดสินใจว่าอะไรดีที่สุดสำหรับแบ็กเอนด์ (ตัวอย่างเช่นบน Oracle Django emulate LIMIT จึงจะเจ็บแทนการช่วยเหลือ)
lqc

ฉันใช้คำตอบนี้โดยไม่ใช้. get () หากรายการถูกส่งคืนฉันจะส่งคืนองค์ประกอบแรกของรายการ
Keith John Hutchison

อะไรคือความแตกต่างของการมีEntry.objects.all()[0]??
James Lin

15
@JamesLin ความแตกต่างคือ [: 1] .get () ทำให้ DoesNotExist เพิ่มในขณะที่ [0] จะเพิ่ม IndexError
Ropez

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

1
หากคุณเปิดใช้การติดตามฉันค่อนข้างแน่ใจว่าคุณจะเห็นสิ่งนี้เพิ่มLIMIT 1ในแบบสอบถามและฉันไม่รู้ว่าคุณสามารถทำได้ดีกว่านี้ อย่างไรก็ตามภายใน__nonzero__ในการQuerySetจะดำเนินการเป็นtry: iter(self).next() except StopIteration: return false...จึงไม่ได้หลบหนีข้อยกเว้น
เบ็คแจ็คสัน

@Ben: QuerySet.__nonzero__()ไม่เคยถูกเรียกเนื่องจากQuerySetถูกแปลงเป็น a listก่อนตรวจสอบความจริง อย่างไรก็ตามอาจมีข้อยกเว้นอื่น ๆ
Ignacio Vazquez-Abrams

@Aron: นั่นสามารถสร้างStopIterationข้อยกเว้น
Ignacio Vazquez-Abrams

การแปลงไปยังรายการ === เรียก__iter__เพื่อรับวัตถุตัววนซ้ำใหม่และเรียกมันว่าnextวิธีการจนกว่าStopIterationจะถูกโยน ดังนั้นจะมีข้อยกเว้นอย่างแน่นอนอยู่ที่ไหนสักแห่ง;)
lqc

14
คำตอบนี้ล้าสมัยแล้วลองดูที่ @ cod3monk3y คำตอบสำหรับ Django 1.6+
ValAyal

37

ตอนนี้ใน Django 1.9 คุณมีfirst() วิธีการตั้งค่าแบบสอบถาม

YourModel.objects.all().first()

นี่เป็นวิธีที่ดีกว่า.get()หรือ[0]เพราะมันไม่ทำให้เกิดข้อยกเว้นถ้าชุดแบบสอบถามว่างเปล่าเทอร์เฟอร์คุณไม่จำเป็นต้องตรวจสอบการใช้exists()


1
สิ่งนี้ทำให้ LIMIT 1 ใน SQL และฉันเคยเห็นการอ้างว่าสามารถทำให้คิวรีช้าลง - แม้ว่าฉันต้องการเห็นการยืนยัน: ถ้าเคียวรีคืนค่าหนึ่งไอเท็มทำไม LIMIT 1 ควรส่งผลต่อประสิทธิภาพจริงๆ? ดังนั้นฉันคิดว่าคำตอบข้างต้นนั้นใช้ได้ แต่ชอบที่จะเห็นหลักฐานยืนยัน
ruuenza

ฉันจะไม่พูดว่า "ดีกว่า" มันขึ้นอยู่กับความคาดหวังของคุณ
ตรีโกณมิติ

7

หากคุณวางแผนที่จะรับองค์ประกอบแรกบ่อยๆ - คุณสามารถขยาย QuerySet ในทิศทางนี้:

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


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

กำหนดรูปแบบเช่นนี้:

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

และใช้มันเช่นนี้

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

เรียกวัตถุ = ManagerWithFirstQuery เป็นวัตถุ = ManagerWithFirstQuery () - อย่าลืมความจำเป็น - อย่างไรก็ตามคุณช่วยฉันได้ +1 +1
Kamil

7

สิ่งนี้สามารถทำงานได้เช่นกัน:

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

ถ้ามันว่างเปล่าแล้วส่งกลับรายการที่ว่างเปล่ามิฉะนั้นจะส่งกลับองค์ประกอบแรกภายในรายการ


1
นี่คือทางออกที่ดีที่สุด ... ส่งผลให้โทรไปยังฐานข้อมูลเดียวเท่านั้น
Shh


3

คุณควรใช้วิธี django เช่นที่มีอยู่ มันมีให้คุณใช้

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

1
ยกเว้นถ้าผมเข้าใจเพื่อให้ถูกต้องสำนวนงูใหญ่มักจะใช้ง่ายต่อการขอการให้อภัยกว่าอนุญาต ( EAFP ) วิธีการมากกว่าLook ก่อน Leapวิธี
BigSmoke

EAFP ไม่ได้เป็นเพียงการแนะนำสไตล์ แต่มีเหตุผล (ตัวอย่างเช่นการตรวจสอบก่อนเปิดไฟล์ไม่ได้ป้องกันข้อผิดพลาด) ที่นี่ฉันคิดว่าการพิจารณาที่เกี่ยวข้องคือที่มีอยู่ + รับรายการทำให้เกิดแบบสอบถามฐานข้อมูลที่สองซึ่งอาจไม่พึงประสงค์ขึ้นอยู่กับโครงการและมุมมอง
Éric Araujo

2

ตั้งแต่ django 1.6 คุณสามารถใช้ filter () กับวิธีแรก () ดังนี้:

Model.objects.filter(field_name=some_param).first()
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.