Django ORM distinct 查询,其中订单由带注释的字段完成,您需要 distinct('id')
Posted
技术标签:
【中文标题】Django ORM distinct 查询,其中订单由带注释的字段完成,您需要 distinct(\'id\')【英文标题】:Django ORM distinct query where order is done by annotated field and you need distinct('id')Django ORM distinct 查询,其中订单由带注释的字段完成,您需要 distinct('id') 【发布时间】:2017-11-09 12:32:30 【问题描述】:商家有地方。地方有一个位置坐标。我需要通过 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')
DRF 视图
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',)
utils_filters.LocationDistanceFilter
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,
)
有什么想法吗?
【问题讨论】:
您是否仅尝试过.distinct()
,即没有'pk'
参数。
嘿,@DmitrySemenov 我想知道我的回答是否有帮助
【参考方案1】:
如果您查看distinct()
文档(正如@Todor 建议的那样),您会发现:
order_by()
调用中使用的任何字段都包含在SQL SELECT
列中。与distinct()
结合使用时,这有时会导致意外结果。如果您按相关模型中的字段排序,这些字段将被添加到选定的列中,否则它们可能会使重复的行看起来不同。由于额外的列不会出现在返回的结果中(它们只是为了支持排序),因此有时看起来返回的结果不明确。
所以无论你做什么,都要牢记这一点。
让我们尝试解决这个问题:
annotate(distance=...)
创建一个名为distance
的列,您可以使用它对查询进行排序。您需要不同的商家,可以通过不同的pk
s 来保证:
...annotate(distance=...).order_by('pk', 'distance').distinct('pk')
这将首先按pk
对您的查询集进行排序,然后按distance
排序,最后它将仅返回具有不同pk
的商家。
【讨论】:
【参考方案2】:我有类似的任务,对我有帮助 https://code.djangoproject.com/ticket/24218#comment:11
所以我有模型Listing
+ 模型ListingAddress
(多对一Listing
)+ 点。任务是获得由最接近的ListingAddress
排序的Listings
的不同列表。这是我的代码:
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',
)
【讨论】:
以上是关于Django ORM distinct 查询,其中订单由带注释的字段完成,您需要 distinct('id')的主要内容,如果未能解决你的问题,请参考以下文章
django ORM model filter 条件过滤,及多表连接查询反向查询,某字段的distinct
python测试开发django(12)--ORM查询表结果