从Django的queryset获取第一个对象的最快方法?


192

我经常发现自己想要从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

这样会生成一个数据库调用,这很好。但是需要很多时间创建一个异常对象,当您真正需要的只是一个琐碎的if-test时,这是一项非常占用内存的工作。

我该如何仅用一个数据库调用就可以做到这一点,而又不浪费带有异常对象的内存?


21
经验法则:如果您担心最小化数据库往返,请不要len()在querysets上使用,请始终使用.count()
Daniel DiPaolo 2011年

7
“很多时候创建一个异常对象,这是非常耗费内存的事情”-如果您担心创建一个额外的异常,那么您做错了,因为Python到处都使用异常。您是否实际针对您的情况进行了基准测试,确定它占用大量内存?
lqc 2012年

1
@Leopd如果您实际上以任何方式(或至少是注释)对基准进行了基准测试,您会知道它并没有更快。实际上,它可能会比较慢,因为您创建了一个额外的列表只是为了将其丢弃。与调用python函数或首先使用Django的ORM的成本相比,这仅仅是花生米!一次调用filter()会比引发异常慢很多很多倍(仍然会引发异常,因为这就是迭代器协议的工作原理!)。
lqc

1
您的直觉是正确的,即性能差异很小,但是您的结论是错误的。我确实做了一个基准测试,实际上可以接受的答案要快得多。去搞清楚。
Leopd 2012年

11
对于使用Django 1.6的人们,他们终于添加了first()last()便捷方法:docs.djangoproject.com/en/dev/ref/models/querysets/#first
Wei Yen

Answers:


326

Django的1.6(发布2013年11月)介绍了方便的方法 first()last()其吞由此导致的异常和返回None在queryset不返回任何对象时返回。


1
它不执行[:1],所以它不那么快(除非您仍然需要评估整个查询集)。
janek37 '16

13
同时,first()last()强制执行ORDER BY的查询子句。它将使结果具有确定性,但很可能会降低查询速度。
Phil Krylov

@ janek37在性能上没有差异。如cod3monk3y所示,这是一种方便的方法,它不会读取整个查询集。
Zompa

141

正确答案是

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

可用于:

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

您不希望先将其转换为列表,因为这将强制对所有记录进行完整的数据库调用。只需执行以上操作,它只会拉第一个。您甚至可以.order_by用来确保获得想要的第一个。

确保添加,.get()否则您将获得一个QuerySet而不是一个对象。


9
您仍然需要尝试将其包装起来...除了ObjectDoesNotExist,它类似于原始的第三个选项,但具有切片功能。
Danny W. Adair'3

1
如果最后要调用get(),设置LIMIT有什么意义?让ORM和SQL编译器确定最适合后端的方法(例如,在Oracle上Django模拟LIMIT,因此会不利于帮助而不是帮助)。
lqc 2012年

我使用此答案时没有结尾的.get()。如果返回列表,则返回列表的第一个元素。
基思·约翰·哈奇森

有什么不同Entry.objects.all()[0]
詹姆士·林

15
@JamesLin的区别是[:1] .get()引发DidsNotExist,而[0]引发IndexError。
Ropez 2013年

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转换list为。但是,其他异常可能仍然会发生。
伊格纳西奥·巴斯克斯

@Aron:那会产生StopIteration异常。
伊格纳西奥·巴斯克斯

转换为list ===调用__iter__以获取新的迭代器对象,并调用其next方法直到StopIteration抛出该对象。因此,肯定在某个地方会有例外;)
lqc 2012年

14
这个答案现在已经过时了,看看Django
1.6+的

37

现在,在Django 1.9中,您有了first() 用于查询集的方法。

YourModel.objects.all().first()

这是比.get()或更好的方法,[0]因为如果queryset为空,它不会引发异常。因此,您不需要使用exists()


1
这会在SQL中导致LIMIT 1,并且我已经看到声称它可以使查询变慢-尽管我希望看到这种说法得到证实:如果查询仅返回一项,为什么LIMIT 1真正影响性能?因此,我认为上述答案很好,但是希望看到证据可以证实。
rrauenza

我不会说“更好”。这真的取决于您的期望。
trigras

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
卡米尔

7

这也可以工作:

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

如果为空,则返回一个空列表,否则返回列表中的第一个元素。


1
到目前为止,这是最好的解决方案...仅对数据库发出一次调用
Shh 19'Jul


3

您应该使用django方法,就像存在一样。它在那里供您使用。

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

1
除非我理解得正确,否则惯用的Python通常会使用“ 比许可更容易获得宽恕”EAFP)的方法,而不是“ 先跳后看”的方法。
BigSmoke

EAFP不仅是样式建议,它还有原因(例如,在打开文件之前进行检查不能防止错误)。在这里,我认为相关的考虑是存在+获取项导致两个数据库查询,这可能是不希望的,具体取决于项目和视图。
埃里克·阿劳霍

2

从Django 1.6开始,您可以将filter()与first()方法一起使用,如下所示:

Model.objects.filter(field_name=some_param).first()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.