从 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:更新查询集中对象的顺序属性

Django - 如何从模板中的查询集中获取最后一项

在 django 的查询集中获取对象的计数

Django:如何从与用户相关的子查询集中获取不同的父列表?

从 Django Rest Framework SIMPLE JWT 令牌(第 3 方)获取中间件中的用户名

Django 数据库操作