Django ORM条件部分去重:匹配公司时仅返回单一场地
解决DRF中按Venue/Company名称搜索时的结果去重需求
核心思路
将搜索拆分为两个独立查询:
- 匹配Venue名称的所有结果
- 匹配Company名称但每个Company仅返回一个Venue的结果
最后合并两个查询集并自动去重,避免重复返回同一Venue。
基于PostgreSQL的实现方案(使用DISTINCT ON)
模型假设
class Company(models.Model): name = models.CharField(max_length=255) class Venue(models.Model): name = models.CharField(max_length=255) company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='venues')
在ViewSet的get_queryset中实现
from django.db.models import Q from rest_framework import viewsets from .models import Venue from .serializers import VenueSerializer class VenueSearchViewSet(viewsets.ReadOnlyModelViewSet): queryset = Venue.objects.all() serializer_class = VenueSerializer def get_queryset(self): search_text = self.request.query_params.get('text', '') if not search_text: return super().get_queryset() # 1. 匹配Venue名称的所有结果 venue_matches = Venue.objects.filter(name__icontains=search_text) # 2. 匹配Company名称,每个Company仅返回一个Venue(这里取ID最小的,可按需调整排序字段) company_matches = Venue.objects.filter( company__name__icontains=search_text ).distinct('company_id').order_by('company_id', 'id') # 合并两个查询集并去重 return venue_matches.union(company_matches)
关键点说明
distinct('company_id'):PostgreSQL特有的DISTINCT ON语法,确保每个company_id仅返回一条记录order_by('company_id', 'id'):必须先按去重字段排序,否则Django会抛出语法错误;第二个字段决定每个Company返回哪一个Venue(比如改成'-created_at'可返回最新创建的Venue)union:自动合并两个查询集并去重,避免同一Venue因同时匹配名称和所属Company名称而重复返回
用django-filter实现过滤逻辑
如果项目使用django-filter统一管理过滤规则,可以在FilterSet中实现相同逻辑:
import django_filters from .models import Venue class VenueFilter(django_filters.FilterSet): text = django_filters.CharFilter(method='filter_text') class Meta: model = Venue fields = ['text'] def filter_text(self, queryset, name, value): if not value: return queryset venue_matches = queryset.filter(name__icontains=value) company_matches = queryset.filter(company__name__icontains=value).distinct('company_id').order_by('company_id', 'id') return venue_matches.union(company_matches)
然后在ViewSet中配置:
class VenueSearchViewSet(viewsets.ReadOnlyModelViewSet): queryset = Venue.objects.all() serializer_class = VenueSerializer filterset_class = VenueFilter
跨数据库兼容的替代方案(使用Window函数)
如果需要兼容非PostgreSQL数据库,可以用Django的Window函数实现每个Company返回特定Venue:
from django.db.models import Window, F from django.db.models.functions import Rank # 在get_queryset或filter方法中替换company_matches部分 company_matches = Venue.objects.filter( company__name__icontains=search_text ).annotate( # 按公司分组,按评分降序排名,取排名第一的Venue rank=Window( expression=Rank(), partition_by=F('company_id'), order_by=F('rating').desc() ) ).filter(rank=1)
这种方式不依赖数据库特定语法,可灵活调整每个Company返回的Venue规则(比如评分最高、最新创建等),但需要注意为相关字段添加索引以优化性能。
内容的提问来源于stack exchange,提问作者kunambi
相关产品推荐
相关产品推荐

