2010-03-28 81 views
9

我在我的網站上到處都是使用Django Paginator,甚至寫了一個特殊的模板標籤,以使它更方便。但是現在我到了一個狀態,在那裏我需要製作一個複雜的自定義原始SQL查詢,沒有LIMIT就會返回大約100K條記錄。Django:Paginator +原始SQL查詢

如何在自定義查詢中使用Django Pagintor?

我的問題

簡單的例子:

我的模型:

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(); 

我用pagintation 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(); 

所有數據被選擇,才把蟒蛇名單切片,這是非常緩慢的。我如何讓自定義管理器的行爲類似於內置的?

+0

在Python中,你不應該只在你的Person類中使用空格。 – Gereltod 2017-05-25 04:16:47

回答

8

查看Paginator的源代碼,特別是page() function,我認爲只需要在您的身邊實施slicing,並將其轉換爲SQL查詢中的相關LIMIT子句。您可能還需要添加一些緩存,但開始看起來像查詢集,所以也許你可以做別的事情:

  • 您可以使用CREATE VIEW MyView的AS [查詢]創建數據庫視圖。
  • 添加的Django模型的這一觀點,與Meta: managed=False
  • 使用,像任何其他的模式,包括切片其查詢集模型 - 這意味着它完全適用於分頁程序使用

(爲了您的信息 - 我我已經使用這種方法很長一段時間了,即使與VIEWs僞造m2m中間表的複雜多對多關係也是如此。)

+0

哇,多數民衆贊成酷:)謝謝你的回覆,但我guiess我正在尋找一個錯誤的方向。我問過另一個問題:http://stackoverflow.com/questions/2532686/django-getting-the-list-of-related-records-for-a-list-of-objects Guiess定製管理員不是這裏最好的事情。 – 2010-03-28 11:21:32

2

我不知道Django的1.1,但如果你可以等待1.2(這不應該是長了),如this article並在development documentation描述,你可以使objects.raw()使用。

否則,如果您查詢不太複雜,也許使用extra clause就足夠了。

+0

感謝您提供有用的提示。但我想這對我的情況沒有幫助 – 2010-03-28 11:24:42

+0

您仍然無法獲得原始查詢結果的計數。看起來你實際上必須做list(objects.raw())才能使用paginator。感謝http://stackoverflow.com/questions/2317452/django-count-rawqueryset獲取該信息。 – Josh 2012-06-19 00:03:30

1

這是一個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) 
+0

非常好的解決方案。謝謝。 – 2017-02-25 22:51:40

+0

當你調用新的Paginator類時,當作爲參數給出時,'count'會採用什麼值? – 2017-10-17 13:43:06

0

我也想插一PaginatedRawQuerySet我寫的(請認爲這是一個alpha版本)。這將切片功能添加到原始查詢集。請參閱to this answer - 我爲其他問題編寫了一個類似的需求 - 以便了解它是如何工作的(特別是最後的「謹慎之詞」一節)。

from django.db import models 
from django.db.models import sql 
from django.db.models.query import RawQuerySet 


class PaginatedRawQuerySet(RawQuerySet): 
    def __init__(self, raw_query, **kwargs): 
     super(PaginatedRawQuerySet, self).__init__(raw_query, **kwargs) 
     self.original_raw_query = raw_query 
     self._result_cache = None 

    def __getitem__(self, k): 
     """ 
     Retrieves an item or slice from the set of results. 
     """ 
     if not isinstance(k, (slice, int,)): 
      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.set_limits(start, stop) 
      return qs 

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

    def __iter__(self): 
     self._fetch_all() 
     return iter(self._result_cache) 

    def count(self): 
     if self._result_cache is not None: 
      return len(self._result_cache) 

     return self.model.objects.count() 

    def set_limits(self, start, stop): 
     limit_offset = '' 

     new_params = tuple() 
     if start is None: 
      start = 0 
     elif start > 0: 
      new_params += (start,) 
      limit_offset = ' OFFSET %s' 
     if stop is not None: 
      new_params = (stop - start,) + new_params 
      limit_offset = 'LIMIT %s' + limit_offset 

     self.params = self.params + new_params 
     self.raw_query = self.original_raw_query + limit_offset 
     self.query = sql.RawQuery(sql=self.raw_query, using=self.db, params=self.params) 

    def _fetch_all(self): 
     if self._result_cache is None: 
      self._result_cache = list(super().__iter__()) 

    def __repr__(self): 
     return '<%s: %s>' % (self.__class__.__name__, self.model.__name__) 

    def __len__(self): 
     self._fetch_all() 
     return len(self._result_cache) 

    def _clone(self): 
     clone = self.__class__(raw_query=self.raw_query, model=self.model, using=self._db, hints=self._hints, 
           query=self.query, params=self.params, translations=self.translations) 
     return clone