2012-05-03 13 views
18

पर SELECT COUNT (*) चलाने से django व्यवस्थापक को रोकें हर बार जब मैं किसी मॉडल की प्रविष्टियों को सूचीबद्ध करने के लिए व्यवस्थापक का उपयोग करता हूं, तो व्यवस्थापक तालिका में पंक्तियों को गिनता है। इससे भी बदतर, ऐसा लगता है कि जब भी आप अपनी क्वेरी फ़िल्टर कर रहे हों।सूची फॉर्म

उदाहरण के लिए अगर मैं केवल मॉडल जिसका आईडी 123 है, 456 दिखाना चाहते हैं, 789 मैं कर सकते हैं:

/admin/myapp/mymodel/?id__in=123,456,789 

लेकिन प्रश्नों (दूसरों के बीच) भाग गया हैं:

SELECT COUNT(*) FROM `myapp_mymodel` WHERE `myapp_mymodel`.`id` IN (123, 456, 789) # okay 
SELECT COUNT(*) FROM `myapp_mymodel` # why??? 

जो MySQL + innodb हत्या कर रहा है। ऐसा लगता है कि समस्या आंशिक रूप से in this ticket स्वीकार की गई है, लेकिन मेरी समस्या अधिक विशिष्ट प्रतीत होती है क्योंकि यह सभी पंक्तियों की गणना करता है, भले ही यह नहीं माना जाता है।

क्या वैश्विक पंक्तियों को गिनने का कोई तरीका है?

नोट: मैं django 1.2.7 का उपयोग कर रहा हूं।

+1

जब आप फ़िल्टर यह जैसे को प्रदर्शित करता है "21 परिणाम (3011 कुल)", इसलिए कुल मिलाकर प्रदर्शित करने के लिए 'गिनती (*) 'आवश्यक है। इसे बंद करने के लिए, मुझे लगता है कि आपको django admin app को हैकिंग करने के लिए बहुत कुछ करना होगा। – Alasdair

+0

अब मैं देखता हूं, धन्यवाद। मुझे लगता है कि हमें उस टिकट के माध्यम से जाने के लिए इंतजार करना होगा। – Nova

+0

'नोट: मैं django 1.2.7 का उपयोग कर रहा हूं। - उन्नयन पर विचार करें। –

उत्तर

18

ठीक है, मुझे लगता है कि मुझे एक समाधान मिला है।पीटर का सुझाव दिया, सबसे अच्छा तरीका count संपत्ति पर काम करने के लिए है और यह कस्टम क्वेरी सेट (this post में देखा जाता है) के साथ यह अधिभावी द्वारा किया जा सकता है कि लगभग एक बराबर के साथ गिनती माहिर: में फिर

from django.db import connections, models 
from django.db.models.query import QuerySet 

class ApproxCountQuerySet(QuerySet): 
    """Counting all rows is very expensive on large Innodb tables. This 
    is a replacement for QuerySet that returns an approximation if count() 
    is called with no additional constraints. In all other cases it should 
    behave exactly as QuerySet. 

    Only works with MySQL. Behaves normally for all other engines. 
    """ 

    def count(self): 
     # Code from django/db/models/query.py 

     if self._result_cache is not None and not self._iter: 
      return len(self._result_cache) 

     is_mysql = 'mysql' in connections[self.db].client.executable_name.lower() 

     query = self.query 
     if (is_mysql and not query.where and 
       query.high_mark is None and 
       query.low_mark == 0 and 
       not query.select and 
       not query.group_by and 
       not query.having and 
       not query.distinct): 
      # If query has no constraints, we would be simply doing 
      # "SELECT COUNT(*) FROM foo". Monkey patch so the we 
      # get an approximation instead. 
      cursor = connections[self.db].cursor() 
      cursor.execute("SHOW TABLE STATUS LIKE %s", 
        (self.model._meta.db_table,)) 
      return cursor.fetchall()[0][4] 
     else: 
      return self.query.get_count(using=self.db) 

व्यवस्थापक:

class MyAdmin(admin.ModelAdmin): 

    def queryset(self, request): 
     qs = super(MyAdmin, self).queryset(request) 
     return qs._clone(klass=ApproxCountQuerySet) 

अनुमानित कार्य पृष्ठ संख्या 100000 पर गड़बड़ कर सकता है, लेकिन यह मेरे मामले के लिए पर्याप्त है।

+2

धन्यवाद। थोड़ा tweaking के साथ, यह PostgreSQL में भी मेरे लिए काम किया, हालांकि मुझे 'SELECT reltuples :: int FROM pg_class WHERE oid ='% s ':: regclass; ' – Cerin

+4

क्वेरी का उपयोग करना था 1.9 – LS55321

+0

@ LS55321 कोई भी काम नहीं करता विचार करें कि हम ApproxCountQuerySet का उपयोग कैसे कर सकते हैं? – Jickson

3

यदि यह एक गंभीर समस्या है तो आपको कठोर क्रियाएं लेनी पड़ सकती है।

1.3.1 इंस्टॉल के लिए कोड को देखते हुए, मुझे लगता है कि व्यवस्थापक कोड द्वारा लौटाए गए पेजिनेटर का उपयोग कर रहा है। डिफ़ॉल्ट पेजिनेटर वर्ग django/core/paginator.py में प्रतीत होता है। उस वर्ग में _count नामक एक निजी मूल्य है जो Paginator._get_count() (मेरी प्रतिलिपि में पंक्ति 120) में सेट है। बदले में यह संपत्ति को count नामक पेजिनेटर क्लास सेट करने के लिए उपयोग किया जाता है। मुझे लगता है कि _get_count() आपका लक्ष्य है। अब मंच सेट है।

  1. सीधे स्रोत को संशोधित:

    आप विकल्पों में से एक जोड़ी है। मैं नहीं इसकी अनुशंसा करता हूं, लेकिन चूंकि आप 1.2.7 पर फंस गए प्रतीत होते हैं, तो आप पाएंगे कि यह सबसे अधिक उपयुक्त है। इस परिवर्तन को दस्तावेज करना याद रखें! भविष्य के रखरखाव (संभावित रूप से स्वयं सहित) आपको सिर के लिए धन्यवाद देंगे।

  2. कक्षा बंदरपैच। यह प्रत्यक्ष संशोधन से बेहतर है क्योंकि ए) यदि आपको परिवर्तन पसंद नहीं है तो आप बंदरपैच पर टिप्पणी करें, और बी) यह Django के भविष्य के संस्करणों के साथ काम करने की अधिक संभावना है। मेरे पास 4 साल से अधिक समय तक एक बंदरगाह वापस जा रहा है क्योंकि उन्होंने अभी भी टेम्पलेट चर _resolve_lookup() कोड में एक बग तय नहीं किया है जो केवल निम्न स्तर पर मूल्यांकन के शीर्ष स्तर पर कॉलबेल को पहचाना नहीं जाता है। हालांकि पैच (जो कक्षा के तरीके को लपेटता है) 0.97-प्री के खिलाफ लिखा गया था, फिर भी यह 1.3.1 पर काम करता है।

मैं यह पता लगाने की वास्तव में क्या बदल जाता है आप अपनी समस्या के लिए बनाने के लिए होता है समय बिताने नहीं किया, लेकिन यह उचित वर्गों class META करने के लिए एक _approx_count सदस्य को जोड़ने की तर्ज पर हो सकता है और उसके बाद यह देखने के लिए परीक्षण वह अटारी मौजूद है। यदि यह करता है और None है तो आप sql.count() करते हैं और इसे सेट करते हैं। यदि आप सूची के अंतिम पृष्ठ पर (या निकट) हैं, तो आपको इसे रीसेट करने की भी आवश्यकता हो सकती है। अगर आपको इस पर थोड़ा और मदद चाहिए तो मुझसे संपर्क करें; मेरा ईमेल मेरी प्रोफाइल में है।

7

मुझे नोवा का उत्तर बहुत उपयोगी पाया गया, लेकिन मैं पोस्टग्रेस का उपयोग करता हूं। मैंने टेबल नामस्थानों को संभालने के लिए कुछ मामूली बदलावों के साथ पोस्टग्रेज़ के लिए काम करने के लिए इसे थोड़ा संशोधित किया, और थोड़ा अलग "पोस्टग्रेस का पता लगाएं" तर्क।

यहां पीजी संस्करण है।

class ApproxCountPgQuerySet(models.query.QuerySet): 
    """approximate unconstrained count(*) with reltuples from pg_class""" 

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

     if hasattr(connections[self.db].client.connection, 'pg_version'): 
      query = self.query 
      if (not query.where and query.high_mark is None and query.low_mark == 0 and 
       not query.select and not query.group_by and not query.having and not query.distinct): 
       # If query has no constraints, we would be simply doing 
       # "SELECT COUNT(*) FROM foo". Monkey patch so the we get an approximation instead. 
       parts = [p.strip('"') for p in self.model._meta.db_table.split('.')] 
       cursor = connections[self.db].cursor() 
       if len(parts) == 1: 
        cursor.execute("select reltuples::bigint FROM pg_class WHERE relname = %s", parts) 
       else: 
        cursor.execute("select reltuples::bigint FROM pg_class c JOIN pg_namespace n on (c.relnamespace = n.oid) WHERE n.nspname = %s AND c.relname = %s", parts) 
      return cursor.fetchall()[0][0] 
     return self.query.get_count(using=self.db) 
17

Django 1.8 आपको show_full_result_count = False सेट करके इसे अक्षम करने देता है।

https://docs.djangoproject.com/en/1.8/ref/contrib/admin/#django.contrib.admin.ModelAdmin.show_full_result_count

+10

यह केवल एक फ़िल्टर की गई क्वेरीसेट पर है। यदि आप फ़िल्टर के बिना व्यवस्थापक को मार रहे हैं, तो यह अभी भी SELECT COUNT (*) का उपयोग करेगा ... –

3

नोवा के समाधान (ApproxCountQuerySet) अच्छा काम करता है, फिर भी Django क्वेरीसमूह विधि के नए संस्करण में, get_queryset से बदल दिया है तो अब यह होना चाहिए:

class MyAdmin(admin.ModelAdmin): 

    def get_queryset(self, request): 
     qs = super(MyAdmin, self).get_queryset(request) 
     return qs._clone(klass=ApproxCountQuerySet)