从 django 的查询集中获取第一个对象的最快方法?
Posted
技术标签:
【中文标题】从 django 的查询集中获取第一个对象的最快方法?【英文标题】:Fastest way to get the first object from a queryset in django? 【发布时间】:2011-07-04 16:13:07 【问题描述】:我经常发现自己想从 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 测试时,这是一件非常耗费内存的事情。
我怎样才能只用一个数据库调用而不用异常对象搅动内存?
【问题讨论】:
经验法则:如果您担心最小化数据库往返,请不要在查询集上使用len()
,始终使用.count()
。
“经常创建异常对象,这是一件非常耗费内存的事情” - 如果您担心创建一个额外的异常,那么您做错了,因为 Python 使用到处都有例外。您是否真的对您的情况进行了基准测试,认为它是内存密集型的?
@Leopd 如果您实际上以任何方式(或至少是 cmets)对 anwser 进行了基准测试,您会知道它并没有更快。它实际上可能会更慢,因为你创建一个额外的列表只是为了把它扔掉。与调用 python 函数或首先使用 Django 的 ORM 的成本相比,所有这些都只是小菜一碟!对 filter() 的单次调用要比引发异常慢很多很多很多倍。
你的直觉是正确的,性能差异很小,但你的结论是错误的。我确实运行了一个基准测试,并且接受的答案实际上比实际速度更快。去图吧。
对于使用 Django 1.6 的人们,他们终于添加了 first()
和 last()
便利方法:docs.djangoproject.com/en/dev/ref/models/querysets/#first
【参考方案1】:
r = list(qs[:1])
if r:
return r[0]
return None
【讨论】:
如果您打开跟踪,我很确定您甚至会看到在查询中添加LIMIT 1
,而且我不知道您能做得比这更好。但是,QuerySet
中的 __nonzero__
在内部实现为 try: iter(self).next() except StopIteration: return false...
,因此它不会逃脱异常。
@Ben: QuerySet.__nonzero__()
永远不会被调用,因为QuerySet
在检查真实性之前会转换为list
。然而,其他例外情况仍可能发生。
@Aron:这会产生一个StopIteration
异常。
转换为列表 === 调用__iter__
来获取一个新的迭代器对象并调用它的next
方法,直到StopIteration
被抛出。所以肯定会有一个例外的地方;)
这个答案现在已经过时了,看看@cod3monk3y answer for Django 1.6+【参考方案2】:
你可以使用array slicing:
Entry.objects.all()[:1].get()
可以和.filter()
一起使用:
Entry.objects.filter()[:1].get()
您不希望先将其转换为列表,因为这会强制对所有记录进行完整的数据库调用。只需执行上述操作,它只会拉第一个。你甚至可以使用.order_by()
来确保你得到你想要的第一个。
请务必添加.get()
,否则您将得到QuerySet 而不是对象。
【讨论】:
您仍然需要尝试将其包装起来...除了 ObjectDoesNotExist,它与原来的第三个选项类似,但带有切片。 如果你最后要调用 get(),那么设置 LIMIT 的意义何在?让 ORM 和 SQL 编译器决定什么最适合它的后端(例如,在 Oracle 上,Django 模拟 LIMIT,所以它会伤害而不是帮助)。 我使用了这个没有尾随 .get() 的答案。如果返回列表,则返回列表的第一个元素。 拥有Entry.objects.all()[0]
有什么不同??
@JamesLin 不同之处在于 [:1].get() 引发了 DoesNotExist,而 [0] 引发了 IndexError。【参考方案3】:
如果你打算经常获取第一个元素 - 你可以在这个方向扩展 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()
【讨论】:
Call objects = ManagerWithFirstQuery as objects = ManagerWithFirstQuery() - 不要忘记括号 - 无论如何,你帮了我+1【参考方案4】:您应该使用 django 方法,例如存在。它在那里供您使用。
if qs.exists():
return qs[0]
return None
【讨论】:
除非我理解正确,惯用的 Python 通常使用请求宽恕比许可更容易 (EAFP) 方法而不是先看飞跃方法。 EAFP 不仅仅是一种风格推荐,它是有原因的(例如,在打开文件之前检查并不能防止错误)。这里我认为相关的考虑是exists + get item导致两次数据库查询,根据项目和视图的不同,这可能是不可取的。【参考方案5】:可以是这样的
obj = model.objects.filter(id=emp_id)[0]
或
obj = model.objects.latest('id')
【讨论】:
【参考方案6】:Django 1.6 (released Nov 2013) 引入了 convenience methods first()
和 last()
,它们会吞下生成的异常,如果查询集没有返回任何对象,则返回 None
。
【讨论】:
它不做 [:1],所以它没有那么快(除非你需要评估整个查询集)。 另外,first()
和 last()
对查询强制使用 ORDER BY
子句。这将使结果具有确定性,但很可能会减慢查询速度。
@janek37 性能没有差异。正如 cod3monk3y 所指出的,这是一种方便的方法,它不会读取整个查询集。
@Zompa 不正确。 性能存在差异,因为@Phil Krylov 指出强制执行ORDER BY
,而[:1]
避免了这一点。
回滚编辑,除了重新措辞之外没有增加任何价值,并将原始建议断章取意。我并不是说 first() 和 last() 是 fastest 方法,就性能而言,只是这些方法存在,有用且方便。没有人声称这将回答 OP 的性能目标。但很明显,我和其他人发现这些信息有点用处。【参考方案7】:
现在,在 Django 1.9 中,您有 first()
用于查询集的方法。
YourModel.objects.all().first()
这是比.get()
或[0]
更好的方法,因为如果查询集为空,它不会抛出异常,因此,您无需使用exists()
进行检查
【讨论】:
这会导致 SQL 中的 LIMIT 1 并且我已经看到声称它可以使查询变慢 - 尽管我希望看到这一点得到证实:如果查询只返回一项,为什么LIMIT 1 真的会影响性能吗?所以我认为上述答案很好,但希望看到证据证实。 我不会说“更好”。这真的取决于你的期望。【参考方案8】:这也可以:
def get_first_element(MyModel):
my_query = MyModel.objects.all()
return my_query[:1]
如果为空,则返回一个空列表,否则返回列表中的第一个元素。
【讨论】:
这是迄今为止最好的解决方案...只调用一次数据库以上是关于从 django 的查询集中获取第一个对象的最快方法?的主要内容,如果未能解决你的问题,请参考以下文章
Django:如何从与用户相关的子查询集中获取不同的父列表?