Django Queryset .last() 处理返回不正确元素的查询集的方法

Django Queryset .last() method on processed queryset returning incorrect element

我有以下 python 代码:

models.py
class Person(models.Model):
    first_name = models.CharField(max_length=32, null=True, blank=True)
    last_name = models.CharField(max_length=64, null=True, blank=True)
    order = models.PositiveSmallIntegerField(null=True, blank=True)

我添加了两个人 Persons,分别命名为 'Person 1' 和 'Person 2'。他们都有相同的顺序 1.

views.py
def get_people():
    people = Person.objects.order_by('order')
    print(people)
    for p in people:
        print(p)
        if p == people.last():
            print ('Last Person')

结果如下:

>>> get_people()
[<Person: Person 1>, <Person: Person 2>]
<Person 1>
u'Last Person'
<Person 2>

进行了一些挖掘,但我发现了这个结果和根本原因。

>>> people = Person.objects.order_by('order')
>>> print(people)
[<Person: Person 1>, <Person: Person 2>]
>>> print(people.first())
<Person 1>
>>> print(people.last())
<Person 1>
>>> people.first() == people.last()
True
>>> people[0]
<Person 1>
>>> people[1]
<Person 2>

我查看了源代码,看起来 last() 方法只是 运行 reverse(),与我选择的顺序相同。由于这两个元素的序号相同,均为 1,因此反向方法返回与原始列表完全相同的列表,假设因为在反向排序时,应用相同的规则,即在平局中,具有最低记录 ID 的元素排在第一位,而不是真正反转已经检索到的列表。我不明白为什么他们不只获取已经检索到的元素列表并从索引中获取最后一个元素。我尝试使用 [-1] 负索引来获取它,但未实现并引发异常。

那么有人可以解释为什么要这样编码吗?如果您的几个元素对于被排序的 属性 共享相同的值,则可能会出现问题。特别是如果通过随后调用 last() 多次访问查询集。是为了性能还是为了我没有看到的其他问题?我没有在这个用例中使用 last() 方法,而是简单地进行了比较,而不是:

if p == people[len(people) - 1]:

这行得通。在这种情况下,我知道 people 不是空的,所以我们不会得到 IndexError - 如果它是空的,代码将永远不会在循环中执行。一般情况可能是:

l = len(people)
return None if l == 0 else return people[l -1]

或者:

try:
    l = len(people)
    return people[l - 1]
except IndexError:
    return None

你能分享一些关于这种行为的见解吗? Django 文档中唯一指出 last() 方法就像 first() 但 returns 查询集的最后一个元素。在这种情况下,它没有按描述运行。这种行为让我很困惑。我认为它只会从当前列表中取出最后一个元素,而不是制作一个新的反向列表并获取其中的第一个元素。

提前致谢...

我认为代码的问题在于您按升序排序,让 Django 弄清楚如何处理两者之间的决胜局。 SQL 中与您在 get_people() 方法中编写的等效内容如下:

SELECT * FROM Person ORDER BY order ASC

因此,如果有两个人都具有相同的 "order" 值,您的结果将永远不会正确返回。相反,您需要一个看起来更类似于此的查询:

SELECT * FROM Person ORDER BY order, last_name, first_name(假设您想在排序后按姓氏排序。

我 运行 曾经设计过一个应用程序遇到这样的问题,解决方案非常简单。与其绞尽脑汁试图用 Django API 找出底层的 "problem"(尽管实际上它只是和表的设计一样聪明),你可以使用这样的东西:

views.py
def get_people():
    people = Person.objects.order_by('order', 'last_name', 'first_name')
    print(people)
    for p in people:
        print(p)
        if p == people.last():
            print ('Last Person')

请注意,在我们通过 Django "creating the query" 的行中,我包含了多个列。这将解决您的领带问题,因此如果两个人的顺序相同,它将按姓氏排序。

如果有人考虑过这种极端情况,原因很可能是一致性和性能的结合。

首先,您通常无法评估整个查询集只是为了获得最后一个元素而不会造成巨大的性能损失。 Person.objects.order_by('order').last() 应该得到一行,而不是整个 table -- 它可能包含数百万行。因此,如果是未计算的查询集,您需要反转 SQL 中的顺序并获取顶部元素。这将始终受到您描述的问题的困扰。

只有在对查询集求值时,您才能获取缓存中的最后一个元素,但这意味着您会得到不一致的结果。取下面的代码:

people = Person.objects.order_by('order')
p1 = people.last()
bool(people)
p2 = people.last()

在您的示例中,p1 将是 <Person 1>。但是,如果在计算查询集时取缓存的最后一个元素,p2 会突然变成 <Person 2>,因为缓存已满。这种自我矛盾使开发人员的工作非常困难。

虽然这可能不是很直观,但这是在实际数据库查询中转换 .last() 方法并同时获得 acceptable 性能和自洽结果的最佳方式。未排序或部分排序的结果集具有 undefined 顺序(甚至可能在查询之间任意更改)这一事实是 SQL 的一个很好理解的方面,因此总体而言,这是least astonishment 的路径。