Django Queryset .last()方法返回错误的元素



我有以下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"one_answers"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>

我查看了源代码,似乎最后一个()方法只是以我选择的相同顺序运行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()一样,但是返回查询集的最后一个元素。在这种情况下,它的功能不像描述的那样。这种行为使我迷惑不解。我认为它只会从当前列表中取出最后一个元素,而不是创建一个新的反向列表并获取其中的第一个元素。

如果有人仔细思考这个边缘情况,原因很可能是一致性和性能的结合。

首先,通常不可能仅仅为了得到最后一个元素而求值整个查询集,而不会造成巨大的性能损失。Person.objects.order_by('order').last()应该得到一行,而不是整个表——它可能包含数百万行。因此,对于未求值的查询集,您需要在SQL中颠倒顺序并获取顶部元素。这总是会遇到你所描述的问题。

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

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

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

虽然这可能不是很直观,但这是在实际数据库查询中转换.last()方法并获得可接受的性能和自一致结果的最佳方法。事实上,无序或部分有序的结果集具有未定义的顺序(甚至可以在查询之间任意更改),这是SQL的一个很好理解的方面,所以总的来说,这是最不令人惊讶的路径。

我认为这段代码的问题在于,你是按升序排序的,而把如何处理两者之间的tie breaker留给了Django。在SQL中与您在get_people()方法中编写的内容等效如下:

SELECT * FROM Person ORDER BY order ASC

所以在有两个人都有相同的"顺序"值的情况下,你的结果永远不会正确返回。相反,您希望查询看起来更像这样:

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

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

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"创建查询"的那一行,我包含了多个列。这将解决您的领带问题,因此,如果两个人有相同的订单,它将按姓氏排序。

相关内容

  • 没有找到相关文章

最新更新