ChatGPT解决这个技术问题 Extra ChatGPT

从 django 的查询集中获取第一个对象的最快方法?

我经常发现自己想从 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如果您实际上以任何方式(或至少是评论)对anwser进行了基准测试,您会知道它并没有更快。它实际上可能会更慢,因为你创建一个额外的列表只是为了把它扔掉。与调用 python 函数或首先使用 Django 的 ORM 的成本相比,这一切都只是小菜一碟!对 filter() 的一次调用要比引发异常慢很多很多很多倍(仍然会引发异常,因为这就是迭代器协议的工作原理!)。
你的直觉是正确的,性能差异很小,但你的结论是错误的。我确实运行了一个基准测试,并且接受的答案实际上比实际速度更快。去搞清楚。
对于使用 Django 1.6 的人们,他们终于添加了 first()last() 便利方法:docs.djangoproject.com/en/dev/ref/models/querysets/#first

c
cod3monk3y

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() 是最快的方法,就性能而言,只是这些方法存在,有用且方便。没有人声称这将回答 OP 的性能目标。但很明显,我和其他人发现这些信息有点用处。
B
Boris Verkhovskiy

您可以使用 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。
I
Ignacio Vazquez-Abrams
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 异常。
转换为 list === 调用 __iter__ 以获取新的迭代器对象并调用它的 next 方法,直到 StopIteration 被抛出。所以肯定会有一个例外的地方;)
这个答案现在已经过时了,看看@cod3monk3y answer for Django 1.6+
l
levi

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

YourModel.objects.all().first()

这是比 .get()[0] 更好的方法,因为如果查询集为空,它不会引发异常,因此,您无需使用 exists() 进行检查


这会导致 SQL 中的 LIMIT 1 并且我已经看到声称它可以使查询变慢——尽管我希望看到这一点得到证实:如果查询只返回一个项目,为什么 LIMIT 1 真的会影响性能?所以我认为上述答案很好,但希望看到证据证实。
我不会说“更好”。这真的取决于你的期望。
N
Nick Cuevas

这也可以工作:

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

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


这是迄今为止最好的解决方案......导致只有一次调用数据库
N
Nikolay Fominyh

如果您打算经常获取第一个元素 - 您可以在这个方向扩展 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
N
Naftali

它可以是这样的

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

或者

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

A
Ari

您应该使用 django 方法,例如存在。它在那里供您使用。

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

除了,如果我理解正确的话,惯用的 Python 通常使用 比许可更容易请求宽恕 (EAFP) 方法,而不是 三思而后行 方法。
EAFP 不仅仅是一种风格推荐,它是有原因的(例如,在打开文件之前检查并不能防止错误)。这里我认为相关的考虑是exists + get item导致两次数据库查询,根据项目和视图的不同,这可能是不可取的。