You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 02:40:30