商人有地方。地方有一个位置坐标。我需要通过API调用(DRF)返回 UNIQUE 商家列表,这些商家通过距离他们关联的任何地点的距离过滤,并从最近的地方返回距离值。现在我得到重复(即如果商家的几个地方在附近,商家会多次返回)。
如果我尝试annotate(distance=...).distinct('pk')
,我会收到错误消息
django.db.utils.ProgrammingError:
SELECT DISTINCT ON expressions must match initial ORDER BY expressions
LINE 1: SELECT COUNT(*) FROM (SELECT DISTINCT ON ("merchants_merchan
如果我添加.order_by('pk')
,我可以使用.distinct('pk')
,但我无法按距离对返回的查询集进行排序。
这是我到目前为止所做的:
class MerchantQuerySet(models.QuerySet):
def nearby(self, latitude, longitude, proximity=None):
"""Get nearby Merchants.
Custom queryset method for getting merchants associated with
nearby places.
Returns:
A queryset of ``Merchant`` objects.
"""
point = Point(latitude, longitude)
# we query for location nearby for places first and then
# annotate with distance to the same place
return self.filter(
places__location__distance_lte=(point, D(ft=proximity))).\
annotate(distance=Distance(
'places__location', point)).distinct()
class Merchant(TimeStampedModel):
name = models.CharField(max_length=255, verbose_name=_('Name'))
description = models.TextField(
blank=True,
verbose_name=_('Description'),
)
logo = imagekitmodels.ProcessedImageField(
max_length=512,
upload_to=get_upload_path_for_model,
processors=[ResizeToFill(300, 300)],
format='PNG',
options={'quality': 100},
editable=True,
null=True,
blank=True,
verbose_name=_('Company logo'),
help_text=_('Image will be resized to 300x300px.')
)
categories = models.ManyToManyField(
'categories.Category',
blank=True,
related_name='merchants',
verbose_name=_('Categories'),
)
address = models.TextField(blank=True, verbose_name=_('Address'))
contact = models.CharField(
max_length=32,
blank=True,
verbose_name=_('Contact phone'),
)
url = models.URLField(blank=True, verbose_name=_('Site URL'))
social_urls = ArrayField(
models.URLField(blank=True),
blank=True,
null=True,
verbose_name=_('Social URLs'),
)
budget_tips = models.TextField(
blank=True,
verbose_name=_('Budget tips'),
help_text=_('Recommendations to determine merchant budget.'),
)
objects = query.MerchantQuerySet.as_manager()
class Meta:
verbose_name = _('Merchant')
verbose_name_plural = _('Merchants')
class Place(TimeStampedModel):
id = models.CharField(
max_length=32,
primary_key=True,
unique=True,
verbose_name=_('ID'),
help_text=_('Forsquare ID of the venue.'),
)
merchant = models.ForeignKey(
'merchants.Merchant',
related_name='places',
verbose_name=_('Merchant'),
help_text=_('Merchant, business owner.'),
)
categories = models.ManyToManyField(
'categories.Category',
blank=True,
related_name='places',
verbose_name=_('Categories'),
)
name = models.CharField(
max_length=255,
blank=True,
verbose_name=_('Name')
)
address = models.TextField(blank=True, verbose_name=_('Address'))
contact = models.CharField(
max_length=32,
blank=True,
verbose_name=_('Contact phone')
)
location = PointField(blank=True, null=True, verbose_name=_('Location'))
objects = PlaceQuerySet.as_manager()
class Meta:
verbose_name = _('Place')
verbose_name_plural = _('Places')
class MerchantViewSet(
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
favorites_api_mixins.UserFavoritesMixin,
viewsets.GenericViewSet,
):
queryset = models.Merchant.objects.all()
serializer_class = serializers.MerchantSerializer
filter_backends = (MerchantOrderingFilter,
filters.DjangoFilterBackend,
filters.SearchFilter,
utils_filters.LocationDistanceFilter)
ordering_fields = ('name', 'created', 'distance')
ordering = ('-created')
search_fields = ('name',)
class LocationDistanceFilter(BaseFilterBackend):
"""Location distance filter.
Class for filtering objects by distance. Takes three GET params ``lat``,
``long`` and ``dist``:
.../?dist=300&lat=55.56&long=98.01
"""
dist_param = 'dist'
def get_filter_point(self, request):
"""Get filter point.
Get point for filtering by distance to this point.
Args:
request (Request): A ``Request`` object.
Returns:
A ``Point`` object or None.
Raises:
Parse error if point is invalid.
"""
latitude = request.query_params.get('lat')
longitude = request.query_params.get('long')
if latitude and longitude:
try:
latitude = float(latitude)
longitude = float(longitude)
except ValueError:
raise ParseError(
'Invalid geometry string supplied for '
'latitude or longitude'
)
return Point(latitude, longitude)
else:
return None
def filter_queryset(self, request, queryset, view):
"""Filter queryset.
Filter queryset by ``lat``, ``long`` and ``dist` params. Queryset
should have method ``nearby``.
Args:
request (Request): A ``Request`` object.
queryset (QuerySet): A ``QuerySet`` object.
view (ViewSet): Current API view instance.
Returns:
A query set of objects filtered by latitude, longitude
and distance.
"""
distance = request.query_params.get(self.dist_param)
point = self.get_filter_point(request)
if not point:
return queryset
return queryset.nearby(
latitude=point.x,
longitude=point.y,
proximity=distance,
)
有什么想法吗?
答案 0 :(得分:1)
如果您查看distinct()
文档(如@Todor建议的那样),您会发现:
order_by()
来电中使用的所有字段都包含在SQL SELECT
列中。当与distinct()
结合使用时,这有时会导致意外结果。如果您按相关模型中的字段进行排序,则这些字段将添加到选定的列中,否则可能会使重复的行显示为不同。由于额外的列没有出现在返回的结果中(它们仅用于支持排序),因此有时看起来会返回非独特的结果。
所以无论你做什么,都要考虑到这一点。
让我们试着解决这个问题:
annotate(distance=...)
创建一个名为distance
的列,您可以使用该列对查询进行排序。您需要通过不同的pk
s确保不同的商家:
...annotate(distance=...).order_by('pk', 'distance').distinct('pk')
这将首先按pk
然后按distance
订购您的查询集,最后它将仅返回具有不同pk
的商家。
答案 1 :(得分:0)
我有类似的任务,对我有帮助 https://code.djangoproject.com/ticket/24218#comment:11
所以我有模型Listing
+模型ListingAddress
(很多Listing
)+点。任务是获取Listings
最接近的ListingAddress
顺序的唯一列表。这是我的代码:
orm_request = ListingAddress.objects.filter(
loc__distance_lte=(point, D(m=search_distance)),
**make_category_filter(category_id),
).annotate(
distance=Distance('loc', point)
)
closest_addresses = [rec['pk'] for rec in orm_request.order_by(
'listing_id',
'distance',
).distinct(
'listing_id',
).values('pk')]
final_request = orm_request.filter(
pk__in=subquery,
).order_by(
'distance',
).select_related(
'listing',
)