2016-08-18 55 views
0

我有以下Python代码:Django的查询集。去年()方法返回不正确元件

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名为“人1”和“人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()的后续调用来多次访问queryset。这是为了表现还是我没有看到其他问题?相反,在这个用例使用最后的()方法,我只是在做这种比较,而不是:

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

这工作。在这种情况下,我知道这些人并不是空的,所以我们不会得到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()类似,但返回查询集的最后一个元素。在这种情况下,它不像描述的那样运行。这种行为让我感到困惑。我认为它只是从当前列表中取出最后一个元素,而不是创建一个新的反向列表并获取第一个元素。

在此先感谢...

+1

你为什么不定义一个更好的排序?你有它的方式他们是一个领带,所以这里的第一个和最后一个想法是没有意义的.. – wim

+0

这只是一个例子。人们在添加时应该被正确地命令,但不知怎的,他们不知所措。在可能具有相同记录的多个记录的字段上进行订购时,有许多实际示例。说出生日期。 – Furbeenator

回答

2

如果有人对这个边缘案例有任何想法,其原因很可能是一致性和性能的组合。

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

只有在计算queryset时,才可以获取缓存中的最后一个元素,但这意味着您会得到不一致的结果。看看下面的代码:

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

在你的榜样,p1<Person 1>。但是,如果在计算查询集时采用高速缓存的最后一个元素,则p2会突然变为<Person 2>,这是因为缓存已填满。这种自我矛盾使得开发者的工作非常困难。

虽然这可能不是很直观,但它是在实际数据库查询中翻译.last()方法并获得可接受的性能和自洽结果的最佳方法。无序或部分排序的结果集有一个未定义的顺序(甚至可能在查询之间任意改变)的事实是SQL的一个很好理解的方面,因此总的来说这是least astonishment的路径。

+0

啊哈。得到它了。感谢您的澄清。 – Furbeenator

1

相信随着代码的问题是,你以升序排列留给了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“创建查询”的那一行包括多列。这将解决你的领带问题,所以如果两个人有相同的顺序,它会按姓氏排序。

+0

是的,我理解并感谢您的回答。在这种情况下,我只想通过订单字段进行订购,并且在很多情况下您不希望进行额外的订购,但我想您可以添加ID或其他内容。尽管如此,对于我所写的每个查询集都必须这样做,我似乎很愚蠢。我更加好奇他们为什么选择以这种方式写出来,或者如果它是随意的。似乎对我来说反转列表的效率较低,而不仅仅是获取长度并获得列表末尾的元素。 – Furbeenator

+0

不幸的是,这可能是你不得不问的开发者。我会假设他们给出的答案可能类似,因为他们会说这是“你正在排序的问题”,而不是他们的“我们如何排序”问题。请记住,您在Django中构建的查询只是转换为SQL的一种方式,因此限制甚至可能不在Django上。 :-) –

+0

我听到了。感谢您的洞察力。 – Furbeenator