2016-07-01 22 views
2

所以我有这样的查询集:优化Django的计数()方法

from django.contrib.gis.db.models.query import GeoQuerySet 
from django.db import models as base_models 

class RestaurantsQuerySet(GeoQuerySet): 
    def get_list(self, lng, lat): 
     reference_point = Point(lng, lat, srid=SRID) 

     return self.annotate(rating=models.Avg('comments__rating'))\ 
        .annotate(distance=Distance('location', reference_point)) 

    def count(self): 
     return self.values('id').aggregate(count=base_models.Count('id'))['count'] 

我认为查询看起来是这样的:

SELECT COUNT("__col1") 
FROM (
    SELECT "restaurants_restaurant"."id" AS "__col1" 
    FROM "restaurants_restaurant" 
    GROUP BY "restaurants_restaurant"."id") subquery 

,而是Django的ORM创建这个小怪物:

SELECT COUNT("__col1") 
    FROM (
     SELECT "restaurants_restaurant"."id" AS Col1, "restaurants_restaurant"."id" AS "__col1" 
     FROM "restaurants_restaurant" 
     LEFT OUTER JOIN "comments_comment" ON ("restaurants_restaurant"."id" = "comments_comment"."restaurant_id") 
     GROUP BY "restaurants_restaurant"."id", ST_Distance_Sphere("restaurants_restaurant"."location", 
       ST_GeomFromEWKB('\x0101000020e61000003eb555a41d2d4b405a338d81d0a73240'::bytea 
))) subquery 

要调用的第一种方法是get_list。它看起来好像django会“记住”那个调用,并且qs被注解为ratingdistance,并将它也放入count查询中。所以我想问题是 - 在注释它之前,我怎样“重置”这个查询集到状态?

编辑:

好吧,看来我的问题是不完整的。我也有定义的RestaurantsList观点如下:

class RestaurantList(generics.ListAPIView): 
    def get_queryset(self): 
     return Restaurant.objects.get_list(self._lng, self._lat) 

我接过来一看进入Django的REST的架构的内脏,我可以看到这一点:

class ListModelMixin(object): 
    """ 
    List a queryset. 
    """ 
    def list(self, request, *args, **kwargs): 
     queryset = self.filter_queryset(self.get_queryset()) 

     page = self.paginate_queryset(queryset) 
     if page is not None: 
      serializer = self.get_serializer(page, many=True) 
      return self.get_paginated_response(serializer.data) 

     serializer = self.get_serializer(queryset, many=True) 
     return Response(serializer.data) 

所以看起来它总是使用从get_queryset方法返回的查询集以及由distancerating注释的查询集最终包含在计数查询中。仍然没有解决这个...

回答

0

通过Django的REST的框架代码浏览后,我想出了这个主意:

  1. 覆盖默认Paginator类对于给定的观点:

    class RestaurantList(generics.ListAPIView): 
        pagination_class = custom.LimitOffsetPagination 
    
  2. 创建自定义分页类:

    class LimitOffsetPagination(pagination.LimitOffsetPagination): 
        def __init__(self): 
         self._countable_queryset = None 
         self._was_counted = False 
    
        def was_initialized(self): 
         return self._countable_queryset is not None 
    
        def set_raw_queryset_for_count(self, queryset: QuerySet): 
         self._countable_queryset = queryset 
    
        def paginate_queryset(self, queryset, request, view=None): 
         self.limit = self.get_limit(request) 
         if self.limit is None: 
          return None 
         self.offset = self.get_offset(request) 
         self.count = self._countable_queryset.count() 
         self.request = request 
         if self.count > self.limit and self.template is not None: 
          self.display_page_controls = True 
         return list(queryset[self.offset:self.offset + self.limit]) 
    
  3. 覆盖意见paginator属性:

    @property 
    def paginator(self): 
        paginator = super().paginator 
        if not paginator.was_initialized(): 
         paginator.set_raw_queryset_for_count(Restaurant.objects.all()) 
        return paginator 
    

现在count查询会看起来有点更友好

SELECT COUNT(*) AS "__count" FROM "restaurants_restaurant"