【问题标题】:Why do queryset[0] and queryset.first() return different records?为什么 queryset[0] 和 queryset.first() 返回不同的记录?
【发布时间】:2020-03-05 20:49:16
【问题描述】:

我今天发现我可以通过索引来访问查询集中的元素,即queryset[n]。然而,在我发现queryset[0] 没有返回与queryset.first() 相同的记录之后。为什么会这样,并且是其中一个更“正确”的? (我知道.first() 更快,但除此之外)

Python 3.7.4
django 1.11.20

【问题讨论】:

  • .first() 如果您没有没有定义特定订单,则按主键订购。此外,.first() 本身并不快。事实上,它通常会更慢。根据查询集是否已经缓存,它甚至可以导致 less 个查询。

标签: python django django-queryset


【解决方案1】:

qs[0]qs.first() 之间存在细微的语义差异。如果您没有自己在查询集中指定顺序,那么 Django 将在获取第一个元素之前按主键对查询集本身进行排序。

此外,如果查询集为空.first() 将返回 None。而qs[0] 将引发IndexError

声称.first() 更快,但不是 True。事实上,如果你使用qs[<i>n</i>],那么 Django 将在幕后通过使用qs[<i>n</i>:<i>n</i>+1] 切片来获取记录,因此它会在数据库后端支持这一点的情况下使用LIMIT 1 OFFSET <i>n</i> 进行查询,从而获取一条记录,就像.first() 会做的那样。如果查询集已经被检索到,那么它将不会再进行额外的查询根本,因为数据已经被缓存了。

你可以在GitHub看到实现:

    def first(self):
        """
        Returns the first object of a query, returns None if no match is found.
        """
        objects = list((self if self.ordered else self.order_by('pk'))[:1])
        if objects:
            return objects[0]
        return None

如您所见,如果查询集已经排序(self.orderedTrue,那么我们取self[:1],检查是否有记录,如果有则返回。如果没有我们返回None .

在特定索引处检索项目的代码更加神秘。它本质上将设置从kk+1 的限制,具体化项目,并返回第一个项目,正如我们在source code [GitHub] 中看到的那样:

    def __getitem__(self, k):
        """
        Retrieves an item or slice from the set of results.
        """
        if not isinstance(k, (slice,) + six.integer_types):
            raise TypeError
        assert ((not isinstance(k, slice) and (k >= 0)) or
                (isinstance(k, slice) and (k.start is None or k.start >= 0) and
                 (k.stop is None or k.stop >= 0))), \
            "Negative indexing is not supported."

        if self._result_cache is not None:
            return self._result_cache[k]

        if isinstance(k, slice):
            qs = self._clone()
            if k.start is not None:
                start = int(k.start)
            else:
                start = None
            if k.stop is not None:
                stop = int(k.stop)
            else:
                stop = None
            qs.query.set_limits(start, stop)
            return list(qs)[::k.step] if k.step else qs

        qs = self._clone()
        qs.query.set_limits(k, k + 1)
        return list(qs)[0]

【讨论】:

  • 啊,好吧,所以qs.first() 在获得第一个项目之前按 pk 订购它,而 qs[0] 不是。那么,在对其应用订单之前,它处于什么“订单”中?
  • @SaintWacko:不,只有当查询集没有提前排序时。如果您使用MyModel.objects.order_by('some_field'),那么它的顺序将不会不同。仅当您没有自己订购时,.first() 才会按主键订购。如果您没有指定订单,则数据库会确定一个,并且可以是任何订单。
  • 对,对不起,忘了说明我假设的是一个无序的查询集。无论如何,这回答了我的问题,但是,出于好奇,您是否有任何关于数据库如何确定订单的信息?我已经多次检索查询集,并且它始终是相同的顺序,只是不确定它是如何按这个顺序来的。
  • @SaintWacko:这通常由索引机制的底层特征指定,但它取决于数据库,而且通常修改(如插入、删除或更新)会改变命令。一些数据库通常会列出从新到旧的项目,但有时过滤索引可能会改变这种行为。
猜你喜欢
  • 2019-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-27
  • 2015-06-21
  • 2022-01-23
  • 1970-01-01
相关资源
最近更新 更多