Django: 分页器 + 原生 SQL 查询

14 投票
4 回答
11703 浏览
提问于 2025-04-15 20:57

我在网站上到处使用Django的分页功能,还写了一个特别的模板标签,让它更方便使用。但现在我遇到了一个问题,我需要做一个复杂的自定义SQL查询,如果不加LIMIT的话,会返回大约10万条记录。

我该如何在自定义查询中使用Django的分页功能?

我的问题简化一下就是:

我的模型:

class PersonManager(models.Manager):

    def complicated_list(self):

        from django.db import connection

        #Real query is much more complex        
        cursor.execute("""SELECT * FROM `myapp_person`""");  

        result_list = []

        for row in cursor.fetchall():
            result_list.append(row[0]); 

        return result_list


class Person(models.Model):
    name      = models.CharField(max_length=255);
    surname   = models.CharField(max_length=255);     
    age       = models.IntegerField(); 

    objects   = PersonManager();

我用Django的ORM进行分页的方式:

all_objects = Person.objects.all();

paginator = Paginator(all_objects, 10);

try:
    page = int(request.GET.get('page', '1'))
except ValueError:
    page = 1

try:
    persons = paginator.page(page)
except (EmptyPage, InvalidPage):
    persons = paginator.page(paginator.num_pages)

这样,Django会很聪明地在执行查询时自动加上LIMIT。但是当我使用自定义管理器时:

all_objects = Person.objects.complicated_list();

所有数据都会被选出来,然后再用Python的列表切片,这样非常慢。我该如何让我的自定义管理器的行为像内置的那样?

4 个回答

2

我对Django 1.1不太了解,不过如果你能等到1.2版本(应该不会等太久),你就可以使用objects.raw()这个功能,具体可以参考这篇文章开发文档

如果你的查询不是特别复杂,可能使用extra子句就足够了。

6

这里有一个我制作的 RawPaginator 类,它是对 Paginator 的一个改进,可以处理原始查询。这个类多了一个参数 count,它表示你查询的总数。它不会对 object_list 进行切片,因为你需要在你的原始查询中通过 OFFSETLIMIT 来进行分页。

from django.core.paginator import Paginator

class RawPaginator(Paginator):
    def __init__(self, object_list, per_page, count, **kwargs):
        super().__init__(object_list, per_page, **kwargs)
        self.raw_count = count

    def _get_count(self):
        return self.raw_count
    count = property(_get_count)

    def page(self, number):
        number = self.validate_number(number)
        return self._get_page(self.object_list, number, self)
15

看了Paginator的源代码,特别是page()函数,我觉得你只需要在自己这边实现一下切片,然后把它转化为SQL查询中的LIMIT语句就可以了。你可能还需要加一些缓存,但这就有点像QuerySet了,所以也许你可以尝试其他方法:

  • 你可以用CREATE VIEW myview AS [你的查询]来创建一个数据库视图;
  • 为这个视图添加一个Django模型,并设置Meta: managed=False
  • 像使用其他模型一样使用这个模型,包括对它的查询集进行切片——这意味着它完全可以和Paginator一起使用。

(顺便说一下,我已经用这种方法很久了,甚至在处理复杂的多对多关系时,也用视图来伪装中间表。)

撰写回答